From 7a94d619a4fe2b30bf3a5535540f140f8d185872 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <7019101+cwillisf@users.noreply.github.com> Date: Tue, 16 May 2023 07:28:34 -0700 Subject: [PATCH] feat: actually update micro:bit, avoid 'firmware' term --- package-lock.json | 489 ++++++++++-------- package.json | 3 + .../connection-modal/connection-modal.jsx | 4 +- .../connection-modal/scanning-step.jsx | 7 +- ...re-step.jsx => update-peripheral-step.jsx} | 73 +-- src/containers/connection-modal.jsx | 42 +- src/containers/scanning-step.jsx | 6 +- src/lib/microbit-update.js | 165 ++++++ webpack.config.js | 9 + 9 files changed, 526 insertions(+), 272 deletions(-) rename src/components/connection-modal/{update-firmware-step.jsx => update-peripheral-step.jsx} (72%) create mode 100644 src/lib/microbit-update.js diff --git a/package-lock.json b/package-lock.json index abae272ad..6db2da361 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { + "@microbit/microbit-universal-hex": "0.2.2", "arraybuffer-loader": "^1.0.6", "autoprefixer": "^9.0.1", "base64-loader": "1.0.0", @@ -20,6 +21,7 @@ "copy-webpack-plugin": "6.4.1", "core-js": "2.5.7", "css-loader": "^1.0.0", + "dapjs": "2.3.0", "es6-object-assign": "1.1.0", "file-loader": "2.0.0", "get-float-time-domain-data": "0.1.0", @@ -42,9 +44,9 @@ "prop-types": "^15.5.10", "query-string": "^5.1.1", "raw-loader": "^0.5.1", - "react": "16.2.0", + "react": "^16.0.0", "react-contextmenu": "2.9.4", - "react-dom": "16.2.1", + "react-dom": "^16.0.0", "react-draggable": "3.0.5", "react-ga": "2.5.3", "react-intl": "2.9.0", @@ -110,6 +112,7 @@ "selenium-webdriver": "3.6.0", "semantic-release": "19.0.5", "uglifyjs-webpack-plugin": "1.3.0", + "url-loader": "4.1.1", "web-audio-test-api": "0.5.2", "webpack": "4.46.0", "webpack-cli": "3.3.12", @@ -4874,12 +4877,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@commitlint/config-validator/node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, "node_modules/@commitlint/config-validator/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -5702,6 +5699,19 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@microbit/microbit-universal-hex": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@microbit/microbit-universal-hex/-/microbit-universal-hex-0.2.2.tgz", + "integrity": "sha512-qyFt8ATgxAyPkNz9Yado4HXEeCctwP/8L1/v2hFLeVUqw/HFqVqV4piJbqRMmyOefMcQ9OyVPhLXjtbKn9063Q==", + "engines": { + "node": ">=8.5", + "npm": ">=6.0", + "yarn": "^1.0" + }, + "peerDependencies": { + "tslib": ">=1.11.1" + } + }, "node_modules/@nicolo-ribaudo/chokidar-2": { "version": "2.1.8-no-fsevents.2", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.2.tgz", @@ -7391,10 +7401,9 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz", - "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==", - "dev": true + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, "node_modules/@types/json5": { "version": "0.0.29", @@ -7417,8 +7426,15 @@ "node_modules/@types/node": { "version": "12.7.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.12.tgz", - "integrity": "sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ==", - "dev": true + "integrity": "sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ==" + }, + "node_modules/@types/node-hid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@types/node-hid/-/node-hid-1.3.1.tgz", + "integrity": "sha512-VPxuGDCoDxOUKrTZPSok7IEmiK4cVLfj8Csu09FtG5uF+eqf1HETERHXQkO02Rk6j6YiiHxp0/DA9R4llvhEzQ==", + "dependencies": { + "@types/node": "*" + } }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -7438,6 +7454,19 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, + "node_modules/@types/usb": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/usb/-/usb-1.5.4.tgz", + "integrity": "sha512-NOUza/8yuswu6RoECQyPHEjA34qpDaeONQ72fm+bCnnN2DJjDePAY+NsmV17H88oIlq4JlJ2mD5Kh5d6R2MwTQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/w3c-web-usb": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.6.tgz", + "integrity": "sha512-cSjhgrr8g4KbPnnijAr/KJDNKa/bBa+ixYkywFRvrhvi9n1WEl7yYbtRyzE6jqNQiSxxJxoAW3STaOQwJHndaw==" + }, "node_modules/@types/yargs": { "version": "13.0.3", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz", @@ -7815,14 +7844,18 @@ } }, "node_modules/ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dependencies": { - "fast-deep-equal": "^2.0.1", + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, "node_modules/ajv-errors": { @@ -7834,9 +7867,9 @@ } }, "node_modules/ajv-keywords": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", - "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "peerDependencies": { "ajv": "^6.9.1" } @@ -8550,37 +8583,6 @@ "webpack": ">=2" } }, - "node_modules/babel-loader/node_modules/@types/json-schema": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", - "dev": true - }, - "node_modules/babel-loader/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/babel-loader/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, "node_modules/babel-loader/node_modules/emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", @@ -8590,12 +8592,6 @@ "node": ">= 4" } }, - "node_modules/babel-loader/node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, "node_modules/babel-loader/node_modules/find-cache-dir": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", @@ -9934,6 +9930,11 @@ "node": ">=6.0" } }, + "node_modules/chrome-trace-event/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/chromedriver": { "version": "112.0.0", "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-112.0.0.tgz", @@ -10259,15 +10260,6 @@ "node": ">= 0.6" } }, - "node_modules/compressible/node_modules/mime-db": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", - "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/compression": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", @@ -10580,34 +10572,6 @@ "webpack": "^4.37.0 || ^5.0.0" } }, - "node_modules/copy-webpack-plugin/node_modules/@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" - }, - "node_modules/copy-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/copy-webpack-plugin/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, "node_modules/copy-webpack-plugin/node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -10671,11 +10635,6 @@ "node": ">= 4" } }, - "node_modules/copy-webpack-plugin/node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, "node_modules/copy-webpack-plugin/node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -11444,6 +11403,19 @@ "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" }, + "node_modules/dapjs": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/dapjs/-/dapjs-2.3.0.tgz", + "integrity": "sha512-quanzq7+2xnqgGqqYgARz9o3iBcZ3Ir5r5mTA7WPsjrp9ilEqqCToSFGTL+8HuGP35dUIL7O+yMBloYHhHgZDA==", + "dependencies": { + "@types/node-hid": "^1.2.0", + "@types/usb": "^1.5.1", + "@types/w3c-web-usb": "^1.0.4" + }, + "engines": { + "node": ">=8.14.0" + } + }, "node_modules/dargs": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", @@ -13897,9 +13869,9 @@ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" }, "node_modules/fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { "version": "3.2.6", @@ -19957,19 +19929,19 @@ } }, "node_modules/mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "mime-db": "1.40.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" @@ -25811,6 +25783,12 @@ "npm": ">=2.0.0" } }, + "node_modules/rxjs/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -29345,9 +29323,10 @@ "dev": true }, "node_modules/tslib": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", - "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "peer": true }, "node_modules/tty-browserify": { "version": "0.0.0", @@ -29917,6 +29896,74 @@ "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "dev": true }, + "node_modules/url-loader": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.27", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "file-loader": "*", + "webpack": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "file-loader": { + "optional": true + } + } + }, + "node_modules/url-loader/node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/url-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/url-loader/node_modules/schema-utils": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", + "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/url-parse": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", @@ -34928,12 +34975,6 @@ "uri-js": "^4.2.2" } }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -35550,6 +35591,12 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@microbit/microbit-universal-hex": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@microbit/microbit-universal-hex/-/microbit-universal-hex-0.2.2.tgz", + "integrity": "sha512-qyFt8ATgxAyPkNz9Yado4HXEeCctwP/8L1/v2hFLeVUqw/HFqVqV4piJbqRMmyOefMcQ9OyVPhLXjtbKn9063Q==", + "requires": {} + }, "@nicolo-ribaudo/chokidar-2": { "version": "2.1.8-no-fsevents.2", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.2.tgz", @@ -36803,10 +36850,9 @@ } }, "@types/json-schema": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz", - "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==", - "dev": true + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" }, "@types/json5": { "version": "0.0.29", @@ -36829,8 +36875,15 @@ "@types/node": { "version": "12.7.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.12.tgz", - "integrity": "sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ==", - "dev": true + "integrity": "sha512-KPYGmfD0/b1eXurQ59fXD1GBzhSQfz6/lKBxkaHX9dKTzjXbK68Zt7yGUxUsCS1jeTy/8aL+d9JEr+S54mpkWQ==" + }, + "@types/node-hid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@types/node-hid/-/node-hid-1.3.1.tgz", + "integrity": "sha512-VPxuGDCoDxOUKrTZPSok7IEmiK4cVLfj8Csu09FtG5uF+eqf1HETERHXQkO02Rk6j6YiiHxp0/DA9R4llvhEzQ==", + "requires": { + "@types/node": "*" + } }, "@types/normalize-package-data": { "version": "2.4.1", @@ -36850,6 +36903,19 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, + "@types/usb": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/usb/-/usb-1.5.4.tgz", + "integrity": "sha512-NOUza/8yuswu6RoECQyPHEjA34qpDaeONQ72fm+bCnnN2DJjDePAY+NsmV17H88oIlq4JlJ2mD5Kh5d6R2MwTQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/w3c-web-usb": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.6.tgz", + "integrity": "sha512-cSjhgrr8g4KbPnnijAr/KJDNKa/bBa+ixYkywFRvrhvi9n1WEl7yYbtRyzE6jqNQiSxxJxoAW3STaOQwJHndaw==" + }, "@types/yargs": { "version": "13.0.3", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz", @@ -37181,11 +37247,11 @@ } }, "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "requires": { - "fast-deep-equal": "^2.0.1", + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" @@ -37198,9 +37264,9 @@ "requires": {} }, "ajv-keywords": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz", - "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "requires": {} }, "amdefine": { @@ -37755,43 +37821,12 @@ "schema-utils": "^2.6.5" }, "dependencies": { - "@types/json-schema": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", - "dev": true - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} - }, "emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "dev": true }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, "find-cache-dir": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", @@ -38932,6 +38967,13 @@ "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", "requires": { "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } } }, "chromedriver": { @@ -39202,14 +39244,6 @@ "dev": true, "requires": { "mime-db": ">= 1.43.0 < 2" - }, - "dependencies": { - "mime-db": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", - "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", - "dev": true - } } }, "compression": { @@ -39465,28 +39499,6 @@ "webpack-sources": "^1.4.3" }, "dependencies": { - "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "requires": {} - }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -39535,11 +39547,6 @@ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -40131,6 +40138,16 @@ "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" }, + "dapjs": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/dapjs/-/dapjs-2.3.0.tgz", + "integrity": "sha512-quanzq7+2xnqgGqqYgARz9o3iBcZ3Ir5r5mTA7WPsjrp9ilEqqCToSFGTL+8HuGP35dUIL7O+yMBloYHhHgZDA==", + "requires": { + "@types/node-hid": "^1.2.0", + "@types/usb": "^1.5.1", + "@types/w3c-web-usb": "^1.0.4" + } + }, "dargs": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", @@ -42101,9 +42118,9 @@ } }, "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { "version": "3.2.6", @@ -46955,16 +46972,16 @@ "dev": true }, "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.40.0" + "mime-db": "1.52.0" } }, "mimic-fn": { @@ -51396,6 +51413,14 @@ "dev": true, "requires": { "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } } }, "safe-buffer": { @@ -54233,9 +54258,10 @@ } }, "tslib": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", - "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==" + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "peer": true }, "tty-browserify": { "version": "0.0.0", @@ -54673,6 +54699,47 @@ "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", "dev": true }, + "url-loader": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.27", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "schema-utils": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.2.tgz", + "integrity": "sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, "url-parse": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", diff --git a/package.json b/package.json index 67d5baf94..38b55c990 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ } }, "dependencies": { + "@microbit/microbit-universal-hex": "0.2.2", "arraybuffer-loader": "^1.0.6", "autoprefixer": "^9.0.1", "base64-loader": "1.0.0", @@ -43,6 +44,7 @@ "copy-webpack-plugin": "6.4.1", "core-js": "2.5.7", "css-loader": "^1.0.0", + "dapjs": "2.3.0", "es6-object-assign": "1.1.0", "file-loader": "2.0.0", "get-float-time-domain-data": "0.1.0", @@ -137,6 +139,7 @@ "selenium-webdriver": "3.6.0", "semantic-release": "19.0.5", "uglifyjs-webpack-plugin": "1.3.0", + "url-loader": "4.1.1", "web-audio-test-api": "0.5.2", "webpack": "4.46.0", "webpack-cli": "3.3.12", diff --git a/src/components/connection-modal/connection-modal.jsx b/src/components/connection-modal/connection-modal.jsx index 97218ce93..8d6cad583 100644 --- a/src/components/connection-modal/connection-modal.jsx +++ b/src/components/connection-modal/connection-modal.jsx @@ -11,7 +11,7 @@ import ConnectingStep from './connecting-step.jsx'; import ConnectedStep from './connected-step.jsx'; import ErrorStep from './error-step.jsx'; import UnavailableStep from './unavailable-step.jsx'; -import UpdateFirmwareStep from './update-firmware-step.jsx'; +import UpdatePeripheralStep from './update-peripheral-step.jsx'; import styles from './connection-modal.css'; @@ -41,7 +41,7 @@ const ConnectionModalComponent = props => ( {props.phase === PHASES.connected && } {props.phase === PHASES.error && } {props.phase === PHASES.unavailable && } - {props.phase === PHASES.updateFirmware && } + {props.phase === PHASES.updateFirmware && } ); diff --git a/src/components/connection-modal/scanning-step.jsx b/src/components/connection-modal/scanning-step.jsx index c3bf5f997..8ed65ce92 100644 --- a/src/components/connection-modal/scanning-step.jsx +++ b/src/components/connection-modal/scanning-step.jsx @@ -52,7 +52,7 @@ const ScanningStep = props => ( id="gui.connection.scanning.noPeripheralsFound" /> { - props.onSendFirmware &&

+ props.onUpdatePeripheral &&

( /> @@ -148,10 +159,10 @@ class UpdateFirmwareStep extends React.Component { } } -UpdateFirmwareStep.propTypes = { +UpdatePeripheralStep.propTypes = { name: PropTypes.string.isRequired, onScanning: PropTypes.func.isRequired, - onSendFirmware: PropTypes.func.isRequired + onSendPeripheralUpdate: PropTypes.func.isRequired }; -export default UpdateFirmwareStep; +export default UpdatePeripheralStep; diff --git a/src/containers/connection-modal.jsx b/src/containers/connection-modal.jsx index 823fc1506..9cff06e7b 100644 --- a/src/containers/connection-modal.jsx +++ b/src/containers/connection-modal.jsx @@ -6,7 +6,9 @@ import VM from 'scratch-vm'; import analytics from '../lib/analytics'; import extensionData from '../lib/libraries/extensions/index.jsx'; import {connect} from 'react-redux'; + import {closeConnectionModal} from '../reducers/modals'; +import {isMicroBitUpdateSupported, selectAndUpdateMicroBit} from '../lib/microbit-update'; class ConnectionModal extends React.Component { constructor (props) { @@ -19,8 +21,8 @@ class ConnectionModal extends React.Component { 'handleDisconnect', 'handleError', 'handleHelp', - 'handleUpdateFirmware', - 'handleSendFirmware' + 'handleSendUpdate', + 'handleUpdatePeripheral' ]); this.state = { extension: extensionData.find(ext => ext.extensionId === props.extensionId), @@ -106,33 +108,33 @@ class ConnectionModal extends React.Component { label: this.props.extensionId }); } - handleUpdateFirmware () { + handleUpdatePeripheral () { this.setState({ phase: PHASES.updateFirmware }); analytics.event({ category: 'extensions', - action: 'update firmware', + action: 'enter peripheral update flow', label: this.props.extensionId }); } - handleSendFirmware (progressCallback) { - // TODO: get this functionality from the extension - // TODO: actually send firmware to the device - let progress = 0; - return new Promise(resolve => { - const interval = setInterval(() => { - progress += 0.01; - if (progress >= 1) { - clearInterval(interval); - resolve({success: true}); - } - progressCallback(progress); - }, 100); + /** + * Handle sending an update to the peripheral. + * @param {function(number): void} [progressCallback] Optional callback for progress updates in the range of [0..1]. + * @returns {Promise} Resolves when the update is complete. + */ + handleSendUpdate (progressCallback) { + analytics.event({ + category: 'extensions', + action: 'send update to peripheral', + label: this.props.extensionId }); + + // TODO: get this functionality from the extension + return selectAndUpdateMicroBit(progressCallback); } render () { - const canSendFirmware = this.props.extensionId === 'microbit'; + const canUpdatePeripheral = (this.props.extensionId === 'microbit') && isMicroBitUpdateSupported(); return ( ); } diff --git a/src/containers/scanning-step.jsx b/src/containers/scanning-step.jsx index b638e3230..419b87c53 100644 --- a/src/containers/scanning-step.jsx +++ b/src/containers/scanning-step.jsx @@ -62,8 +62,7 @@ class ScanningStep extends React.Component { onConnected={this.props.onConnected} onConnecting={this.props.onConnecting} onRefresh={this.handleRefresh} - onSendFirmware={this.props.onSendFirmware} - onUpdateFirmware={this.props.onUpdateFirmware} + onUpdatePeripheral={this.props.onUpdatePeripheral} /> ); } @@ -74,8 +73,7 @@ ScanningStep.propTypes = { extensionId: PropTypes.string.isRequired, onConnected: PropTypes.func.isRequired, onConnecting: PropTypes.func.isRequired, - onSendFirmware: PropTypes.func, - onUpdateFirmware: PropTypes.func, + onUpdatePeripheral: PropTypes.func, vm: PropTypes.instanceOf(VM).isRequired }; diff --git a/src/lib/microbit-update.js b/src/lib/microbit-update.js new file mode 100644 index 000000000..f4230ddb5 --- /dev/null +++ b/src/lib/microbit-update.js @@ -0,0 +1,165 @@ +import { + isUniversalHex, + separateUniversalHex +} from '@microbit/microbit-universal-hex'; +import {WebUSB, DAPLink} from 'dapjs'; +import keyMirror from 'keymirror'; + +import log from './log.js'; + +import hexUrl from '../generated/microbit-hex-url.cjs'; + +/** + * @typedef {import('@microbit/microbit-universal-hex').IndividualHex} IndividualHex + */ + +/** + * The version of a micro:bit. + * @enum {string} + */ +const DeviceVersion = keyMirror({ + V1: null, + V2: null +}); + +const vendorId = 0x0d28; +const productId = 0x0204; + +/** + * Assumes the device is a micro:bit and determines its version. + * @param {USBDevice} device The USB device to check. + * @returns {DeviceVersion} The version of the device. + * @throws {Error} If the device is not a recognized micro:bit. + */ +const getDeviceVersion = device => { + const microBitBoardId = device?.serialNumber?.substring(0, 4) ?? ''; + switch (microBitBoardId) { + case '9900': + case '9901': + return DeviceVersion.V1; + case '9903': + case '9904': + case '9905': + case '9906': + return DeviceVersion.V2; + } + + throw new Error('Could not identify micro:bit board version'); +}; + +/** + * Checks micro:bit board version targetted by the hex file. + * @param {IndividualHex} hex The hex file to check. + * @returns {DeviceVersion} The version of the hex file. + * @throws {Error} If the hex file does not target a recognized micro:bit version. + */ +const getHexVersion = hex => { + switch (hex.boardId) { + case 0x9900: + case 0x9901: + return DeviceVersion.V1; + case 0x9903: + case 0x9904: + case 0x9905: + case 0x9906: + return DeviceVersion.V2; + } + + throw new Error('Could not identify hex version'); +}; + +/** + * Fetches the hex file and returns a map of micro:bit versions to hex file contents. + * @returns {Promise>} A map of micro:bit versions to hex file contents. + * @throws {Error} If the fetch fails or cannot be interpreted as text. + * @throws {Error} If the hex file is not in universal format. + */ +const getHexMap = async () => { + const response = await fetch(hexUrl); + const hex = await response.text(); + + if (!isUniversalHex(hex)) { + throw new Error('Hex file must be in universal format'); + } + + const hexMap = new Map(); + + for (const hexObj of separateUniversalHex(hex)) { + const version = getHexVersion(hexObj); + const binary = new TextEncoder().encode(hex); + hexMap.set(version, binary); + } + + return hexMap; +}; + +/** + * Copy the Scratch-specific hex file to the specified micro:bit. + * @param {USBDevice} device The micro:bit to update. + * @param {function(number): void} [progress] Optional function to call with progress updates in the range of [0..1]. + * @returns {Promise} A Promise that resolves when the update is completed. + * @throws {Error} If anything goes wrong while fetching the hex file or updating the micro:bit. + */ +const updateMicroBit = async (device, progress) => { + log.info(`Connecting to micro:bit`); + const transport = new WebUSB(device); + const target = new DAPLink(transport); + if (progress) { + target.on(DAPLink.EVENT_PROGRESS, progress); + } + log.info(`Checking micro:bit version`); + const version = getDeviceVersion(device); + log.info(`Collecting hex file`); + const hexMap = await getHexMap(); + const hexData = hexMap.get(version); + if (!hexData) { + throw new Error(`Could not find hex file for micro:bit ${version}`); + } + log.info(`Connecting to micro:bit ${version}`); + await target.connect(); + log.info(`Sending hex file...`); + try { + await target.flash(hexData); + } finally { + log.info('Disconnecting'); + if (target.connected) { + await target.disconnect(); + } else { + log.info('Already disconnected'); + } + } +}; + +/** + * Requests a micro:bit from the browser then updates it with the Scratch-specific hex file. + * The browser is expected to prompt the user to select a micro:bit. + * @param {function(number): void} [progress] Optional function to call with progress updates in the range of [0..1]. + * @returns {Promise} A Promise that resolves when the update is completed. + * @throws {Error} If anything goes wrong while fetching the hex file or updating the micro:bit. + */ +const selectAndUpdateMicroBit = async progress => { + log.info('Selecting micro:bit'); + + const device = await navigator.usb.requestDevice({ + filters: [{vendorId, productId}] + }); + + if (!device) { + log.info('No device selected'); + return; + } + + return updateMicroBit(device, progress); +}; + +/** + * Checks if the browser supports updating a micro:bit. + * @returns {boolean} True if the browser appears to support updating a micro:bit. + */ +const isMicroBitUpdateSupported = () => + !!(navigator.usb && navigator.usb.requestDevice); + +export { + isMicroBitUpdateSupported, + selectAndUpdateMicroBit +}; diff --git a/webpack.config.js b/webpack.config.js index 815333b96..5fd34c0b2 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -79,6 +79,15 @@ const base = { } } }] + }, + { + test: /\.hex$/, + use: [{ + loader: 'url-loader', + options: { + limit: 16 * 1024 + } + }] }] }, optimization: {