diff --git a/js/js-flipper/README.md b/js/js-flipper/README.md index cf712136a..9ff64b148 100644 --- a/js/js-flipper/README.md +++ b/js/js-flipper/README.md @@ -24,8 +24,8 @@ if the corresponding desktop plugin is selected in the Flipper Desktop. The full plugin API is documented [here](https://fbflipper.com/docs/extending/create-plugin). - `start` method. It starts the client. It has two arguments: - - `appName` - (required) the name dsplayed in Flipper - - `options` which conforms to the infterface + - `appName` - (required) the name displayed in Flipper + - `options` which conforms to the interface ```ts interface FlipperClientOptions { // Make the client connect to a different URL @@ -36,6 +36,8 @@ plugin API is documented onError?: (e: unknown) => void; // Timeout after which client tries to reconnect to Flipper reconnectTimeout?: number; + // Set device ID. Default: random ID persisted to local storage. + getDeviceId?: () => Promise | string } ``` @@ -54,7 +56,7 @@ The sources of the corresponding Desktop plugin can be found ## Node.js Node.js does not have a built-in WebSocket implementation. You need to install -any implmentation of WebSockets for Node.js that is compatible with the +any implementation of WebSockets for Node.js that is compatible with the interface of the [web version](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket). diff --git a/js/js-flipper/src/client.ts b/js/js-flipper/src/client.ts index 3bdc46d33..b64f134b8 100644 --- a/js/js-flipper/src/client.ts +++ b/js/js-flipper/src/client.ts @@ -11,7 +11,12 @@ import {FlipperConnection} from './connection'; import {FlipperRequest, FlipperResponse} from './message'; import {FlipperPlugin} from './plugin'; import {FlipperResponder} from './responder'; -import {assert, detectDevice, detectOS} from './util'; +import { + assert, + detectDevice, + detectOS, + getDeviceId as getDeviceIdDefault, +} from './util'; import {RECONNECT_TIMEOUT} from './consts'; // TODO: Share with flipper-server-core @@ -133,13 +138,15 @@ interface FlipperClientOptions { onError?: (e: unknown) => void; // Timeout after which client tries to reconnect to Flipper reconnectTimeout?: number; + // Set device ID. Default: random ID persisted to local storage. + getDeviceId?: () => Promise | string; } export class FlipperClient { private readonly plugins: Map = new Map(); private readonly connections: Map = new Map(); private ws?: FlipperWebSocket; - private readonly devicePseudoId = `${Date.now()}.${Math.random()}`; + private deviceId!: string; private readonly os = detectOS(); private readonly device = detectDevice(); private reconnectionTimer?: NodeJS.Timeout; @@ -171,12 +178,14 @@ export class FlipperClient { websocketFactory = (url) => new WebSocket(url) as FlipperWebSocket, onError = (e) => console.error('WebSocket error', e), reconnectTimeout = RECONNECT_TIMEOUT, + getDeviceId = getDeviceIdDefault, }: FlipperClientOptions = {}, ): Promise { if (this.ws) { return; } + this.deviceId = await getDeviceId(); this.appName = appName; this.onError = onError; this.urlBase = urlBase; @@ -218,7 +227,7 @@ export class FlipperClient { } private connectToFlipper() { - const url = `ws://${this.urlBase}?device_id=${this.device}${this.devicePseudoId}&device=${this.device}&app=${this.appName}&os=${this.os}`; + const url = `ws://${this.urlBase}?device_id=${this.device}${this.deviceId}&device=${this.device}&app=${this.appName}&os=${this.os}`; const encodedUrl = encodeURI(url); this.ws = this.websocketFactory(encodedUrl); diff --git a/js/js-flipper/src/consts.ts b/js/js-flipper/src/consts.ts index 0e6c885a9..233283319 100644 --- a/js/js-flipper/src/consts.ts +++ b/js/js-flipper/src/consts.ts @@ -8,3 +8,4 @@ */ export const RECONNECT_TIMEOUT = 1000; +export const DEVICE_ID_STORAGE_KEY = 'js-flipper-device-id'; diff --git a/js/js-flipper/src/index.ts b/js/js-flipper/src/index.ts index 2ea67a377..58e7560ed 100644 --- a/js/js-flipper/src/index.ts +++ b/js/js-flipper/src/index.ts @@ -9,6 +9,7 @@ import {FlipperClient} from './client'; +export * from './consts'; export * from './client'; export * from './plugin'; diff --git a/js/js-flipper/src/util.ts b/js/js-flipper/src/util.ts index 2afee8c90..c5c7ad44b 100644 --- a/js/js-flipper/src/util.ts +++ b/js/js-flipper/src/util.ts @@ -7,6 +7,8 @@ * @format */ +import {DEVICE_ID_STORAGE_KEY} from './consts'; + // https://github.com/microsoft/TypeScript/issues/36931#issuecomment-846131999 type Assert = (condition: unknown) => asserts condition; export const assert: Assert = (condition) => { @@ -66,3 +68,22 @@ export const detectDevice = (): string => { } return require('os').release(); }; + +export const getDeviceId = () => { + // localStorage is not defined in Node.js env + const persistedId = + typeof localStorage === 'object' + ? localStorage?.getItem(DEVICE_ID_STORAGE_KEY) + : undefined; + + if (persistedId) { + return persistedId; + } + + const newId = `${Date.now()}.${Math.random()}`; + if (typeof localStorage === 'object') { + localStorage?.setItem(DEVICE_ID_STORAGE_KEY, newId); + } + + return newId; +};