Skip to content

Commit

Permalink
Fire state changed events (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
bramkragten committed Jun 14, 2021
1 parent 7378381 commit 43b018c
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 15 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,16 @@ It is possible to customize the button and the message. You do this by putting y
</improv-wifi-launch-button>
```

## Events

When the state of provisioning changes, a `state-changed` event is fired.

A `state-changed` event contains the following information:

Field | Description
-- | --
state | The current state (`CONNECTING`, `AUTHORIZATION_REQUIRED`, `AUTHORIZED`, `PROVISIONING`, `PROVISIONED`, `ERROR`, `UNKNOWN`)

## Browser Support

This SDK requires a browser with support for WebBluetooth. Currently this is supported by Google Chrome, Microsoft Edge and other browsers based on the Blink engine.
Expand Down
10 changes: 10 additions & 0 deletions example.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@
<improv-wifi-launch-button>
<span slot="unsupported">⚠️ Browser set-up not supported</span>
</improv-wifi-launch-button>
<p>Events:</p>
<pre id="events"></pre>
<script src="./dist/web/launch-button.js" type="module"></script>
<script>
document.querySelectorAll("improv-wifi-launch-button").forEach((button) =>
button.addEventListener("state-changed", (ev) => {
document.querySelector("pre").innerText +=
JSON.stringify(ev.detail) + "\n";
})
);
</script>
</body>
</html>
17 changes: 16 additions & 1 deletion src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,16 @@ export const IMPROV_BLE_RPC_RESULT_CHARACTERISTIC =
export const IMPROV_BLE_CAPABILITIES_CHARACTERISTIC =
"00467768-6228-2272-4663-277478268005";

export const enum ImprovCurrentState {
export type State = "CONNECTING" | "IMPROV-STATE" | "ERROR";

export interface ImprovState {
state:
| Omit<State, "IMPROV-STATE">
| keyof typeof ImprovCurrentState
| "UNKNOWN";
}

export enum ImprovCurrentState {
AUTHORIZATION_REQUIRED = 0x01,
AUTHORIZED = 0x02,
PROVISIONING = 0x03,
Expand Down Expand Up @@ -39,3 +48,9 @@ export interface ImprovRPCResult {

export const hasIdentifyCapability = (capabilities: number) =>
(capabilities & 1) === 1;

declare global {
interface HTMLElementEventMap {
"state-changed": CustomEvent<ImprovState>;
}
}
2 changes: 1 addition & 1 deletion src/launch-button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class LaunchButton extends HTMLElement {
slot.addEventListener("click", async (ev) => {
ev.preventDefault();
const mod = await import("./provision");
mod.startProvisioning();
mod.startProvisioning(this);
});

if (
Expand Down
41 changes: 29 additions & 12 deletions src/provision-dialog.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LitElement, html, PropertyValues, css } from "lit";
import { LitElement, html, PropertyValues, css, TemplateResult } from "lit";
import { customElement, query, state } from "lit/decorators.js";
import "@material/mwc-dialog";
import "@material/mwc-textfield";
Expand All @@ -16,6 +16,8 @@ import {
IMPROV_BLE_RPC_RESULT_CHARACTERISTIC,
IMPROV_BLE_SERVICE,
ImprovRPCResult,
State,
ImprovState,
} from "./const";

const ERROR_ICON = "⚠️";
Expand All @@ -27,8 +29,9 @@ const DEBUG = false;
class ProvisionDialog extends LitElement {
public device!: BluetoothDevice;

@state() private _state: "connecting" | "improv-state" | "error" =
"connecting";
public stateUpdateCallback!: (state: ImprovState) => void;

@state() private _state: State = "CONNECTING";

@state() private _improvCurrentState?: ImprovCurrentState | undefined;
@state() private _improvErrorState = ImprovErrorState.NO_ERROR;
Expand All @@ -52,14 +55,14 @@ class ProvisionDialog extends LitElement {
@query("mwc-textfield[name=password]") private _inputPassword!: TextField;

protected render() {
let heading;
let content;
let heading: string = "";
let content: TemplateResult;
let hideActions = false;

if (this._state === "connecting") {
if (this._state === "CONNECTING") {
content = this._renderProgress("Connecting");
hideActions = true;
} else if (this._state === "error") {
} else if (this._state === "ERROR") {
content = this._renderMessage(
ERROR_ICON,
`An error occurred. ${this._error}`,
Expand Down Expand Up @@ -227,12 +230,12 @@ class ProvisionDialog extends LitElement {
this.device.addEventListener("gattserverdisconnected", () => {
// If we're provisioned, we expect to be disconnected.
if (
this._state === "improv-state" &&
this._state === "IMPROV-STATE" &&
this._improvCurrentState === ImprovCurrentState.PROVISIONED
) {
return;
}
this._state = "error";
this._state = "ERROR";
this._error = "Device disconnected.";
});
this._connect();
Expand All @@ -241,9 +244,23 @@ class ProvisionDialog extends LitElement {
protected updated(changedProps: PropertyValues) {
super.updated(changedProps);

if (
changedProps.has("_state") ||
(this._state === "IMPROV-STATE" &&
changedProps.has("_improvCurrentState"))
) {
const state =
this._state === "IMPROV-STATE"
? (ImprovCurrentState[
this._improvCurrentState!
] as keyof typeof ImprovCurrentState) || "UNKNOWN"
: this._state;
this.stateUpdateCallback({ state });
}

if (
(changedProps.has("_improvCurrentState") || changedProps.has("_state")) &&
this._state === "improv-state" &&
this._state === "IMPROV-STATE" &&
this._improvCurrentState === ImprovCurrentState.AUTHORIZED
) {
const input = this._inputSSID;
Expand Down Expand Up @@ -309,9 +326,9 @@ class ProvisionDialog extends LitElement {

this._handleImprovCurrentStateChange(curState);
this._handleImprovErrorStateChange(errorState);
this._state = "improv-state";
this._state = "IMPROV-STATE";
} catch (err) {
this._state = "error";
this._state = "ERROR";
this._error = `Unable to establish a connection: ${err}`;
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/provision.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { IMPROV_BLE_SERVICE } from "./const";
import { LaunchButton } from "./launch-button";
import "./provision-dialog";
import { fireEvent } from "./util";

export const startProvisioning = async () => {
export const startProvisioning = async (button: LaunchButton) => {
let device: BluetoothDevice | undefined;
try {
device = await navigator.bluetooth.requestDevice({
Expand All @@ -17,5 +19,8 @@ export const startProvisioning = async () => {

const el = document.createElement("improv-wifi-provision-dialog");
el.device = device;
el.stateUpdateCallback = (state) => {
fireEvent(button, "state-changed", state);
};
document.body.appendChild(el);
};
20 changes: 20 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const fireEvent = <Event extends keyof HTMLElementEventMap>(
eventTarget: EventTarget,
type: Event,
// @ts-ignore
detail?: HTMLElementEventMap[Event]["detail"],
options?: {
bubbles?: boolean;
cancelable?: boolean;
composed?: boolean;
}
): void => {
options = options || {};
const event = new CustomEvent(type, {
bubbles: options.bubbles === undefined ? true : options.bubbles,
cancelable: Boolean(options.cancelable),
composed: options.composed === undefined ? true : options.composed,
detail,
});
eventTarget.dispatchEvent(event);
};

0 comments on commit 43b018c

Please sign in to comment.