diff --git a/clients/chrome/README.md b/clients/chrome/README.md
new file mode 100644
index 00000000..b3261250
--- /dev/null
+++ b/clients/chrome/README.md
@@ -0,0 +1,26 @@
+# microbit-chrome
+Prototype chrome addon that exposes the micro:bit's serial output to webpages.
+* watch the [demo video](https://vimeo.com/146207766)
+
+# Installation
+See [developer.chrome.com](https://developer.chrome.com/extensions/getstarted#unpacked)
+for instructions on how to install the local version into your chrome browser.
+
+# Requirements
+* Chrome 48 or later.
+
+# Sample page
+The `demo.html` webpage goes along with the
+https://github.com/Microsoft/microbit-touchdevelop/blob/master/examples/tcs34725.cpp
+program. Run `http-server` from this directory, then visit
+http://localhost:8080/demo.html
+(keep in mind that pages served from `file://` cannot open ports).
+
+# Building
+
+Open a command prompt and run the following commands.
+
+````
+npm install
+typings update
+````
\ No newline at end of file
diff --git a/clients/chrome/background.js b/clients/chrome/background.js
new file mode 100644
index 00000000..777b73ae
--- /dev/null
+++ b/clients/chrome/background.js
@@ -0,0 +1,68 @@
+///
+var connections = [];
+// A list of "ports", i.e. connected clients (such as web pages). Multiple web
+// pages can connect to our service: they all receive the same data.
+var ports = [];
+function byPath(path) {
+ return connections.filter(function (x) { return x.path == path; });
+}
+function byId(id) {
+ return connections.filter(function (x) { return x.id == id; });
+}
+function onReceive(data, id) {
+ if (ports.length == 0)
+ return;
+ var view = new DataView(data);
+ var decoder = new TextDecoder("utf-8");
+ var decodedString = decoder.decode(view);
+ ports.forEach(function (port) { return port.postMessage({
+ type: "serial",
+ data: decodedString,
+ id: id
+ }); });
+}
+function findNewDevices() {
+ chrome.serial.getDevices(function (serialPorts) {
+ serialPorts.forEach(function (serialPort) {
+ if (byPath(serialPort.path).length == 0 &&
+ serialPort.displayName == "mbed Serial Port") {
+ chrome.serial.connect(serialPort.path, { bitrate: 115200 }, function (info) {
+ // In case the [connect] operation takes more than five seconds...
+ if (info && byPath(serialPort.path).length == 0)
+ connections.push({
+ id: info.connectionId,
+ path: serialPort.path
+ });
+ });
+ }
+ });
+ });
+}
+function main() {
+ // Register new clients in the [ports] global variable.
+ chrome.runtime.onConnectExternal.addListener(function (port) {
+ if (/^(micro:bit|touchdevelop|yelm|pxt|codemicrobit|codethemicrobit)$/.test(port.name)) {
+ ports.push(port);
+ port.onDisconnect.addListener(function () {
+ ports = ports.filter(function (x) { return x != port; });
+ });
+ }
+ });
+ // When receiving data for one of the connections that we're tracking, forward
+ // it to all connected clients.
+ chrome.serial.onReceive.addListener(function (info) {
+ if (byId(info.connectionId).length > 0)
+ onReceive(info.data, info.connectionId);
+ });
+ // When it looks like we've been disconnected, drop the corresponding
+ // connection object from the [connections] global variable.
+ chrome.serial.onReceiveError.addListener(function (info) {
+ if (info.error == "system_error" || info.error == "disconnected" || info.error == "device_lost")
+ connections = connections.filter(function (x) { return x.id != info.connectionId; });
+ });
+ // Probe serial connections at regular intervals. In case we find an mbed port
+ // we haven't yet connected to, connect to it.
+ setInterval(findNewDevices, 5000);
+ findNewDevices();
+}
+document.addEventListener("DOMContentLoaded", main);
diff --git a/clients/chrome/background.ts b/clients/chrome/background.ts
new file mode 100644
index 00000000..8925a363
--- /dev/null
+++ b/clients/chrome/background.ts
@@ -0,0 +1,92 @@
+// A list of: {
+// id: number;
+// path: string;
+// } where [id] is the [connectionId] (internal to Chrome) and [path] is the
+// OS' name for the device (e.g. "COM4").
+interface Connection {
+ id: string;
+ path: string;
+}
+let connections: Connection[] = [];
+
+// A list of "ports", i.e. connected clients (such as web pages). Multiple web
+// pages can connect to our service: they all receive the same data.
+let ports = [];
+
+interface Message {
+ type: string;
+ data: string;
+ id: string;
+}
+
+function byPath(path: string): Connection[] {
+ return connections.filter((x) => x.path == path);
+}
+
+function byId(id: string): Connection[] {
+ return connections.filter((x) => x.id == id);
+}
+
+function onReceive(data, id: string) {
+ if (ports.length == 0) return;
+
+ let view = new DataView(data);
+ let decoder = new TextDecoder("utf-8");
+ let decodedString = decoder.decode(view);
+ ports.forEach(port => port.postMessage({
+ type: "serial",
+ data: decodedString,
+ id: id,
+ }));
+}
+
+function findNewDevices() {
+ chrome.serial.getDevices(function (serialPorts) {
+ serialPorts.forEach(function (serialPort) {
+ if (byPath(serialPort.path).length == 0 &&
+ serialPort.displayName == "mbed Serial Port") {
+ chrome.serial.connect(serialPort.path, { bitrate: 115200 }, function (info) {
+ // In case the [connect] operation takes more than five seconds...
+ if (info && byPath(serialPort.path).length == 0)
+ connections.push({
+ id: info.connectionId,
+ path: serialPort.path
+ });
+ });
+ }
+ });
+ });
+}
+
+function main() {
+ // Register new clients in the [ports] global variable.
+ chrome.runtime.onConnectExternal.addListener(function (port) {
+ if (/^(micro:bit|touchdevelop|yelm|pxt|codemicrobit|codethemicrobit)$/.test(port.name)) {
+ ports.push(port);
+ port.onDisconnect.addListener(function () {
+ ports = ports.filter(function (x) { return x != port });
+ });
+ }
+ });
+
+ // When receiving data for one of the connections that we're tracking, forward
+ // it to all connected clients.
+ chrome.serial.onReceive.addListener(function (info) {
+ if (byId(info.connectionId).length > 0)
+ onReceive(info.data, info.connectionId);
+ });
+
+ // When it looks like we've been disconnected, drop the corresponding
+ // connection object from the [connections] global variable.
+ chrome.serial.onReceiveError.addListener(function (info) {
+ if (info.error == "system_error" || info.error == "disconnected" || info.error == "device_lost")
+ connections = connections.filter((x) => x.id != info.connectionId);
+ });
+
+ // Probe serial connections at regular intervals. In case we find an mbed port
+ // we haven't yet connected to, connect to it.
+ setInterval(findNewDevices, 5000);
+ findNewDevices();
+}
+
+document.addEventListener("DOMContentLoaded", main);
diff --git a/clients/chrome/logo128.png b/clients/chrome/logo128.png
new file mode 100644
index 00000000..eae35f4c
Binary files /dev/null and b/clients/chrome/logo128.png differ
diff --git a/clients/chrome/logo48.png b/clients/chrome/logo48.png
new file mode 100644
index 00000000..0af483f8
Binary files /dev/null and b/clients/chrome/logo48.png differ
diff --git a/clients/chrome/manifest.json b/clients/chrome/manifest.json
new file mode 100644
index 00000000..ae3adecb
--- /dev/null
+++ b/clients/chrome/manifest.json
@@ -0,0 +1,28 @@
+{
+ "app": {
+ "background": {
+ "scripts": [ "background.js" ]
+ }
+ },
+
+ "manifest_version": 2,
+ "name": "code the micro:bit",
+ "version": "0.1.0",
+ "author": "Microsoft Corporation",
+ "short_name": "code the micro:bit",
+
+ "description": "This extension reads the serial output from connected BBC micro:bit and sends it to https://codethemicrobit.com.",
+ "icons": {
+ "48": "logo48.png",
+ "128": "logo128.png"
+ },
+
+ "permissions": [
+ "serial",
+ "usb"
+ ],
+
+ "externally_connectable": {
+ "matches": [ "*://localhost/*", "https://*.pxt.io/*", "https://codethemicrobit.com/*" ]
+ }
+}
diff --git a/clients/chrome/screenshot.png b/clients/chrome/screenshot.png
new file mode 100644
index 00000000..c6f4b56b
Binary files /dev/null and b/clients/chrome/screenshot.png differ
diff --git a/clients/chrome/tsconfig.json b/clients/chrome/tsconfig.json
new file mode 100644
index 00000000..ffa98b79
--- /dev/null
+++ b/clients/chrome/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "compiler-options": {
+ "target": "ES5",
+ "module": "amd",
+ "sourceMap": false
+ }
+ }
\ No newline at end of file