@intuitionrobotics/push-pub-sub
Version:
175 lines • 8.05 kB
JavaScript
/*
* A typescript & react boilerplate with api call example
*
* Copyright (C) 2018 Intuition Robotics
*
* 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
*
* http://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.
*/
import { __stringify, BadImplementationException, compare, generateHex, ImplementationMissingException, Module } from "@intuitionrobotics/ts-common";
import { StorageKey, XhrHttpModule } from "@intuitionrobotics/thunderstorm/frontend";
// noinspection TypeScriptPreferShortImport
import {} from "../../index.js";
import { HttpMethod } from "@intuitionrobotics/thunderstorm";
import { FirebaseModule } from "@intuitionrobotics/firebase/app-frontend/FirebaseModule";
import { NotificationsModule } from "./NotificationModule.js";
export const Command_SwToApp = "SwToApp";
export const pushSessionIdKey = "x-push-session-id";
const pushSessionId = new StorageKey(pushSessionIdKey, false);
export class PushPubSubModule_Class extends Module {
subscriptions = [];
firebaseToken;
messaging;
pushSessionId;
timeout = 800;
constructor() {
super("PushPubSubModule");
window.name = window.name || generateHex(32);
this.pushSessionId = pushSessionId.set(window.name);
}
init() {
if (this.config?.registerOnInit === false)
return;
this.initApp();
}
getPushSessionId() {
return this.pushSessionId;
}
registerServiceWorker = async () => {
console.log("Registering service worker...");
return await navigator.serviceWorker.register(`/${this.config.swFileName || "ts_service_worker.js"}`);
};
initApp = () => {
if (!this.config?.publicKeyBase64)
throw new ImplementationMissingException(`Please specify the right config for the 'PushPubSubModule'`);
this.runAsync("Initializing Firebase SDK and registering SW", async () => {
if ("serviceWorker" in navigator) {
const asyncs = [
this.registerServiceWorker(),
FirebaseModule.createSession()
];
const { 0: registration, 1: app } = await Promise.all(asyncs);
await registration.update();
this.messaging = await app.getMessaging();
// this.messaging.usePublicVapidKey(this.config.publicKeyBase64);
// await this.messaging.useServiceWorker(registration);
await this.getToken({ vapidKey: this.config.publicKeyBase64, serviceWorkerRegistration: registration });
if (navigator.serviceWorker.controller) {
console.log(`This page is currently controlled by: ${navigator.serviceWorker.controller}`);
}
navigator.serviceWorker.oncontrollerchange = function () {
console.log("This page is now controlled by:", navigator.serviceWorker.controller);
};
navigator.serviceWorker.onmessage = (event) => {
this.processMessageFromSw(event.data);
};
}
});
};
// / need to call this from the login verified
getToken = async (options) => {
try {
this.logVerbose("Checking/Requesting permission...");
const permission = await Notification.requestPermission();
this.logVerbose(`Notification permission: ${permission}`);
if (permission !== "granted")
return;
if (!this.messaging)
throw new BadImplementationException("I literally just set this!");
this.firebaseToken = await this.messaging.getToken(options);
if (!this.firebaseToken)
return;
this.messaging.onMessage((payload) => {
if (!payload.data)
return this.logInfo('No data passed to the message handler, I got this', payload);
this.processMessage(payload.data);
});
this.logVerbose("new token received: " + this.firebaseToken);
// this.logWarning("I don't believe there is a good reason to register whenever an app starts.. before we have any information about user or app status!!")
// this.logWarning("Convince me otherwise.. :)")
// Race Condition in CC proved that I didnt register for push due to the getToken being async which ended after modules init
// so I had subscriptions but didnt register them
if (this.subscriptions.length > 0)
this.register();
}
catch (err) {
this.logError("Unable to get token", err);
}
};
processMessageFromSw = (data) => {
this.logInfo("Got data from SW: ", data);
if (!data.command || !data.message || data.command !== Command_SwToApp)
return;
this.processMessage(data.message);
};
processMessage = (data) => {
this.logInfo("process message", data);
const arr = JSON.parse(data.messages);
arr.forEach(s => {
if (s.persistent)
NotificationsModule.addNotification(s);
this.subscriptions
.filter(d => d.pushKey === s.pushKey && compare(s.props, d.props))
.forEach(d => d.callBack(s.data));
});
};
subscribe = (subscription) => {
this.subscribeImpl(subscription);
return this.register();
};
subscribeImpl = (subscription) => {
const foundSubscription = this.subscriptions.find(d => d.pushKey === subscription.pushKey && compare(subscription.props, d.props));
if (foundSubscription) {
this.logError("Subscription already exists", subscription);
return;
}
this.subscriptions.push(subscription);
};
subscribeMulti = (subscriptions) => {
subscriptions.forEach(subscription => this.subscribeImpl(subscription));
return this.register();
};
unsubscribe = (subscription) => {
const idx = this.subscriptions.findIndex(d => d.pushKey === subscription.pushKey && compare(subscription.props, d.props));
const foundSubscription = idx !== -1 ? this.subscriptions[idx] : undefined;
if (foundSubscription)
this.subscriptions.splice(idx, 1);
return this.register();
};
register = () => {
const firebaseToken = this.firebaseToken;
if (!firebaseToken)
return this.logWarning("No Firebase token...");
this.debounce(() => {
const body = {
firebaseToken,
pushSessionId: this.getPushSessionId(),
subscriptions: this.subscriptions.map(({ pushKey, props }) => ({ pushKey, props }))
};
this.logDebug("Registering subscriptions");
for (const sub of this.subscriptions) {
this.logDebug(`${sub.pushKey} => ${sub.props ? __stringify(sub.props) : "no props"}`);
}
XhrHttpModule
.createRequest(HttpMethod.POST, "register-pub-sub-tab")
.setRelativeUrl("/v1/push/register")
.setJsonBody(body)
.setOnError("Failed to register for push")
.execute((response) => {
NotificationsModule.setNotificationList(response);
this.logVerbose("Finished register PubSub");
});
}, "debounce-register", this.timeout);
};
}
export const PushPubSubModule = new PushPubSubModule_Class();
//# sourceMappingURL=PushPubSubModule.js.map