Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

« AWS créé un fork de mon projet et le lance comme son propre service », dit le créateur de l'application headless recorder
Auquel Amazon répond : « nous promettons de mieux faire. »

Le , par Patrick Ruiz

49PARTAGES

10  0 
Headless recorder est une extension Chrome qui enregistre les interactions des internautes avec leur navigateur web et génère un script Puppeteer ou Playwright. Son créateur accuse Amazon Web Services d’avoir créé un fork de son projet et de l’avoir lancé comme son propre service. La situation divise les observateurs : un pan est d’avis que les créateurs de projets qui finissent par être forkés par des géants technologiques méritent compensation pour leurs efforts ; un autre lui oppose la nécessité pour les développeurs de choisir des licences logicielles qui cadrent avec leurs prétentions.

Le code source de l’application web headless recorder est disponible. Ci-dessous celui des sections enregistreur d’événements et contrôleur d’interfaces utilisateur :

Code JavaScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import eventsToRecord from '../code-generator/dom-events-to-record'
import UIController from './UIController'
import actions from '../models/extension-ui-actions'
import ctrl from '../models/extension-control-messages'
import finder from '@medv/finder'
 
const DEFAULT_MOUSE_CURSOR = 'default'
 
export default class EventRecorder {
  constructor () {
    this._boundedMessageListener = null
    this._eventLog = []
    this._previousEvent = null
    this._dataAttribute = null
    this._uiController = null
    this._screenShotMode = false
    this._isTopFrame = (window.location === window.parent.location)
    this._isRecordingClicks = true
  }
 
  boot () {
    // We need to check the existence of chrome for testing purposes
    if (chrome.storage && chrome.storage.local) {
      chrome.storage.local.get(['options'], ({options}) => {
        const { dataAttribute } = options ? options.code : {}
        if (dataAttribute) {
          this._dataAttribute = dataAttribute
        }
        this._initializeRecorder()
      })
    } else {
      this._initializeRecorder()
    }
  }
 
  _initializeRecorder () {
    const events = Object.values(eventsToRecord)
    if (!window.pptRecorderAddedControlListeners) {
      this._addAllListeners(events)
      this._boundedMessageListener = this._boundedMessageListener || this._handleBackgroundMessage.bind(this)
      chrome.runtime.onMessage.addListener(this._boundedMessageListener)
      window.pptRecorderAddedControlListeners = true
    }
 
    if (!window.document.pptRecorderAddedControlListeners && chrome.runtime && chrome.runtime.onMessage) {
      window.document.pptRecorderAddedControlListeners = true
    }
 
    if (this._isTopFrame) {
      this._sendMessage({ control: ctrl.EVENT_RECORDER_STARTED })
      this._sendMessage({ control: ctrl.GET_CURRENT_URL, href: window.location.href })
      this._sendMessage({ control: ctrl.GET_VIEWPORT_SIZE, coordinates: { width: window.innerWidth, height: window.innerHeight } })
      console.debug('Puppeteer Recorder in-page EventRecorder started')
    }
  }
 
  _handleBackgroundMessage (msg, sender, sendResponse) {
    console.debug('content-script: message from background', msg)
    if (msg && msg.action) {
      switch (msg.action) {
        case actions.TOGGLE_SCREENSHOT_MODE:
          this._handleScreenshotMode(false)
          break
        case actions.TOGGLE_SCREENSHOT_CLIPPED_MODE:
          this._handleScreenshotMode(true)
          break
        default:
      }
    }
  }
 
  _addAllListeners (events) {
    const boundedRecordEvent = this._recordEvent.bind(this)
    events.forEach(type => {
      window.addEventListener(type, boundedRecordEvent, true)
    })
  }
 
  _sendMessage (msg) {
    // filter messages based on enabled / disabled features
    if (msg.action === 'click' && !this._isRecordingClicks) return
 
    try {
      // poor man's way of detecting whether this script was injected by an actual extension, or is loaded for
      // testing purposes
      if (chrome.runtime && chrome.runtime.onMessage) {
        chrome.runtime.sendMessage(msg)
      } else {
        this._eventLog.push(msg)
      }
    } catch (err) {
      console.debug('caught error', err)
    }
  }
 
  _recordEvent (e) {
    if (this._previousEvent && this._previousEvent.timeStamp === e.timeStamp) return
    this._previousEvent = e
 
    // we explicitly catch any errors and swallow them, as none node-type events are also ingested.
    // for these events we cannot generate selectors, which is OK
    try {
      const optimizedMinLength = (e.target.id) ? 2 : 10 // if the target has an id, use that instead of multiple other selectors
      const selector = this._dataAttribute
        ? finder(e.target, {seedMinLength: 5, optimizedMinLength: optimizedMinLength, attr: (name, _value) => name === this._dataAttribute})
        : finder(e.target, {seedMinLength: 5, optimizedMinLength: optimizedMinLength})
 
      const msg = {
        selector: selector,
        value: e.target.value,
        tagName: e.target.tagName,
        action: e.type,
        keyCode: e.keyCode ? e.keyCode : null,
        href: e.target.href ? e.target.href : null,
        coordinates: EventRecorder._getCoordinates(e)
      }
      this._sendMessage(msg)
    } catch (e) {}
  }
 
  _getEventLog () {
    return this._eventLog
  }
 
  _clearEventLog () {
    this._eventLog = []
  }
 
  _handleScreenshotMode (isClipped) {
    this._disableClickRecording()
    this._uiController = new UIController({ showSelector: isClipped })
    this._screenShotMode = !this._screenShotMode
    document.body.style.cursor = 'crosshair'
 
    console.debug('screenshot mode:', this._screenShotMode)
 
    if (this._screenShotMode) {
      this._uiController.showSelector()
    } else {
      this._uiController.hideSelector()
    }
 
    this._uiController.on('click', event => {
      this._screenShotMode = false
      document.body.style.cursor = DEFAULT_MOUSE_CURSOR
      this._sendMessage({ control: ctrl.GET_SCREENSHOT, value: event.clip })
      this._enableClickRecording()
    })
  }
 
  _disableClickRecording () {
    this._isRecordingClicks = false
  }
 
  _enableClickRecording () {
    this._isRecordingClicks = true
  }
 
  static _getCoordinates (evt) {
    const eventsWithCoordinates = {
      mouseup: true,
      mousedown: true,
      mousemove: true,
      mouseover: true
    }
    return eventsWithCoordinates[evt.type] ? { x: evt.clientX, y: evt.clientY } : null
  }
 
  static _formatDataSelector (element, attribute) {
    return `[${attribute}="${element.getAttribute(attribute)}"]`
  }
}
Code JavaScript : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import EventEmitter from 'events'
 
const BORDER_THICKNESS = 3
 
const defaults = {
  showSelector: false
}
 
class UIController extends EventEmitter {
  constructor (options) {
    options = Object.assign({}, defaults, options)
 
    super()
    this._overlay = null
    this._selector = null
    this._element = null
    this._dimensions = {}
    this._showSelector = options.showSelector
 
    this._boundeMouseMove = this._mousemove.bind(this)
    this._boundeMouseUp = this._mouseup.bind(this)
  }
 
  showSelector () {
    console.debug('UIController:show')
    if (!this._overlay) {
      this._overlay = document.createElement('div')
      this._overlay.className = 'headlessRecorderOverlay'
      this._overlay.style.position = 'fixed'
      this._overlay.style.top = '0px'
      this._overlay.style.left = '0px'
      this._overlay.style.width = '100%'
      this._overlay.style.height = '100%'
      this._overlay.style.pointerEvents = 'none'
 
      if (this._showSelector) {
        this._selector = document.createElement('div')
        this._selector.className = 'headlessRecorderOutline'
        this._selector.style.position = 'fixed'
        this._selector.style.border = BORDER_THICKNESS + 'px solid rgba(69,200,241,0.8)'
        this._selector.style.borderRadius = '3px'
        this._overlay.appendChild(this._selector)
      }
    }
    if (!this._overlay.parentNode) {
      document.body.appendChild(this._overlay)
      document.body.addEventListener('mousemove', this._boundeMouseMove, false)
      document.body.addEventListener('click', this._boundeMouseUp, false)
    }
  }
 
  hideSelector () {
    console.debug('UIController:hide')
    if (this._overlay) {
      document.body.removeChild(this._overlay)
    }
    this._overlay = this._selector = this._element = null
    this._dimensions = {}
  }
 
  _mousemove (e) {
    if (this._element !== e.target) {
      this._element = e.target
 
      this._dimensions.top = -window.scrollY
      this._dimensions.left = -window.scrollX
 
      let elem = e.target
 
      while (elem && elem !== document.body) {
        this._dimensions.top += elem.offsetTop
        this._dimensions.left += elem.offsetLeft
        elem = elem.offsetParent
      }
      this._dimensions.width = this._element.offsetWidth
      this._dimensions.height = this._element.offsetHeight
 
      if (this._selector) {
        this._selector.style.top = (this._dimensions.top - BORDER_THICKNESS) + 'px'
        this._selector.style.left = (this._dimensions.left - BORDER_THICKNESS) + 'px'
        this._selector.style.width = this._dimensions.width + 'px'
        this._selector.style.height = this._dimensions.height + 'px'
        console.debug(`top: ${this._selector.style.top}, left: ${this._selector.style.left}, width: ${this._selector.style.width}, height: ${this._selector.style.height}`)
      }
    }
  }
  _mouseup (e) {
    this._overlay.style.backgroundColor = 'white'
    setTimeout(() => {
      this._overlay.style.backgroundColor = 'none'
      this._cleanup()
 
      let clip = null
 
      if (this._selector) {
        clip = {
          x: this._selector.style.left,
          y: this._selector.style.top,
          width: this._selector.style.width,
          height: this._selector.style.height
        }
      }
 
      this.emit('click', { clip, raw: e })
    }, 100)
  }
 
  _cleanup () {
    document.body.removeEventListener('mousemove', this._boundeMouseMove, false)
    document.body.removeEventListener('mouseup', this._boundeMouseUp, false)
    document.body.removeChild(this._overlay)
  }
}
 
module.exports = UIController

En matière d’open source, l’un des principes de base est d’ouvrir l’accès d’une base de code à des tiers pour qu’ils puissent en faire une copie et introduire des modifications en fonction de la nouvelle orientation recherchée. Les contraintes additionnelles sont spécifiques à chaque type de licence. C’est grosso modo ce sur quoi Amazon s’est appuyé pour créer un fork du projet Headless recorder sachant que ce dernier est publié sous une licence permissive : Apache version 2.0. L’entreprise l’a ensuite lancé comme son propre service, ce que rappelle l’auteur de l’application Headless recorder tout en rappelant quelles sont les obligations d’Amazon…


La situation n’est pas sans faire penser à celle du système de gestion de base de données clé-valeur scalable – Redis. Le cœur du projet est publié sous licence permissive BSD. Au troisième trimestre de l’année 2018, l’équipe Redis a procédé à une reformulation des licences de certains de ses modules complémentaires, bloquant de fait leur utilisation par des tiers offrant des services commerciaux basés sur Redis. L’entreprise visait les gros fournisseurs de cloud computing : Google, Amazon, Microsoft, IBM.

« Les fournisseurs de services cloud ont à plusieurs reprises tiré profit de projets open source réussis et les ont reconditionnés en offres de services propriétaires compétitives. Les fournisseurs de cloud computing contribuent très peu (voire pas du tout) à ces projets open source. Au lieu de cela, ils utilisent leur nature monopolistique pour en tirer des centaines de millions de dollars de revenus. Ce comportement porte préjudice aux communautés open source et a mis en faillite certaines des entreprises à pied d’œuvre dans la sphère », avait lancé l’équipe Redis.

Le responsable de ces questions chez Amazon a fait une sortie pour indiquer qu’il n’avait pas été mis au courant et est en train d’étudier la question. « Je vous suis reconnaissant de votre aide dans ce domaine. AWS utilise beaucoup de logiciels libres et nous y contribuons beaucoup. Mais l'open source est en fin de compte une communauté d'utilisateurs et je pense personnellement que nous aurions pu faire mieux ici. Je vais voir avec vous comment nous pouvons mieux soutenir l'excellent travail que vous faites avec headless recorder », a-t-il ajouté.


Source : Twitter, annonce du lancement du service par Amazon

Et vous ?

Qui d'Amazon ou des responsables du projet Headless recorder est le plus à blâmer dans cette situation ?
Le projet Headless recorder ne bénéficie-t-il pas quelque part d'une meilleure visibilité au travers d'Amazon ?

voir aussi :

la rubrique Cloud computing

Une erreur dans cette actualité ? Signalez-le nous !

Avatar de redcurve
Membre éclairé https://www.developpez.com
Le 17/10/2020 à 12:05
Ouais enfin le type propose son soft dans une licence en mode yolo, et vient se plaindre après

Concernant Redis sur Azure Microsoft propose Azure Cache For Redis, et RedisLab propose des images Enterprise ce qui est plutôt cool. Sur AWS je ne sais pas du tout ce qui est proposé.

Je dirai que la différence entre ces différents fournisseurs tiens à leur politique une grosse partie des briques utilisés par Microsoft sur le health data hub est dispo sur leur repo Github, notamment toute la partie gestion des différents formats d'entrées sorties, DICOM server, FHIR server, pour les stats ils utilisent uniquement le format SARIF, pour le deep learning sur les données médical ils utilisent InnerEye-DeepLearning. Tout ça est dispo sous licence MIT.

Par contre j'ai genre 100 fois moins confiance en AWS ils ne publient pas du tout le code de brique aussi importantes en plus de ne pas avoir du tout le même politique en terme de datacenter.
5  1 
Avatar de schlebe
Membre régulier https://www.developpez.com
Le 24/10/2020 à 22:16
Dans cet article, il est écrit

La situation divise les observateurs : un pan est d’avis que les créateurs de projets qui finissent par être forkés par des géants technologiques méritent compensation pour leurs efforts ; un autre lui oppose la nécessité pour les développeurs de choisir des licences logicielles qui cadrent avec leurs prétentions.
Autrement dit, si vous voulez être rétribué en postant votre code source, veuillez choisir une licence qui indique que votre code ne peut pas être utilisé par des entreprises hors rétribution.

Mais quelle est donc cette licence à choisir qui permettrait à un développeur d'être rétribué par les entreprises qui cloneraient son code tout en permettant aux autres développeurs de cloner gratuitement ce même code ?

Si cette licence existe, quel est donc le prix d'une rétribution juste ?
2  0 
Avatar de L33tige
Membre éprouvé https://www.developpez.com
Le 19/10/2020 à 11:17
Citation Envoyé par rthomas Voir le message
Que seraient Amazon, Google,. ... sans Linux ?
C'est assez paradoxale, les défenseurs du libre sont souvent anti gafam hors ils n'existeraient peut être pas sans l'Open Source et le "gratuit".
En quoi c'est paradoxale ? C'est comme dire qu'il y aurait pas de meurtre sans victime...Ca fait que souligner son propos au contraire.
2  1 
Avatar de rthomas
Membre du Club https://www.developpez.com
Le 18/10/2020 à 6:29
Que seraient Amazon, Google,. ... sans Linux ?
C'est assez paradoxale, les défenseurs du libre sont souvent anti gafam hors ils n'existeraient peut être pas sans l'Open Source et le "gratuit".
4  4 
Avatar de darklinux
Membre éclairé https://www.developpez.com
Le 18/10/2020 à 13:09
D ' ou la nécessité de savoir ce que l ' on veut dès le départ
1  1