diff --git a/cancel_gatt_connect/device_code.js b/cancel_gatt_connect/device_code.js
new file mode 100644
index 0000000..b5c23ab
--- /dev/null
+++ b/cancel_gatt_connect/device_code.js
@@ -0,0 +1,48 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+function onInit() {
+ // Put into a known state.
+ digitalWrite(LED, 0);
+
+ NRF.setServices({
+ '0b30acec-193e-11eb-adc1-0242ac120002': {
+ '0b30afd0-193e-11eb-adc1-0242ac120002': {
+ value: [17],
+ broadcast: false,
+ readable: true,
+ writable: false,
+ notify: false,
+ description: 'Single read-only characteristic',
+ }
+ }
+ });
+
+
+ NRF.on('disconnect', (reason) => {
+ // Provide feedback that device is no longer connected.
+ digitalWrite(LED, 0);
+ });
+
+ NRF.on('connect', (addr) => {
+ // Provide feedback that device is connected.
+ // TODO: Maybe only do this for some devices when external power is
+ // available. For example, this will turn on the backlight on the
+ // Pixl.js screen, which might not be desirable when powered by the
+ // CR2032 coin cell battery.
+ digitalWrite(LED, 1);
+ });
+}
diff --git a/cancel_gatt_connect/index.html b/cancel_gatt_connect/index.html
new file mode 100644
index 0000000..484015c
--- /dev/null
+++ b/cancel_gatt_connect/index.html
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+ Cancel GATT connect
+
+
+
+
+
+
+
+
Cancel GATT connect
+
This is a simple test to check if gatt.connect() can be cancelled by gatt.disconnect().
+
+
+
Step 1
+
+
+
+ Press the button to load the code to the Espruino IDE.
+ From there, flash any Bluetooth capable Espruino device.
+
+
+ View source.
+
+
+
+
+
+
+
+
Step 2
+
+
+
+ Press the button to start pairing.
+
+
+
+
+
+
+
+
Step 3
+
+
+
+ Unplug the Espruino Pixl.js from power source and click the button.
+
+
+
+
+
+
+
+
Status:
+
+
+
+
+ Web Bluetooth is not supported by this browser.
+
+
+ Bluetooth requires a secure context (HTTPS). For development purposes it
+ does support HTTP, but only the on the loopback adapter (i.e. "localhost").
+
+
+ Web Bluetooth is supported by this browser but this device does not have
+ Bluetooth capabilities.
+
+
+
+
diff --git a/cancel_gatt_connect/web_app.js b/cancel_gatt_connect/web_app.js
new file mode 100644
index 0000000..a04de0f
--- /dev/null
+++ b/cancel_gatt_connect/web_app.js
@@ -0,0 +1,100 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Espruino devices publish a UART service by default.
+const nordicUARTService = '6e400001-b5a3-f393-e0a9-e50e24dcca9e';
+
+let device = undefined;
+
+/**
+ * Load the device code to the Espruino IDE.
+ */
+function loadEspruinoDeviceCode() {
+ fetch('device_code.js').then(response => response.text()).then(data => {
+ let url = 'http://www.espruino.com/webide?code=' + encodeURIComponent(data);
+ window.open(url, '_window');
+ });
+}
+
+async function startPairing() {
+ clearStatus();
+ logInfo('Starting test');
+
+ $('btn_load_code').disabled = true;
+ $('btn_start_pairing').disabled = true;
+ $('btn_connect_then_cancel').disabled = true;
+
+ try {
+ const options = {
+ filters: [
+ { services: [nordicUARTService] }
+ ],
+ };
+
+ device = await navigator.bluetooth.requestDevice(options);
+ logInfo(`Paired to Bluetooh device with name: ${device.name}`);
+ } catch (error) {
+ logError(`Unexpected failure: ${error}`);
+ }
+
+ $('btn_load_code').disabled = false;
+ $('btn_start_pairing').disabled = false;
+ $('btn_connect_then_cancel').disabled = false;
+}
+
+async function connectThenCancel() {
+ $('btn_load_code').disabled = true;
+ $('btn_start_pairing').disabled = true;
+ $('btn_connect_then_cancel').disabled = true;
+
+ logInfo('Try to connect then cancel');
+ try {
+ setTimeout(() => {
+ device.gatt.disconnect();
+ }, 100);
+ await device.gatt.connect();
+ logError('connect() promise was not rejected');
+ } catch (e) {
+ if (e.name !== 'AbortError') {
+ logError(`connect() promise was rejected with unexpected error: ${e}`);
+ }
+ logInfo('connect() promise was rejected with Abort error');
+ }
+
+ $('btn_load_code').disabled = false;
+ $('btn_start_pairing').disabled = false;
+ $('btn_connect_then_cancel').disabled = false;
+ testDone();
+}
+
+async function init() {
+ if (!isBluetoothSupported()) {
+ console.log('Bluetooth not supported.');
+ $('bluetooth_available').style.display = 'none';
+ if (window.isSecureContext) {
+ $('bluetooth_none').style.visibility = 'visible';
+ } else {
+ $('bluetooth_insecure').style.visibility = 'visible';
+ }
+ return;
+ }
+
+ const available = await navigator.bluetooth.getAvailability();
+ if (!available) {
+ $('bluetooth_available').style.display = 'none';
+ $('bluetooth_unavailable').style.visibility = 'visible';
+ }
+}
diff --git a/index.html b/index.html
index 78292ab..d124545 100644
--- a/index.html
+++ b/index.html
@@ -43,6 +43,7 @@