Socket
Socket
Sign inDemoInstall

improv-wifi-sdk

Package Overview
Dependencies
41
Maintainers
2
Versions
18
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.2.2 to 1.3.0

dist/ble.d.ts

5

dist/const.d.ts

@@ -0,1 +1,6 @@

export interface Logger {
log(msg: string, ...args: any[]): void;
error(msg: string, ...args: any[]): void;
debug(msg: string, ...args: any[]): void;
}
export declare const IMPROV_BLE_SERVICE = "00467768-6228-2272-4663-277478268000";

@@ -2,0 +7,0 @@ export declare const IMPROV_BLE_CURRENT_STATE_CHARACTERISTIC = "00467768-6228-2272-4663-277478268001";

30

dist/provision-dialog.d.ts

@@ -1,10 +0,10 @@

/// <reference types="web-bluetooth" />
import { LitElement, PropertyValues, TemplateResult } from "lit";
import "@material/mwc-dialog";
import "@material/mwc-textfield";
import "@material/mwc-button";
import "@material/mwc-circular-progress";
import "./components/ib-dialog";
import "./components/ib-textfield";
import "./components/ib-button";
import "./components/ib-circular-progress";
import { ImprovState } from "./const";
import { ImprovBluetoothLE } from "./ble";
declare class ProvisionDialog extends LitElement {
device: BluetoothDevice;
client: ImprovBluetoothLE;
stateUpdateCallback: (state: ImprovState) => void;

@@ -14,11 +14,5 @@ private _state;

private _improvErrorState;
private _improvRPCResult?;
private _improvCapabilities;
private _busy;
private _error?;
private _currentStateChar?;
private _errorStateChar?;
private _rpcCommandChar?;
private _rpcResultChar?;
private _rpcFeedback?;
private _inputSSID;

@@ -32,12 +26,8 @@ private _inputPassword;

protected firstUpdated(changedProps: PropertyValues): void;
private _connect;
private _provision;
private _identify;
protected updated(changedProps: PropertyValues): void;
private _connect;
private _handleImprovCurrentStateChange;
private _handleImprovErrorStateChange;
private _handleImprovRPCResultChange;
private _rpcIdentify;
private _rpcWriteSettings;
private _sendRPC;
private _handleClose;
static styles: import("lit").CSSResultGroup;
static styles: import("lit").CSSResult;
}

@@ -44,0 +34,0 @@ declare global {

@@ -9,11 +9,10 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {

import { customElement, query, state } from "lit/decorators.js";
import "@material/mwc-dialog";
import "@material/mwc-textfield";
import "@material/mwc-button";
import "@material/mwc-circular-progress";
import { hasIdentifyCapability, ImprovCurrentState, IMPROV_BLE_CURRENT_STATE_CHARACTERISTIC, IMPROV_BLE_ERROR_STATE_CHARACTERISTIC, IMPROV_BLE_RPC_COMMAND_CHARACTERISTIC, IMPROV_BLE_RPC_RESULT_CHARACTERISTIC, IMPROV_BLE_SERVICE, IMPROV_BLE_CAPABILITIES_CHARACTERISTIC, } from "./const";
import "./components/ib-dialog";
import "./components/ib-textfield";
import "./components/ib-button";
import "./components/ib-circular-progress";
import { hasIdentifyCapability, ImprovCurrentState, } from "./const";
const ERROR_ICON = "⚠️";
const OK_ICON = "🎉";
const AUTHORIZE_ICON = "👉";
const DEBUG = false;
let ProvisionDialog = class ProvisionDialog extends LitElement {

@@ -62,3 +61,3 @@ constructor() {

return html `
<mwc-dialog
<ib-dialog
open

@@ -69,3 +68,3 @@ .heading=${heading}

.hideActions=${hideActions}
>${content}</mwc-dialog
>${content}</ib-dialog
>

@@ -78,7 +77,7 @@ `;

<div>
<mwc-circular-progress
<ib-circular-progress
active
indeterminate
density="8"
></mwc-circular-progress>
></ib-circular-progress>
</div>

@@ -97,7 +96,7 @@ ${label}

html `
<mwc-button
<ib-button
slot="primaryAction"
dialogAction="ok"
label="Close"
></mwc-button>
></ib-button>
`}

@@ -120,6 +119,6 @@ `;

Enter the Wi-Fi credentials of the network that you want
${this.device.name || "your device"} to connect to.
${this.client.name || "your device"} to connect to.
${hasIdentifyCapability(this._improvCapabilities)
? html `
<button class="link" @click=${this._rpcIdentify}>
<button class="link" @click=${this._identify}>
Identify the device.

@@ -131,27 +130,21 @@ </button>

${error ? html `<p class="error">${error}</p>` : ""}
<mwc-textfield label="Wi-Fi SSID" name="ssid"></mwc-textfield>
<mwc-textfield
<ib-textfield label="Wi-Fi SSID" name="ssid"></ib-textfield>
<ib-textfield
label="Wi-Fi password"
name="password"
type="password"
></mwc-textfield>
<mwc-button
></ib-textfield>
<ib-button
slot="primaryAction"
label="Save"
@click=${this._rpcWriteSettings}
></mwc-button>
<mwc-button
@click=${this._provision}
></ib-button>
<ib-button
slot="secondaryAction"
dialogAction="close"
label="Cancel"
></mwc-button>
></ib-button>
`;
}
_renderImprovProvisioned() {
let redirectUrl;
if (this._improvRPCResult &&
this._improvRPCResult.command === 1 /* SEND_WIFI_SETTINGS */ &&
this._improvRPCResult.values.length > 0) {
redirectUrl = this._improvRPCResult.values[0];
}
return html `

@@ -162,13 +155,13 @@ <div class="center">

</div>
${redirectUrl === undefined
${this.client.nextUrl === undefined
? html `
<mwc-button
<ib-button
slot="primaryAction"
dialogAction="ok"
label="Close"
></mwc-button>
></ib-button>
`
: html `
<a
href=${redirectUrl}
href=${this.client.nextUrl}
slot="primaryAction"

@@ -178,3 +171,3 @@ class="has-button"

>
<mwc-button label="Next"></mwc-button>
<ib-button label="Next"></ib-button>
</a>

@@ -186,3 +179,16 @@ `}

super.firstUpdated(changedProps);
this.device.addEventListener("gattserverdisconnected", () => {
this.client.addEventListener("state-changed", () => {
this._state = "IMPROV-STATE";
this._busy = false;
this._improvCurrentState = this.client.currentState;
});
this.client.addEventListener("error-changed", () => {
this._improvErrorState = this.client.errorState;
// Sending an RPC command sets error to no error.
// If we get a real error it means the RPC command is done.
if (this._improvErrorState !== 0 /* NO_ERROR */) {
this._busy = false;
}
});
this.client.addEventListener("disconnect", () => {
// If we're provisioned, we expect to be disconnected.

@@ -198,2 +204,30 @@ if (this._state === "IMPROV-STATE" &&

}
async _connect() {
try {
await this.client.initialize();
this._improvCurrentState = this.client.currentState;
this._improvErrorState = this.client.errorState;
this._improvCapabilities = this.client.capabilities;
this._state = "IMPROV-STATE";
}
catch (err) {
this._state = "ERROR";
this._error = err.message;
}
}
async _provision() {
this._busy = true;
try {
await this.client.provision(this._inputSSID.value, this._inputPassword.value);
}
catch (err) {
// Ignore, error state takes care of this.
}
finally {
this._busy = false;
}
}
_identify() {
this.client.identify();
}
updated(changedProps) {

@@ -216,146 +250,4 @@ super.updated(changedProps);

}
async _connect() {
// Do everything in sequence as some OSes do not support parallel GATT commands
// https://github.com/WebBluetoothCG/web-bluetooth/issues/188#issuecomment-255121220
try {
await this.device.gatt.connect();
const service = await this.device.gatt.getPrimaryService(IMPROV_BLE_SERVICE);
this._currentStateChar = await service.getCharacteristic(IMPROV_BLE_CURRENT_STATE_CHARACTERISTIC);
this._errorStateChar = await service.getCharacteristic(IMPROV_BLE_ERROR_STATE_CHARACTERISTIC);
this._rpcCommandChar = await service.getCharacteristic(IMPROV_BLE_RPC_COMMAND_CHARACTERISTIC);
this._rpcResultChar = await service.getCharacteristic(IMPROV_BLE_RPC_RESULT_CHARACTERISTIC);
try {
const capabilitiesChar = await service.getCharacteristic(IMPROV_BLE_CAPABILITIES_CHARACTERISTIC);
const capabilitiesValue = await capabilitiesChar.readValue();
this._improvCapabilities = capabilitiesValue.getUint8(0);
}
catch (err) {
console.warn("Firmware not according to spec, missing capability support.");
}
this._currentStateChar.startNotifications();
this._currentStateChar.addEventListener("characteristicvaluechanged", (ev) => this._handleImprovCurrentStateChange(ev.target.value));
this._errorStateChar.startNotifications();
this._errorStateChar.addEventListener("characteristicvaluechanged", (ev) => this._handleImprovErrorStateChange(ev.target.value));
this._rpcResultChar.startNotifications();
this._rpcResultChar.addEventListener("characteristicvaluechanged", (ev) => this._handleImprovRPCResultChange(ev.target.value));
const curState = await this._currentStateChar.readValue();
const errorState = await this._errorStateChar.readValue();
this._handleImprovCurrentStateChange(curState);
this._handleImprovErrorStateChange(errorState);
this._state = "IMPROV-STATE";
}
catch (err) {
this._state = "ERROR";
this._error = `Unable to establish a connection: ${err}`;
}
}
_handleImprovCurrentStateChange(encodedState) {
const state = encodedState.getUint8(0);
if (DEBUG)
console.log("improv current state", state);
this._improvCurrentState = state;
// If we receive a new state, it means the RPC command is done
this._busy = false;
}
_handleImprovErrorStateChange(encodedState) {
const state = encodedState.getUint8(0);
if (DEBUG)
console.log("improv error state", state);
this._improvErrorState = state;
// Sending an RPC command sets error to no error.
// If we get a real error it means the RPC command is done.
if (state !== 0 /* NO_ERROR */) {
this._busy = false;
if (this._rpcFeedback) {
this._rpcFeedback.reject(state);
this._rpcFeedback = undefined;
}
}
}
_handleImprovRPCResultChange(encodedResult) {
if (DEBUG)
console.log("improv RPC result", encodedResult);
const command = encodedResult.getUint8(0);
const result = {
command,
values: [],
};
const dataLength = encodedResult.getUint8(1);
const baseOffset = 2;
const decoder = new TextDecoder();
for (let start = 0; start < dataLength;) {
const valueLength = encodedResult.getUint8(baseOffset + start);
const valueBytes = new Uint8Array(valueLength);
const valueOffset = baseOffset + start + 1;
for (let i = 0; i < valueLength; i++) {
valueBytes[i] = encodedResult.getUint8(valueOffset + i);
}
result.values.push(decoder.decode(valueBytes));
start += valueLength;
}
this._improvRPCResult = result;
if (this._rpcFeedback) {
this._rpcFeedback.resolve(result);
this._rpcFeedback = undefined;
}
}
_rpcIdentify() {
this._sendRPC(2 /* IDENTIFY */, new Uint8Array(), false);
}
async _rpcWriteSettings() {
const encoder = new TextEncoder();
const ssidEncoded = encoder.encode(this._inputSSID.value);
const pwEncoded = encoder.encode(this._inputPassword.value);
const data = new Uint8Array([
ssidEncoded.length,
...ssidEncoded,
pwEncoded.length,
...pwEncoded,
]);
try {
await this._sendRPC(1 /* SEND_WIFI_SETTINGS */, data, true);
if (DEBUG)
console.log("Provisioned! Disconnecting gatt");
// We're going to set this result manually in case we get RPC result first
// that way it's safe to disconnect.
this._improvCurrentState = ImprovCurrentState.PROVISIONED;
this.device.gatt.disconnect();
}
catch (err) {
// Do nothing. Error code will handle itself.
}
}
async _sendRPC(command, data,
// If set to true, the promise will return the RPC result.
requiresFeedback) {
if (DEBUG)
console.log("RPC COMMAND", command, data);
// Commands that receive feedback will finish when either
// the state changes or the error code becomes not 0.
if (requiresFeedback) {
if (this._rpcFeedback) {
throw new Error("Only 1 RPC command that requires feedback can be active");
}
this._busy = true;
}
const payload = new Uint8Array([command, data.length, ...data, 0]);
payload[payload.length - 1] = payload.reduce((sum, cur) => sum + cur, 0);
this._improvRPCResult = undefined;
if (requiresFeedback) {
return await new Promise((resolve, reject) => {
this._rpcFeedback = { resolve, reject };
this._rpcCommandChar.writeValueWithoutResponse(payload);
});
}
else {
this._rpcCommandChar.writeValueWithoutResponse(payload);
return undefined;
}
}
async _handleClose() {
if (this.device.gatt.connected) {
if (DEBUG)
console.log("Disconnecting gatt");
this.device.gatt.disconnect();
}
_handleClose() {
this.client.close();
this.parentNode.removeChild(this);

@@ -370,6 +262,6 @@ }

}
mwc-textfield {
ib-textfield {
display: block;
}
mwc-textfield {
ib-textfield {
margin-top: 16px;

@@ -380,3 +272,3 @@ }

}
mwc-circular-progress {
ib-circular-progress {
margin-bottom: 16px;

@@ -417,5 +309,2 @@ }

state()
], ProvisionDialog.prototype, "_improvRPCResult", void 0);
__decorate([
state()
], ProvisionDialog.prototype, "_improvCapabilities", void 0);

@@ -426,6 +315,6 @@ __decorate([

__decorate([
query("mwc-textfield[name=ssid]")
query("ib-textfield[name=ssid]")
], ProvisionDialog.prototype, "_inputSSID", void 0);
__decorate([
query("mwc-textfield[name=password]")
query("ib-textfield[name=password]")
], ProvisionDialog.prototype, "_inputPassword", void 0);

@@ -432,0 +321,0 @@ ProvisionDialog = __decorate([

@@ -0,1 +1,2 @@

import { ImprovBluetoothLE } from "./ble";
import { IMPROV_BLE_SERVICE } from "./const";

@@ -18,3 +19,3 @@ import "./provision-dialog";

const el = document.createElement("improv-wifi-provision-dialog");
el.device = device;
el.client = new ImprovBluetoothLE(device, console);
el.stateUpdateCallback = (state) => {

@@ -21,0 +22,0 @@ fireEvent(button, "state-changed", state);

@@ -1,1 +0,1 @@

class o extends HTMLElement{connectedCallback(){if(this.renderRoot)return;if(this.renderRoot=this.attachShadow({mode:"open"}),!o.isSupported||!o.isAllowed)return this.toggleAttribute("not-supported",!0),void(this.renderRoot.innerHTML=o.isAllowed?"<slot name='unsupported'>Your browser does not support bluetooth provisioning. Use Google Chrome or Microsoft Edge.</slot>":"<slot name='not-allowed'>You can only use Improv on HTTPS sites or localhost.</slot>");this.toggleAttribute("supported",!0),this.addEventListener("mouseover",(()=>{import("./provision-754590a3.js")}));const e=document.createElement("slot");e.name="activate";const t=document.createElement("button");if(t.innerText="Connect device to Wi-Fi",e.append(t),e.addEventListener("click",(async o=>{o.preventDefault();(await import("./provision-754590a3.js")).startProvisioning(this)})),"adoptedStyleSheets"in Document.prototype&&"replaceSync"in CSSStyleSheet.prototype){const e=new CSSStyleSheet;e.replaceSync(o.style),this.renderRoot.adoptedStyleSheets=[e]}else{const e=document.createElement("style");e.innerText=o.style,this.renderRoot.append(e)}this.renderRoot.append(e)}}o.isSupported="bluetooth"in navigator,o.isAllowed=window.isSecureContext,o.style='\n button {\n position: relative;\n cursor: pointer;\n font-size: 14px;\n padding: 8px 28px;\n color: var(--improv-on-primary-color, #fff);\n background-color: var(--improv-primary-color, #03a9f4);\n border: none;\n border-radius: 4px;\n box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.12), 0 1px 5px 0 rgba(0,0,0,.2);\n }\n button::before {\n content: " ";\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n opacity: 0.2;\n border-radius: 4px;\n }\n button:hover {\n box-shadow: 0 4px 8px 0 rgba(0,0,0,.14), 0 1px 7px 0 rgba(0,0,0,.12), 0 3px 1px -1px rgba(0,0,0,.2);\n }\n button:hover::before {\n background-color: rgba(255,255,255,.8);\n }\n button:focus {\n outline: none;\n }\n button:focus::before {\n background-color: white;\n }\n button:active::before {\n background-color: grey;\n }\n',customElements.define("improv-wifi-launch-button",o);
class o extends HTMLElement{connectedCallback(){if(this.renderRoot)return;if(this.renderRoot=this.attachShadow({mode:"open"}),!o.isSupported||!o.isAllowed)return this.toggleAttribute("not-supported",!0),void(this.renderRoot.innerHTML=o.isAllowed?"<slot name='unsupported'>Your browser does not support bluetooth provisioning. Use Google Chrome or Microsoft Edge.</slot>":"<slot name='not-allowed'>You can only use Improv on HTTPS sites or localhost.</slot>");this.toggleAttribute("supported",!0),this.addEventListener("mouseover",(()=>{import("./provision-741b3b55.js")}));const e=document.createElement("slot");e.name="activate";const t=document.createElement("button");if(t.innerText="Connect device to Wi-Fi",e.append(t),e.addEventListener("click",(async o=>{o.preventDefault();(await import("./provision-741b3b55.js")).startProvisioning(this)})),"adoptedStyleSheets"in Document.prototype&&"replaceSync"in CSSStyleSheet.prototype){const e=new CSSStyleSheet;e.replaceSync(o.style),this.renderRoot.adoptedStyleSheets=[e]}else{const e=document.createElement("style");e.innerText=o.style,this.renderRoot.append(e)}this.renderRoot.append(e)}}o.isSupported="bluetooth"in navigator,o.isAllowed=window.isSecureContext,o.style='\n button {\n position: relative;\n cursor: pointer;\n font-size: 14px;\n padding: 8px 28px;\n color: var(--improv-on-primary-color, #fff);\n background-color: var(--improv-primary-color, #03a9f4);\n border: none;\n border-radius: 4px;\n box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.12), 0 1px 5px 0 rgba(0,0,0,.2);\n }\n button::before {\n content: " ";\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n right: 0;\n opacity: 0.2;\n border-radius: 4px;\n }\n button:hover {\n box-shadow: 0 4px 8px 0 rgba(0,0,0,.14), 0 1px 7px 0 rgba(0,0,0,.12), 0 3px 1px -1px rgba(0,0,0,.2);\n }\n button:hover::before {\n background-color: rgba(255,255,255,.8);\n }\n button:focus {\n outline: none;\n }\n button:focus::before {\n background-color: white;\n }\n button:active::before {\n background-color: grey;\n }\n',customElements.define("improv-wifi-launch-button",o);
{
"name": "improv-wifi-sdk",
"version": "1.2.2",
"version": "1.3.0",
"description": "Improv Wi-Fi SDK for the browser",

@@ -13,3 +13,3 @@ "main": "dist/launch-button.js",

"devDependencies": {
"@rollup/plugin-node-resolve": "^13.0.0",
"@rollup/plugin-node-resolve": "^13.0.6",
"@rollup/plugin-typescript": "^8.2.1",

@@ -24,9 +24,9 @@ "@types/web-bluetooth": "^0.0.11",

"dependencies": {
"@material/mwc-button": "^0.22.1",
"@material/mwc-circular-progress": "^0.22.1",
"@material/mwc-dialog": "^0.22.1",
"@material/mwc-textfield": "^0.22.1",
"lit": "^2.0.0-rc.3",
"@material/mwc-button": "^0.25.3",
"@material/mwc-circular-progress": "^0.25.3",
"@material/mwc-dialog": "^0.25.3",
"@material/mwc-textfield": "^0.25.3",
"lit": "^2.0.0",
"tslib": "^2.3.1"
}
}

@@ -0,1 +1,7 @@

export interface Logger {
log(msg: string, ...args: any[]): void;
error(msg: string, ...args: any[]): void;
debug(msg: string, ...args: any[]): void;
}
export const IMPROV_BLE_SERVICE = "00467768-6228-2272-4663-277478268000";

@@ -2,0 +8,0 @@ export const IMPROV_BLE_CURRENT_STATE_CHARACTERISTIC =

import { LitElement, html, PropertyValues, css, TemplateResult } from "lit";
import { customElement, query, state } from "lit/decorators.js";
import "@material/mwc-dialog";
import "@material/mwc-textfield";
import "@material/mwc-button";
import "@material/mwc-circular-progress";
import type { TextField } from "@material/mwc-textfield";
import "./components/ib-dialog";
import "./components/ib-textfield";
import "./components/ib-button";
import "./components/ib-circular-progress";
import type { IbTextfield } from "./components/ib-textfield";
import {

@@ -12,13 +12,6 @@ hasIdentifyCapability,

ImprovErrorState,
ImprovRPCCommand,
IMPROV_BLE_CURRENT_STATE_CHARACTERISTIC,
IMPROV_BLE_ERROR_STATE_CHARACTERISTIC,
IMPROV_BLE_RPC_COMMAND_CHARACTERISTIC,
IMPROV_BLE_RPC_RESULT_CHARACTERISTIC,
IMPROV_BLE_SERVICE,
ImprovRPCResult,
State,
ImprovState,
IMPROV_BLE_CAPABILITIES_CHARACTERISTIC,
} from "./const";
import { ImprovBluetoothLE } from "./ble";

@@ -28,7 +21,6 @@ const ERROR_ICON = "⚠️";

const AUTHORIZE_ICON = "👉";
const DEBUG = false;
@customElement("improv-wifi-provision-dialog")
class ProvisionDialog extends LitElement {
public device!: BluetoothDevice;
public client!: ImprovBluetoothLE;

@@ -41,3 +33,2 @@ public stateUpdateCallback!: (state: ImprovState) => void;

@state() private _improvErrorState = ImprovErrorState.NO_ERROR;
@state() private _improvRPCResult?: ImprovRPCResult;
@state() private _improvCapabilities = 0;

@@ -49,14 +40,5 @@

private _currentStateChar?: BluetoothRemoteGATTCharacteristic;
private _errorStateChar?: BluetoothRemoteGATTCharacteristic;
private _rpcCommandChar?: BluetoothRemoteGATTCharacteristic;
private _rpcResultChar?: BluetoothRemoteGATTCharacteristic;
private _rpcFeedback?: {
resolve: (value: ImprovRPCResult) => void;
reject: (err: ImprovErrorState) => void;
};
@query("ib-textfield[name=ssid]") private _inputSSID!: IbTextfield;
@query("ib-textfield[name=password]") private _inputPassword!: IbTextfield;
@query("mwc-textfield[name=ssid]") private _inputSSID!: TextField;
@query("mwc-textfield[name=password]") private _inputPassword!: TextField;
protected render() {

@@ -106,3 +88,3 @@ let heading: string = "";

return html`
<mwc-dialog
<ib-dialog
open

@@ -113,3 +95,3 @@ .heading=${heading}

.hideActions=${hideActions}
>${content}</mwc-dialog
>${content}</ib-dialog
>

@@ -123,7 +105,7 @@ `;

<div>
<mwc-circular-progress
<ib-circular-progress
active
indeterminate
density="8"
></mwc-circular-progress>
></ib-circular-progress>
</div>

@@ -143,7 +125,7 @@ ${label}

html`
<mwc-button
<ib-button
slot="primaryAction"
dialogAction="ok"
label="Close"
></mwc-button>
></ib-button>
`}

@@ -171,6 +153,6 @@ `;

Enter the Wi-Fi credentials of the network that you want
${this.device.name || "your device"} to connect to.
${this.client.name || "your device"} to connect to.
${hasIdentifyCapability(this._improvCapabilities)
? html`
<button class="link" @click=${this._rpcIdentify}>
<button class="link" @click=${this._identify}>
Identify the device.

@@ -182,18 +164,18 @@ </button>

${error ? html`<p class="error">${error}</p>` : ""}
<mwc-textfield label="Wi-Fi SSID" name="ssid"></mwc-textfield>
<mwc-textfield
<ib-textfield label="Wi-Fi SSID" name="ssid"></ib-textfield>
<ib-textfield
label="Wi-Fi password"
name="password"
type="password"
></mwc-textfield>
<mwc-button
></ib-textfield>
<ib-button
slot="primaryAction"
label="Save"
@click=${this._rpcWriteSettings}
></mwc-button>
<mwc-button
@click=${this._provision}
></ib-button>
<ib-button
slot="secondaryAction"
dialogAction="close"
label="Cancel"
></mwc-button>
></ib-button>
`;

@@ -203,12 +185,2 @@ }

private _renderImprovProvisioned() {
let redirectUrl: string | undefined;
if (
this._improvRPCResult &&
this._improvRPCResult.command === ImprovRPCCommand.SEND_WIFI_SETTINGS &&
this._improvRPCResult.values.length > 0
) {
redirectUrl = this._improvRPCResult.values[0];
}
return html`

@@ -219,13 +191,13 @@ <div class="center">

</div>
${redirectUrl === undefined
${this.client.nextUrl === undefined
? html`
<mwc-button
<ib-button
slot="primaryAction"
dialogAction="ok"
label="Close"
></mwc-button>
></ib-button>
`
: html`
<a
href=${redirectUrl}
href=${this.client.nextUrl}
slot="primaryAction"

@@ -235,3 +207,3 @@ class="has-button"

>
<mwc-button label="Next"></mwc-button>
<ib-button label="Next"></ib-button>
</a>

@@ -244,3 +216,17 @@ `}

super.firstUpdated(changedProps);
this.device.addEventListener("gattserverdisconnected", () => {
this.client.addEventListener("state-changed", () => {
this._state = "IMPROV-STATE";
this._busy = false;
this._improvCurrentState = this.client.currentState;
});
this.client.addEventListener("error-changed", () => {
this._improvErrorState = this.client.errorState;
// Sending an RPC command sets error to no error.
// If we get a real error it means the RPC command is done.
if (this._improvErrorState !== ImprovErrorState.NO_ERROR) {
this._busy = false;
}
});
this.client.addEventListener("disconnect", () => {
// If we're provisioned, we expect to be disconnected.

@@ -259,2 +245,33 @@ if (

private async _connect() {
try {
await this.client.initialize();
this._improvCurrentState = this.client.currentState;
this._improvErrorState = this.client.errorState;
this._improvCapabilities = this.client.capabilities;
this._state = "IMPROV-STATE";
} catch (err: any) {
this._state = "ERROR";
this._error = err.message;
}
}
private async _provision() {
this._busy = true;
try {
await this.client.provision(
this._inputSSID.value,
this._inputPassword.value
);
} catch (err) {
// Ignore, error state takes care of this.
} finally {
this._busy = false;
}
}
private _identify() {
this.client.identify();
}
protected updated(changedProps: PropertyValues) {

@@ -287,185 +304,4 @@ super.updated(changedProps);

private async _connect() {
// Do everything in sequence as some OSes do not support parallel GATT commands
// https://github.com/WebBluetoothCG/web-bluetooth/issues/188#issuecomment-255121220
try {
await this.device.gatt!.connect();
const service = await this.device.gatt!.getPrimaryService(
IMPROV_BLE_SERVICE
);
this._currentStateChar = await service.getCharacteristic(
IMPROV_BLE_CURRENT_STATE_CHARACTERISTIC
);
this._errorStateChar = await service.getCharacteristic(
IMPROV_BLE_ERROR_STATE_CHARACTERISTIC
);
this._rpcCommandChar = await service.getCharacteristic(
IMPROV_BLE_RPC_COMMAND_CHARACTERISTIC
);
this._rpcResultChar = await service.getCharacteristic(
IMPROV_BLE_RPC_RESULT_CHARACTERISTIC
);
try {
const capabilitiesChar = await service.getCharacteristic(
IMPROV_BLE_CAPABILITIES_CHARACTERISTIC
);
const capabilitiesValue = await capabilitiesChar.readValue();
this._improvCapabilities = capabilitiesValue.getUint8(0);
} catch (err) {
console.warn(
"Firmware not according to spec, missing capability support."
);
}
this._currentStateChar.startNotifications();
this._currentStateChar.addEventListener(
"characteristicvaluechanged",
(ev: any) => this._handleImprovCurrentStateChange(ev.target.value)
);
this._errorStateChar.startNotifications();
this._errorStateChar.addEventListener(
"characteristicvaluechanged",
(ev: any) => this._handleImprovErrorStateChange(ev.target.value)
);
this._rpcResultChar.startNotifications();
this._rpcResultChar.addEventListener(
"characteristicvaluechanged",
(ev: any) => this._handleImprovRPCResultChange(ev.target.value)
);
const curState = await this._currentStateChar.readValue();
const errorState = await this._errorStateChar.readValue();
this._handleImprovCurrentStateChange(curState);
this._handleImprovErrorStateChange(errorState);
this._state = "IMPROV-STATE";
} catch (err) {
this._state = "ERROR";
this._error = `Unable to establish a connection: ${err}`;
}
}
private _handleImprovCurrentStateChange(encodedState: DataView) {
const state = encodedState.getUint8(0) as ImprovCurrentState;
if (DEBUG) console.log("improv current state", state);
this._improvCurrentState = state;
// If we receive a new state, it means the RPC command is done
this._busy = false;
}
private _handleImprovErrorStateChange(encodedState: DataView) {
const state = encodedState.getUint8(0) as ImprovErrorState;
if (DEBUG) console.log("improv error state", state);
this._improvErrorState = state;
// Sending an RPC command sets error to no error.
// If we get a real error it means the RPC command is done.
if (state !== ImprovErrorState.NO_ERROR) {
this._busy = false;
if (this._rpcFeedback) {
this._rpcFeedback.reject(state);
this._rpcFeedback = undefined;
}
}
}
private _handleImprovRPCResultChange(encodedResult: DataView) {
if (DEBUG) console.log("improv RPC result", encodedResult);
const command = encodedResult.getUint8(0) as ImprovRPCCommand;
const result: ImprovRPCResult = {
command,
values: [],
};
const dataLength = encodedResult.getUint8(1);
const baseOffset = 2;
const decoder = new TextDecoder();
for (let start = 0; start < dataLength; ) {
const valueLength = encodedResult.getUint8(baseOffset + start);
const valueBytes = new Uint8Array(valueLength);
const valueOffset = baseOffset + start + 1;
for (let i = 0; i < valueLength; i++) {
valueBytes[i] = encodedResult.getUint8(valueOffset + i);
}
result.values.push(decoder.decode(valueBytes));
start += valueLength;
}
this._improvRPCResult = result;
if (this._rpcFeedback) {
this._rpcFeedback.resolve(result);
this._rpcFeedback = undefined;
}
}
private _rpcIdentify() {
this._sendRPC(ImprovRPCCommand.IDENTIFY, new Uint8Array(), false);
}
private async _rpcWriteSettings() {
const encoder = new TextEncoder();
const ssidEncoded = encoder.encode(this._inputSSID.value);
const pwEncoded = encoder.encode(this._inputPassword.value);
const data = new Uint8Array([
ssidEncoded.length,
...ssidEncoded,
pwEncoded.length,
...pwEncoded,
]);
try {
await this._sendRPC(ImprovRPCCommand.SEND_WIFI_SETTINGS, data, true);
if (DEBUG) console.log("Provisioned! Disconnecting gatt");
// We're going to set this result manually in case we get RPC result first
// that way it's safe to disconnect.
this._improvCurrentState = ImprovCurrentState.PROVISIONED;
this.device.gatt!.disconnect();
} catch (err) {
// Do nothing. Error code will handle itself.
}
}
private async _sendRPC(
command: ImprovRPCCommand,
data: Uint8Array,
// If set to true, the promise will return the RPC result.
requiresFeedback: boolean
): Promise<ImprovRPCResult | undefined> {
if (DEBUG) console.log("RPC COMMAND", command, data);
// Commands that receive feedback will finish when either
// the state changes or the error code becomes not 0.
if (requiresFeedback) {
if (this._rpcFeedback) {
throw new Error(
"Only 1 RPC command that requires feedback can be active"
);
}
this._busy = true;
}
const payload = new Uint8Array([command, data.length, ...data, 0]);
payload[payload.length - 1] = payload.reduce((sum, cur) => sum + cur, 0);
this._improvRPCResult = undefined;
if (requiresFeedback) {
return await new Promise<ImprovRPCResult>((resolve, reject) => {
this._rpcFeedback = { resolve, reject };
this._rpcCommandChar!.writeValueWithoutResponse(payload);
});
} else {
this._rpcCommandChar!.writeValueWithoutResponse(payload);
return undefined;
}
}
private async _handleClose() {
if (this.device.gatt!.connected) {
if (DEBUG) console.log("Disconnecting gatt");
this.device.gatt!.disconnect();
}
private _handleClose() {
this.client.close();
this.parentNode!.removeChild(this);

@@ -480,6 +316,6 @@ }

}
mwc-textfield {
ib-textfield {
display: block;
}
mwc-textfield {
ib-textfield {
margin-top: 16px;

@@ -490,3 +326,3 @@ }

}
mwc-circular-progress {
ib-circular-progress {
margin-bottom: 16px;

@@ -493,0 +329,0 @@ }

@@ -0,1 +1,2 @@

import { ImprovBluetoothLE } from "./ble";
import { IMPROV_BLE_SERVICE } from "./const";

@@ -21,3 +22,3 @@ import { LaunchButton } from "./launch-button";

const el = document.createElement("improv-wifi-provision-dialog");
el.device = device;
el.client = new ImprovBluetoothLE(device, console);
el.stateUpdateCallback = (state) => {

@@ -24,0 +25,0 @@ fireEvent(button, "state-changed", state);

@@ -15,5 +15,6 @@ {

"strict": true,
"suppressImplicitAnyIndexErrors": true
"suppressImplicitAnyIndexErrors": true,
"skipLibCheck": true
},
"include": ["src/*"]
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc