diff --git a/src/locutus.ts b/src/locutus.ts index 5a8e2dc..b021184 100644 --- a/src/locutus.ts +++ b/src/locutus.ts @@ -29,6 +29,18 @@ export class App { this.handleUrl(document.URL); } + close() { + if (this.session) this.session.close(); + } + + sleep() { + if (this.session) this.session.sleep(); + } + + async wakeup() { + if (this.session) await this.session.wakeup(); + } + setHash(hash: string) { const url = new URL(document.URL); url.hash = hash; @@ -244,8 +256,8 @@ export class App { } }); - this.session.subscribe(response => { - this.addMessage(response.data.data.message); + this.session.subscribe(event => { + this.addMessage(event.data.message); }); } } diff --git a/src/main.ts b/src/main.ts index a525990..ed6248b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,5 +7,32 @@ window.addEventListener("DOMContentLoaded", () => { for (const noscript of document.getElementsByTagName("noscript")) { noscript.remove(); } - new Locutus.App(); + + const locutus = new Locutus.App(); + if (isMobilePlatform()) { + console.log("MOBILE"); + document.addEventListener("visibilitychange", () => { + if (document.visibilityState === 'hidden') { + locutus.sleep(); + } else if (document.visibilityState === 'visible') { + locutus.wakeup().then(); + } + }); + } else { + console.log("DESKTOP"); + window.addEventListener("beforeunload", (evt) => { + locutus.close(); + }); + } }); + +function isMobilePlatform(): boolean { + if (window.matchMedia("(any-pointer:coarse)").matches) return true; + const ua = navigator.userAgent; + if (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(ua)) { + return true; + } else if (/Mobile|Android|iP(hone|od)|IEMobile|BlackBerry|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/.test(ua)) { + return true; + } + return false; +} diff --git a/src/usimp.ts b/src/usimp.ts index da72a4a..3e9d17a 100644 --- a/src/usimp.ts +++ b/src/usimp.ts @@ -30,6 +30,13 @@ interface OutputEnvelopeJson { } | null, } +interface EventJson { + data: { + message: string, + } + uuid: string, +} + interface WellKnownJson { domain: DomainJson, domain_servers: DomainServerJson[] @@ -149,6 +156,7 @@ export class Session { websocket: WebSocket | null; numRequests: number; subscriptions: string[]; + subscriptionEndpoints: {cb: (a: EventJson) => void}[]; constructor(domain: Domain) { this.domain = domain; @@ -158,6 +166,28 @@ export class Session { this.httpBaseUrl = null; this.websocket = null; this.subscriptions = []; + this.subscriptionEndpoints = []; + } + + close() { + if (this.websocket && (this.websocket.readyState !== WebSocket.CLOSING && this.websocket.readyState !== WebSocket.CLOSED)) { + this.websocket.close(); + this.subscriptions = []; + this.websocket = null; + this.server = null; + } + } + + sleep() { + this.close(); + } + + async wakeup() { + await this.chooseDomainServer(); + await this.ping(); + for (const endpoint of this.subscriptionEndpoints) { + await this._subscribe(endpoint.cb); + } } async chooseDomainServer(): Promise<{ http: number | null, ws: number | null } | undefined> { @@ -289,7 +319,7 @@ export class Session { responseData.duration = endTime - startTime; return responseData; } else { - return null; // TODO subscription id + return await this.waitForWebSocketResponse(req_nr, timeout); } } else if (this.httpBaseUrl) { const controller = new AbortController(); @@ -369,25 +399,36 @@ export class Session { }); } - async subscribe(cb: (a: OutputEnvelopeJson) => void) { + async subscribe(cb: (a: EventJson) => void) { + this.subscriptionEndpoints.push({cb: cb}); + await this._subscribe(cb); + } + + private async _subscribe(cb: (a: EventJson) => void) { this.numRequests++; if (this.websocket !== null) { - let subscription = await this.send('subscribe', {}, 60_000, false, cb); + const subscription = await this.send('subscribe', {}, 60_000, false, (res) => { + if (res.data && res.data.events) { + for (const event of res.data.events) { + cb(event); + } + } + }); this.subscriptions.push(subscription); return subscription; } else { this.send('subscribe', {}, 60_000).then((res) => { if (res.status === "success") { - this.subscribe(cb); + this._subscribe(cb); cb(res); } else { setTimeout(() => { - this.subscribe(cb); + this._subscribe(cb); }, 1000); } }).catch(() => { setTimeout(() => { - this.subscribe(cb); + this._subscribe(cb); }, 1000); }); return null; // TODO subscription id