@scratch/scratch-vm 11.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/.jsdoc.json +20 -0
  2. package/.nvmrc +1 -0
  3. package/CHANGELOG.md +7004 -0
  4. package/LICENSE +12 -0
  5. package/README.md +120 -0
  6. package/TRADEMARK +1 -0
  7. package/commitlint.config.js +4 -0
  8. package/docs/extensions.md +527 -0
  9. package/package.json +103 -0
  10. package/release.config.js +13 -0
  11. package/src/blocks/scratch3_control.js +206 -0
  12. package/src/blocks/scratch3_core_example.js +69 -0
  13. package/src/blocks/scratch3_data.js +257 -0
  14. package/src/blocks/scratch3_event.js +137 -0
  15. package/src/blocks/scratch3_looks.js +612 -0
  16. package/src/blocks/scratch3_motion.js +288 -0
  17. package/src/blocks/scratch3_operators.js +154 -0
  18. package/src/blocks/scratch3_procedures.js +79 -0
  19. package/src/blocks/scratch3_sensing.js +336 -0
  20. package/src/blocks/scratch3_sound.js +350 -0
  21. package/src/dispatch/central-dispatch.js +143 -0
  22. package/src/dispatch/shared-dispatch.js +235 -0
  23. package/src/dispatch/worker-dispatch.js +110 -0
  24. package/src/engine/adapter.js +176 -0
  25. package/src/engine/block-utility.js +242 -0
  26. package/src/engine/blocks-execute-cache.js +19 -0
  27. package/src/engine/blocks-runtime-cache.js +78 -0
  28. package/src/engine/blocks.js +1299 -0
  29. package/src/engine/comment.js +56 -0
  30. package/src/engine/execute.js +580 -0
  31. package/src/engine/monitor-record.js +23 -0
  32. package/src/engine/mutation-adapter.js +48 -0
  33. package/src/engine/profiler.js +390 -0
  34. package/src/engine/runtime.js +2686 -0
  35. package/src/engine/scratch-blocks-constants.js +27 -0
  36. package/src/engine/sequencer.js +361 -0
  37. package/src/engine/stage-layering.js +29 -0
  38. package/src/engine/target.js +799 -0
  39. package/src/engine/thread.js +404 -0
  40. package/src/engine/variable.js +70 -0
  41. package/src/extension-support/argument-type.js +47 -0
  42. package/src/extension-support/block-type.js +50 -0
  43. package/src/extension-support/define-messages.js +18 -0
  44. package/src/extension-support/extension-manager.js +440 -0
  45. package/src/extension-support/extension-metadata.js +64 -0
  46. package/src/extension-support/extension-worker.js +59 -0
  47. package/src/extension-support/reporter-scope.js +18 -0
  48. package/src/extension-support/target-type.js +17 -0
  49. package/src/extensions/scratch3_boost/index.js +2113 -0
  50. package/src/extensions/scratch3_ev3/index.js +1355 -0
  51. package/src/extensions/scratch3_gdx_for/index.js +981 -0
  52. package/src/extensions/scratch3_gdx_for/scratch-link-device-adapter.js +44 -0
  53. package/src/extensions/scratch3_makeymakey/index.js +396 -0
  54. package/src/extensions/scratch3_microbit/index.js +984 -0
  55. package/src/extensions/scratch3_music/assets/drums/1-snare.mp3 +0 -0
  56. package/src/extensions/scratch3_music/assets/drums/10-wood-block.mp3 +0 -0
  57. package/src/extensions/scratch3_music/assets/drums/11-cowbell.mp3 +0 -0
  58. package/src/extensions/scratch3_music/assets/drums/12-triangle.mp3 +0 -0
  59. package/src/extensions/scratch3_music/assets/drums/13-bongo.mp3 +0 -0
  60. package/src/extensions/scratch3_music/assets/drums/14-conga.mp3 +0 -0
  61. package/src/extensions/scratch3_music/assets/drums/15-cabasa.mp3 +0 -0
  62. package/src/extensions/scratch3_music/assets/drums/16-guiro.mp3 +0 -0
  63. package/src/extensions/scratch3_music/assets/drums/17-vibraslap.mp3 +0 -0
  64. package/src/extensions/scratch3_music/assets/drums/18-cuica.mp3 +0 -0
  65. package/src/extensions/scratch3_music/assets/drums/2-bass-drum.mp3 +0 -0
  66. package/src/extensions/scratch3_music/assets/drums/3-side-stick.mp3 +0 -0
  67. package/src/extensions/scratch3_music/assets/drums/4-crash-cymbal.mp3 +0 -0
  68. package/src/extensions/scratch3_music/assets/drums/5-open-hi-hat.mp3 +0 -0
  69. package/src/extensions/scratch3_music/assets/drums/6-closed-hi-hat.mp3 +0 -0
  70. package/src/extensions/scratch3_music/assets/drums/7-tambourine.mp3 +0 -0
  71. package/src/extensions/scratch3_music/assets/drums/8-hand-clap.mp3 +0 -0
  72. package/src/extensions/scratch3_music/assets/drums/9-claves.mp3 +0 -0
  73. package/src/extensions/scratch3_music/assets/instruments/1-piano/108.mp3 +0 -0
  74. package/src/extensions/scratch3_music/assets/instruments/1-piano/24.mp3 +0 -0
  75. package/src/extensions/scratch3_music/assets/instruments/1-piano/36.mp3 +0 -0
  76. package/src/extensions/scratch3_music/assets/instruments/1-piano/48.mp3 +0 -0
  77. package/src/extensions/scratch3_music/assets/instruments/1-piano/60.mp3 +0 -0
  78. package/src/extensions/scratch3_music/assets/instruments/1-piano/72.mp3 +0 -0
  79. package/src/extensions/scratch3_music/assets/instruments/1-piano/84.mp3 +0 -0
  80. package/src/extensions/scratch3_music/assets/instruments/1-piano/96.mp3 +0 -0
  81. package/src/extensions/scratch3_music/assets/instruments/10-clarinet/48.mp3 +0 -0
  82. package/src/extensions/scratch3_music/assets/instruments/10-clarinet/60.mp3 +0 -0
  83. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3 +0 -0
  84. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3 +0 -0
  85. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3 +0 -0
  86. package/src/extensions/scratch3_music/assets/instruments/12-flute/60.mp3 +0 -0
  87. package/src/extensions/scratch3_music/assets/instruments/12-flute/72.mp3 +0 -0
  88. package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/60.mp3 +0 -0
  89. package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/72.mp3 +0 -0
  90. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/36.mp3 +0 -0
  91. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/48.mp3 +0 -0
  92. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/60.mp3 +0 -0
  93. package/src/extensions/scratch3_music/assets/instruments/15-choir/48.mp3 +0 -0
  94. package/src/extensions/scratch3_music/assets/instruments/15-choir/60.mp3 +0 -0
  95. package/src/extensions/scratch3_music/assets/instruments/15-choir/72.mp3 +0 -0
  96. package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/60.mp3 +0 -0
  97. package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/72.mp3 +0 -0
  98. package/src/extensions/scratch3_music/assets/instruments/17-music-box/60.mp3 +0 -0
  99. package/src/extensions/scratch3_music/assets/instruments/18-steel-drum/60.mp3 +0 -0
  100. package/src/extensions/scratch3_music/assets/instruments/19-marimba/60.mp3 +0 -0
  101. package/src/extensions/scratch3_music/assets/instruments/2-electric-piano/60.mp3 +0 -0
  102. package/src/extensions/scratch3_music/assets/instruments/20-synth-lead/60.mp3 +0 -0
  103. package/src/extensions/scratch3_music/assets/instruments/21-synth-pad/60.mp3 +0 -0
  104. package/src/extensions/scratch3_music/assets/instruments/3-organ/60.mp3 +0 -0
  105. package/src/extensions/scratch3_music/assets/instruments/4-guitar/60.mp3 +0 -0
  106. package/src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3 +0 -0
  107. package/src/extensions/scratch3_music/assets/instruments/6-bass/36.mp3 +0 -0
  108. package/src/extensions/scratch3_music/assets/instruments/6-bass/48.mp3 +0 -0
  109. package/src/extensions/scratch3_music/assets/instruments/7-pizzicato/60.mp3 +0 -0
  110. package/src/extensions/scratch3_music/assets/instruments/8-cello/36.mp3 +0 -0
  111. package/src/extensions/scratch3_music/assets/instruments/8-cello/48.mp3 +0 -0
  112. package/src/extensions/scratch3_music/assets/instruments/8-cello/60.mp3 +0 -0
  113. package/src/extensions/scratch3_music/assets/instruments/9-trombone/36.mp3 +0 -0
  114. package/src/extensions/scratch3_music/assets/instruments/9-trombone/48.mp3 +0 -0
  115. package/src/extensions/scratch3_music/assets/instruments/9-trombone/60.mp3 +0 -0
  116. package/src/extensions/scratch3_music/index.js +1333 -0
  117. package/src/extensions/scratch3_music/manifest.js +63 -0
  118. package/src/extensions/scratch3_pen/index.js +770 -0
  119. package/src/extensions/scratch3_speech2text/index.js +700 -0
  120. package/src/extensions/scratch3_text2speech/index.js +766 -0
  121. package/src/extensions/scratch3_translate/index.js +286 -0
  122. package/src/extensions/scratch3_video_sensing/debug.js +13 -0
  123. package/src/extensions/scratch3_video_sensing/index.js +594 -0
  124. package/src/extensions/scratch3_video_sensing/library.js +384 -0
  125. package/src/extensions/scratch3_video_sensing/math.js +76 -0
  126. package/src/extensions/scratch3_video_sensing/view.js +509 -0
  127. package/src/extensions/scratch3_wedo2/index.js +1616 -0
  128. package/src/import/load-costume.js +416 -0
  129. package/src/import/load-sound.js +116 -0
  130. package/src/index.js +10 -0
  131. package/src/io/ble.js +256 -0
  132. package/src/io/bt.js +202 -0
  133. package/src/io/clock.js +39 -0
  134. package/src/io/cloud.js +169 -0
  135. package/src/io/keyboard.js +146 -0
  136. package/src/io/mouse.js +145 -0
  137. package/src/io/mouseWheel.js +28 -0
  138. package/src/io/userData.js +24 -0
  139. package/src/io/video.js +210 -0
  140. package/src/playground/benchmark.css +83 -0
  141. package/src/playground/benchmark.js +753 -0
  142. package/src/playground/index.html +111 -0
  143. package/src/playground/suite.css +78 -0
  144. package/src/playground/suite.html +25 -0
  145. package/src/playground/suite.js +544 -0
  146. package/src/playground/video-sensing.html +18 -0
  147. package/src/playground/video-sensing.js +133 -0
  148. package/src/serialization/deserialize-assets.js +178 -0
  149. package/src/serialization/sb2.js +1295 -0
  150. package/src/serialization/sb2_specmap.js +1818 -0
  151. package/src/serialization/sb3.js +1320 -0
  152. package/src/serialization/serialize-assets.js +60 -0
  153. package/src/sprites/rendered-target.js +1115 -0
  154. package/src/sprites/sprite.js +177 -0
  155. package/src/util/base64-util.js +48 -0
  156. package/src/util/cast.js +218 -0
  157. package/src/util/clone.js +17 -0
  158. package/src/util/color.js +204 -0
  159. package/src/util/fetch-with-timeout.js +53 -0
  160. package/src/util/get-monitor-id.js +33 -0
  161. package/src/util/jsonrpc.js +114 -0
  162. package/src/util/log.js +4 -0
  163. package/src/util/math-util.js +122 -0
  164. package/src/util/maybe-format-message.js +18 -0
  165. package/src/util/new-block-ids.js +33 -0
  166. package/src/util/rateLimiter.js +73 -0
  167. package/src/util/scratch-link-websocket.js +133 -0
  168. package/src/util/string-util.js +94 -0
  169. package/src/util/task-queue.js +203 -0
  170. package/src/util/timer.js +103 -0
  171. package/src/util/uid.js +29 -0
  172. package/src/util/variable-util.js +48 -0
  173. package/src/util/xml-escape.js +33 -0
  174. package/src/virtual-machine.js +1586 -0
  175. package/webpack.config.js +134 -0
package/src/io/ble.js ADDED
@@ -0,0 +1,256 @@
1
+ const JSONRPC = require('../util/jsonrpc');
2
+
3
+ class BLE extends JSONRPC {
4
+
5
+ /**
6
+ * A BLE peripheral socket object. It handles connecting, over web sockets, to
7
+ * BLE peripherals, and reading and writing data to them.
8
+ * @param {Runtime} runtime - the Runtime for sending/receiving GUI update events.
9
+ * @param {string} extensionId - the id of the extension using this socket.
10
+ * @param {object} peripheralOptions - the list of options for peripheral discovery.
11
+ * @param {object} connectCallback - a callback for connection.
12
+ * @param {object} resetCallback - a callback for resetting extension state.
13
+ */
14
+ constructor (runtime, extensionId, peripheralOptions, connectCallback, resetCallback = null) {
15
+ super();
16
+
17
+ this._socket = runtime.getScratchLinkSocket('BLE');
18
+ this._socket.setOnOpen(this.requestPeripheral.bind(this));
19
+ this._socket.setOnClose(this.handleDisconnectError.bind(this));
20
+ this._socket.setOnError(this._handleRequestError.bind(this));
21
+ this._socket.setHandleMessage(this._handleMessage.bind(this));
22
+
23
+ this._sendMessage = this._socket.sendMessage.bind(this._socket);
24
+
25
+ this._availablePeripherals = {};
26
+ this._connectCallback = connectCallback;
27
+ this._connected = false;
28
+ this._characteristicDidChangeCallback = null;
29
+ this._resetCallback = resetCallback;
30
+ this._discoverTimeoutID = null;
31
+ this._extensionId = extensionId;
32
+ this._peripheralOptions = peripheralOptions;
33
+ this._runtime = runtime;
34
+
35
+ this._socket.open();
36
+ }
37
+
38
+ /**
39
+ * Request connection to the peripheral.
40
+ * If the web socket is not yet open, request when the socket promise resolves.
41
+ */
42
+ requestPeripheral () {
43
+ this._availablePeripherals = {};
44
+ if (this._discoverTimeoutID) {
45
+ window.clearTimeout(this._discoverTimeoutID);
46
+ }
47
+ this._discoverTimeoutID = window.setTimeout(this._handleDiscoverTimeout.bind(this), 15000);
48
+ this.sendRemoteRequest('discover', this._peripheralOptions)
49
+ .catch(e => {
50
+ this._handleRequestError(e);
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Try connecting to the input peripheral id, and then call the connect
56
+ * callback if connection is successful.
57
+ * @param {number} id - the id of the peripheral to connect to
58
+ */
59
+ connectPeripheral (id) {
60
+ this.sendRemoteRequest('connect', {peripheralId: id})
61
+ .then(() => {
62
+ this._connected = true;
63
+ this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTED);
64
+ this._connectCallback();
65
+ })
66
+ .catch(e => {
67
+ this._handleRequestError(e);
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Close the websocket.
73
+ */
74
+ disconnect () {
75
+ if (this._connected) {
76
+ this._connected = false;
77
+ }
78
+
79
+ if (this._socket.isOpen()) {
80
+ this._socket.close();
81
+ }
82
+
83
+ if (this._discoverTimeoutID) {
84
+ window.clearTimeout(this._discoverTimeoutID);
85
+ }
86
+
87
+ // Sets connection status icon to orange
88
+ this._runtime.emit(this._runtime.constructor.PERIPHERAL_DISCONNECTED);
89
+ }
90
+
91
+ /**
92
+ * @return {bool} whether the peripheral is connected.
93
+ */
94
+ isConnected () {
95
+ return this._connected;
96
+ }
97
+
98
+ /**
99
+ * Start receiving notifications from the specified ble service.
100
+ * @param {number} serviceId - the ble service to read.
101
+ * @param {number} characteristicId - the ble characteristic to get notifications from.
102
+ * @param {object} onCharacteristicChanged - callback for characteristic change notifications.
103
+ * @return {Promise} - a promise from the remote startNotifications request.
104
+ */
105
+ startNotifications (serviceId, characteristicId, onCharacteristicChanged = null) {
106
+ const params = {
107
+ serviceId,
108
+ characteristicId
109
+ };
110
+ this._characteristicDidChangeCallback = onCharacteristicChanged;
111
+ return this.sendRemoteRequest('startNotifications', params)
112
+ .catch(e => {
113
+ this.handleDisconnectError(e);
114
+ });
115
+ }
116
+
117
+ /**
118
+ * Read from the specified ble service.
119
+ * @param {number} serviceId - the ble service to read.
120
+ * @param {number} characteristicId - the ble characteristic to read.
121
+ * @param {boolean} optStartNotifications - whether to start receiving characteristic change notifications.
122
+ * @param {object} onCharacteristicChanged - callback for characteristic change notifications.
123
+ * @return {Promise} - a promise from the remote read request.
124
+ */
125
+ read (serviceId, characteristicId, optStartNotifications = false, onCharacteristicChanged = null) {
126
+ const params = {
127
+ serviceId,
128
+ characteristicId
129
+ };
130
+ if (optStartNotifications) {
131
+ params.startNotifications = true;
132
+ }
133
+ if (onCharacteristicChanged) {
134
+ this._characteristicDidChangeCallback = onCharacteristicChanged;
135
+ }
136
+ return this.sendRemoteRequest('read', params)
137
+ .catch(e => {
138
+ this.handleDisconnectError(e);
139
+ });
140
+ }
141
+
142
+ /**
143
+ * Write data to the specified ble service.
144
+ * @param {number} serviceId - the ble service to write.
145
+ * @param {number} characteristicId - the ble characteristic to write.
146
+ * @param {string} message - the message to send.
147
+ * @param {string} encoding - the message encoding type.
148
+ * @param {boolean} withResponse - if true, resolve after peripheral's response.
149
+ * @return {Promise} - a promise from the remote send request.
150
+ */
151
+ write (serviceId, characteristicId, message, encoding = null, withResponse = null) {
152
+ const params = {serviceId, characteristicId, message};
153
+ if (encoding) {
154
+ params.encoding = encoding;
155
+ }
156
+ if (withResponse !== null) {
157
+ params.withResponse = withResponse;
158
+ }
159
+ return this.sendRemoteRequest('write', params)
160
+ .catch(e => {
161
+ this.handleDisconnectError(e);
162
+ });
163
+ }
164
+
165
+ /**
166
+ * Handle a received call from the socket.
167
+ * @param {string} method - a received method label.
168
+ * @param {object} params - a received list of parameters.
169
+ * @return {object} - optional return value.
170
+ */
171
+ didReceiveCall (method, params) {
172
+ switch (method) {
173
+ case 'didDiscoverPeripheral':
174
+ this._availablePeripherals[params.peripheralId] = params;
175
+ this._runtime.emit(
176
+ this._runtime.constructor.PERIPHERAL_LIST_UPDATE,
177
+ this._availablePeripherals
178
+ );
179
+ if (this._discoverTimeoutID) {
180
+ window.clearTimeout(this._discoverTimeoutID);
181
+ }
182
+ break;
183
+ case 'userDidPickPeripheral':
184
+ this._availablePeripherals[params.peripheralId] = params;
185
+ this._runtime.emit(
186
+ this._runtime.constructor.USER_PICKED_PERIPHERAL,
187
+ this._availablePeripherals
188
+ );
189
+ if (this._discoverTimeoutID) {
190
+ window.clearTimeout(this._discoverTimeoutID);
191
+ }
192
+ break;
193
+ case 'userDidNotPickPeripheral':
194
+ this._runtime.emit(
195
+ this._runtime.constructor.PERIPHERAL_SCAN_TIMEOUT
196
+ );
197
+ if (this._discoverTimeoutID) {
198
+ window.clearTimeout(this._discoverTimeoutID);
199
+ }
200
+ break;
201
+ case 'characteristicDidChange':
202
+ if (this._characteristicDidChangeCallback) {
203
+ this._characteristicDidChangeCallback(params.message);
204
+ }
205
+ break;
206
+ case 'ping':
207
+ return 42;
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Handle an error resulting from losing connection to a peripheral.
213
+ *
214
+ * This could be due to:
215
+ * - battery depletion
216
+ * - going out of bluetooth range
217
+ * - being powered down
218
+ *
219
+ * Disconnect the socket, and if the extension using this socket has a
220
+ * reset callback, call it. Finally, emit an error to the runtime.
221
+ */
222
+ handleDisconnectError (/* e */) {
223
+ // log.error(`BLE error: ${JSON.stringify(e)}`);
224
+
225
+ if (!this._connected) return;
226
+
227
+ this.disconnect();
228
+
229
+ if (this._resetCallback) {
230
+ this._resetCallback();
231
+ }
232
+
233
+ this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTION_LOST_ERROR, {
234
+ message: `Scratch lost connection to`,
235
+ extensionId: this._extensionId
236
+ });
237
+ }
238
+
239
+ _handleRequestError (/* e */) {
240
+ // log.error(`BLE error: ${JSON.stringify(e)}`);
241
+
242
+ this._runtime.emit(this._runtime.constructor.PERIPHERAL_REQUEST_ERROR, {
243
+ message: `Scratch lost connection to`,
244
+ extensionId: this._extensionId
245
+ });
246
+ }
247
+
248
+ _handleDiscoverTimeout () {
249
+ if (this._discoverTimeoutID) {
250
+ window.clearTimeout(this._discoverTimeoutID);
251
+ }
252
+ this._runtime.emit(this._runtime.constructor.PERIPHERAL_SCAN_TIMEOUT);
253
+ }
254
+ }
255
+
256
+ module.exports = BLE;
package/src/io/bt.js ADDED
@@ -0,0 +1,202 @@
1
+ const JSONRPC = require('../util/jsonrpc');
2
+
3
+ class BT extends JSONRPC {
4
+
5
+ /**
6
+ * A BT peripheral socket object. It handles connecting, over web sockets, to
7
+ * BT peripherals, and reading and writing data to them.
8
+ * @param {Runtime} runtime - the Runtime for sending/receiving GUI update events.
9
+ * @param {string} extensionId - the id of the extension using this socket.
10
+ * @param {object} peripheralOptions - the list of options for peripheral discovery.
11
+ * @param {object} connectCallback - a callback for connection.
12
+ * @param {object} resetCallback - a callback for resetting extension state.
13
+ * @param {object} messageCallback - a callback for message sending.
14
+ */
15
+ constructor (runtime, extensionId, peripheralOptions, connectCallback, resetCallback = null, messageCallback) {
16
+ super();
17
+
18
+ this._socket = runtime.getScratchLinkSocket('BT');
19
+ this._socket.setOnOpen(this.requestPeripheral.bind(this));
20
+ this._socket.setOnError(this._handleRequestError.bind(this));
21
+ this._socket.setOnClose(this.handleDisconnectError.bind(this));
22
+ this._socket.setHandleMessage(this._handleMessage.bind(this));
23
+
24
+ this._sendMessage = this._socket.sendMessage.bind(this._socket);
25
+
26
+ this._availablePeripherals = {};
27
+ this._connectCallback = connectCallback;
28
+ this._connected = false;
29
+ this._characteristicDidChangeCallback = null;
30
+ this._resetCallback = resetCallback;
31
+ this._discoverTimeoutID = null;
32
+ this._extensionId = extensionId;
33
+ this._peripheralOptions = peripheralOptions;
34
+ this._messageCallback = messageCallback;
35
+ this._runtime = runtime;
36
+
37
+ this._socket.open();
38
+ }
39
+
40
+ /**
41
+ * Request connection to the peripheral.
42
+ * If the web socket is not yet open, request when the socket promise resolves.
43
+ */
44
+ requestPeripheral () {
45
+ this._availablePeripherals = {};
46
+ if (this._discoverTimeoutID) {
47
+ window.clearTimeout(this._discoverTimeoutID);
48
+ }
49
+ this._discoverTimeoutID = window.setTimeout(this._handleDiscoverTimeout.bind(this), 15000);
50
+ this.sendRemoteRequest('discover', this._peripheralOptions)
51
+ .catch(
52
+ e => this._handleRequestError(e)
53
+ );
54
+ }
55
+
56
+ /**
57
+ * Try connecting to the input peripheral id, and then call the connect
58
+ * callback if connection is successful.
59
+ * @param {number} id - the id of the peripheral to connect to
60
+ * @param {string} pin - an optional pin for pairing
61
+ */
62
+ connectPeripheral (id, pin = null) {
63
+ const params = {peripheralId: id};
64
+ if (pin) {
65
+ params.pin = pin;
66
+ }
67
+ this.sendRemoteRequest('connect', params)
68
+ .then(() => {
69
+ this._connected = true;
70
+ this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTED);
71
+ this._connectCallback();
72
+ })
73
+ .catch(e => {
74
+ this._handleRequestError(e);
75
+ });
76
+ }
77
+
78
+ /**
79
+ * Close the websocket.
80
+ */
81
+ disconnect () {
82
+ if (this._connected) {
83
+ this._connected = false;
84
+ }
85
+
86
+ if (this._socket.isOpen()) {
87
+ this._socket.close();
88
+ }
89
+
90
+ if (this._discoverTimeoutID) {
91
+ window.clearTimeout(this._discoverTimeoutID);
92
+ }
93
+
94
+ // Sets connection status icon to orange
95
+ this._runtime.emit(this._runtime.constructor.PERIPHERAL_DISCONNECTED);
96
+ }
97
+
98
+ /**
99
+ * @return {bool} whether the peripheral is connected.
100
+ */
101
+ isConnected () {
102
+ return this._connected;
103
+ }
104
+
105
+ sendMessage (options) {
106
+ return this.sendRemoteRequest('send', options)
107
+ .catch(e => {
108
+ this.handleDisconnectError(e);
109
+ });
110
+ }
111
+
112
+ /**
113
+ * Handle a received call from the socket.
114
+ * @param {string} method - a received method label.
115
+ * @param {object} params - a received list of parameters.
116
+ * @return {object} - optional return value.
117
+ */
118
+ didReceiveCall (method, params) {
119
+ // TODO: Add peripheral 'undiscover' handling
120
+ switch (method) {
121
+ case 'didDiscoverPeripheral':
122
+ this._availablePeripherals[params.peripheralId] = params;
123
+ this._runtime.emit(
124
+ this._runtime.constructor.PERIPHERAL_LIST_UPDATE,
125
+ this._availablePeripherals
126
+ );
127
+ if (this._discoverTimeoutID) {
128
+ window.clearTimeout(this._discoverTimeoutID);
129
+ }
130
+ break;
131
+ case 'userDidPickPeripheral':
132
+ this._availablePeripherals[params.peripheralId] = params;
133
+ this._runtime.emit(
134
+ this._runtime.constructor.USER_PICKED_PERIPHERAL,
135
+ this._availablePeripherals
136
+ );
137
+ if (this._discoverTimeoutID) {
138
+ window.clearTimeout(this._discoverTimeoutID);
139
+ }
140
+ break;
141
+ case 'userDidNotPickPeripheral':
142
+ this._runtime.emit(
143
+ this._runtime.constructor.PERIPHERAL_SCAN_TIMEOUT
144
+ );
145
+ if (this._discoverTimeoutID) {
146
+ window.clearTimeout(this._discoverTimeoutID);
147
+ }
148
+ break;
149
+ case 'didReceiveMessage':
150
+ this._messageCallback(params); // TODO: refine?
151
+ break;
152
+ default:
153
+ return 'nah';
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Handle an error resulting from losing connection to a peripheral.
159
+ *
160
+ * This could be due to:
161
+ * - battery depletion
162
+ * - going out of bluetooth range
163
+ * - being powered down
164
+ *
165
+ * Disconnect the socket, and if the extension using this socket has a
166
+ * reset callback, call it. Finally, emit an error to the runtime.
167
+ */
168
+ handleDisconnectError (/* e */) {
169
+ // log.error(`BT error: ${JSON.stringify(e)}`);
170
+
171
+ if (!this._connected) return;
172
+
173
+ this.disconnect();
174
+
175
+ if (this._resetCallback) {
176
+ this._resetCallback();
177
+ }
178
+
179
+ this._runtime.emit(this._runtime.constructor.PERIPHERAL_CONNECTION_LOST_ERROR, {
180
+ message: `Scratch lost connection to`,
181
+ extensionId: this._extensionId
182
+ });
183
+ }
184
+
185
+ _handleRequestError (/* e */) {
186
+ // log.error(`BT error: ${JSON.stringify(e)}`);
187
+
188
+ this._runtime.emit(this._runtime.constructor.PERIPHERAL_REQUEST_ERROR, {
189
+ message: `Scratch lost connection to`,
190
+ extensionId: this._extensionId
191
+ });
192
+ }
193
+
194
+ _handleDiscoverTimeout () {
195
+ if (this._discoverTimeoutID) {
196
+ window.clearTimeout(this._discoverTimeoutID);
197
+ }
198
+ this._runtime.emit(this._runtime.constructor.PERIPHERAL_SCAN_TIMEOUT);
199
+ }
200
+ }
201
+
202
+ module.exports = BT;
@@ -0,0 +1,39 @@
1
+ const Timer = require('../util/timer');
2
+
3
+ class Clock {
4
+ constructor (runtime) {
5
+ this._projectTimer = new Timer({now: () => runtime.currentMSecs});
6
+ this._projectTimer.start();
7
+ this._pausedTime = null;
8
+ this._paused = false;
9
+ /**
10
+ * Reference to the owning Runtime.
11
+ * @type{!Runtime}
12
+ */
13
+ this.runtime = runtime;
14
+ }
15
+
16
+ projectTimer () {
17
+ if (this._paused) {
18
+ return this._pausedTime / 1000;
19
+ }
20
+ return this._projectTimer.timeElapsed() / 1000;
21
+ }
22
+
23
+ pause () {
24
+ this._paused = true;
25
+ this._pausedTime = this._projectTimer.timeElapsed();
26
+ }
27
+
28
+ resume () {
29
+ this._paused = false;
30
+ const dt = this._projectTimer.timeElapsed() - this._pausedTime;
31
+ this._projectTimer.startTime += dt;
32
+ }
33
+
34
+ resetProjectTimer () {
35
+ this._projectTimer.start();
36
+ }
37
+ }
38
+
39
+ module.exports = Clock;
@@ -0,0 +1,169 @@
1
+ const Variable = require('../engine/variable');
2
+ const log = require('../util/log');
3
+
4
+ class Cloud {
5
+ /**
6
+ * @typedef updateVariable
7
+ * @param {string} name The name of the cloud variable to update on the server
8
+ * @param {(string | number)} value The value to update the cloud variable with.
9
+ */
10
+
11
+ /**
12
+ * A cloud data provider, responsible for managing the connection to the
13
+ * cloud data server and for posting data about cloud data activity to
14
+ * this IO device.
15
+ * @typedef {object} CloudProvider
16
+ * @property {updateVariable} updateVariable A function which sends a cloud variable
17
+ * update to the cloud data server.
18
+ * @property {Function} requestCloseConnection A function which closes
19
+ * the connection to the cloud data server.
20
+ */
21
+
22
+ /**
23
+ * Part of a cloud io data post indicating a cloud variable update.
24
+ * @typedef {object} VarUpdateData
25
+ * @property {string} name The name of the variable to update
26
+ * @property {(number | string)} value The scalar value to update the variable with
27
+ */
28
+
29
+ /**
30
+ * A cloud io data post message.
31
+ * @typedef {object} CloudIOData
32
+ * @property {VarUpdateData} varUpdate A {@link VarUpdateData} message indicating
33
+ * a cloud variable update
34
+ */
35
+
36
+ /**
37
+ * Cloud IO Device responsible for sending and receiving messages from
38
+ * cloud provider (mananging the cloud server connection) and interacting
39
+ * with cloud variables in the current project.
40
+ * @param {Runtime} runtime The runtime context for this cloud io device.
41
+ */
42
+ constructor (runtime) {
43
+ /**
44
+ * Reference to the cloud data provider, responsible for mananging
45
+ * the web socket connection to the cloud data server.
46
+ * @type {?CloudProvider}
47
+ */
48
+ this.provider = null;
49
+
50
+ /**
51
+ * Reference to the runtime that owns this cloud io device.
52
+ * @type {!Runtime}
53
+ */
54
+ this.runtime = runtime;
55
+
56
+ /**
57
+ * Reference to the stage target which owns the cloud variables
58
+ * in the project.
59
+ * @type {?Target}
60
+ */
61
+ this.stage = null;
62
+ }
63
+
64
+ /**
65
+ * Set a reference to the cloud data provider.
66
+ * @param {CloudProvider} provider The cloud data provider
67
+ */
68
+ setProvider (provider) {
69
+ this.provider = provider;
70
+ }
71
+
72
+ /**
73
+ * Set a reference to the stage target which owns the
74
+ * cloud variables in the project.
75
+ * @param {Target} stage The stage target
76
+ */
77
+ setStage (stage) {
78
+ this.stage = stage;
79
+ }
80
+
81
+ /**
82
+ * Handle incoming data to this io device.
83
+ * @param {CloudIOData} data The {@link CloudIOData} object to process
84
+ */
85
+ postData (data) {
86
+ if (data.varUpdate) {
87
+ this.updateCloudVariable(data.varUpdate);
88
+ }
89
+ }
90
+
91
+ requestCreateVariable (variable) {
92
+ if (this.runtime.canAddCloudVariable()) {
93
+ if (this.provider) {
94
+ this.provider.createVariable(variable.name, variable.value);
95
+ // We'll set the cloud flag and update the
96
+ // cloud variable limit when we actually
97
+ // get a confirmation from the cloud data server
98
+ }
99
+ } // TODO else track creation for later
100
+ }
101
+
102
+ /**
103
+ * Request the cloud data provider to update the given variable with
104
+ * the given value. Does nothing if this io device does not have a provider set.
105
+ * @param {string} name The name of the variable to update
106
+ * @param {string | number} value The value to update the variable with
107
+ */
108
+ requestUpdateVariable (name, value) {
109
+ if (this.provider) {
110
+ this.provider.updateVariable(name, value);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Request the cloud data provider to rename the variable with the given name
116
+ * to the given new name. Does nothing if this io device does not have a provider set.
117
+ * @param {string} oldName The name of the variable to rename
118
+ * @param {string | number} newName The new name for the variable
119
+ */
120
+ requestRenameVariable (oldName, newName) {
121
+ if (this.provider) {
122
+ this.provider.renameVariable(oldName, newName);
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Request the cloud data provider to delete the variable with the given name
128
+ * Does nothing if this io device does not have a provider set.
129
+ * @param {string} name The name of the variable to delete
130
+ */
131
+ requestDeleteVariable (name) {
132
+ if (this.provider) {
133
+ this.provider.deleteVariable(name);
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Update a cloud variable in the runtime based on the message received
139
+ * from the cloud provider.
140
+ * @param {VarData} varUpdate A {@link VarData} object describing
141
+ * a cloud variable update received from the cloud data provider.
142
+ */
143
+ updateCloudVariable (varUpdate) {
144
+ const varName = varUpdate.name;
145
+
146
+ const variable = this.stage.lookupVariableByNameAndType(varName, Variable.SCALAR_TYPE);
147
+ if (!variable || !variable.isCloud) {
148
+ log.warn(`Received an update for a cloud variable that does not exist: ${varName}`);
149
+ return;
150
+ }
151
+
152
+ variable.value = varUpdate.value;
153
+ }
154
+
155
+ /**
156
+ * Request the cloud data provider to close the web socket connection and
157
+ * clear this io device of references to the cloud data provider and the
158
+ * stage.
159
+ */
160
+ clear () {
161
+ if (!this.provider) return;
162
+
163
+ this.provider.requestCloseConnection();
164
+ this.provider = null;
165
+ this.stage = null;
166
+ }
167
+ }
168
+
169
+ module.exports = Cloud;