UNPKG

@intuitionrobotics/push-pub-sub

Version:
198 lines 12.2 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PushPubSubModule = exports.PushPubSubModule_Class = void 0; const ts_common_1 = require("@intuitionrobotics/ts-common"); const backend_1 = require("@intuitionrobotics/firebase/backend"); const backend_2 = require("@intuitionrobotics/thunderstorm/backend"); class PushPubSubModule_Class extends ts_common_1.Module { constructor() { super("PushPubSubModule"); this.buildNotification = (pushkey, persistent, data, props, user) => { const notification = { _id: (0, ts_common_1.generateHex)(16), timestamp: (0, ts_common_1.currentTimeMillies)(), read: false, pushKey: pushkey, persistent }; if (data) notification.data = data; if (props) notification.props = props; if (user) notification.userId = user; return notification; }; this.sendMessage = (persistent, _messages) => __awaiter(this, void 0, void 0, function* () { const messages = Object.keys(_messages).map(token => ({ token, data: { messages: (0, ts_common_1.__stringify)(_messages[token]) } })); if (messages.length === 0) return; console.log("sending a message to \n" + Object.keys(_messages).join("\n")); const response = yield this.messaging.sendAll(messages); console.log("and this is the response: " + response.responses.map(_response => _response.success)); return { response, messages }; }); this.readNotification = (id, read) => __awaiter(this, void 0, void 0, function* () { yield this.notifications.patch({ _id: id, read }); }); this.scheduledCleanup = () => __awaiter(this, void 0, void 0, function* () { var _a, _b; const sessionsCleanupTime = ((_a = this.config) === null || _a === void 0 ? void 0 : _a.sessionsCleanupTime) || ts_common_1.Hour; const notificationsCleanupTime = ((_b = this.config) === null || _b === void 0 ? void 0 : _b.notificationsCleanupTime) || 7 * ts_common_1.Day; const docs = yield this.pushSessions.query({ where: { timestamp: { $lt: (0, ts_common_1.currentTimeMillies)() - sessionsCleanupTime } } }); yield Promise.all([ this.notifications.delete({ where: { timestamp: { $lt: (0, ts_common_1.currentTimeMillies)() - notificationsCleanupTime } } }), this.cleanUpImpl(docs.map(d => d.firebaseToken)) ]); }); this.cleanUp = (response, messages) => __awaiter(this, void 0, void 0, function* () { this.logInfo(`${response.successCount} sent, ${response.failureCount} failed`); if (response.failureCount > 0) this.logWarning(response.responses.filter(r => r.error)); const toDelete = response.responses.reduce((carry, resp, i) => { if (!resp.success && messages[i]) carry.push(messages[i].token); return carry; }, []); //TODO: delete notifications for the user that are older than X return this.cleanUpImpl(toDelete); }); } init() { const session = backend_1.FirebaseModule.createAdminSession(); const firestore = session.getFirestore(); this.pushSessions = firestore.getCollection("push-sessions", ["pushSessionId"]); this.pushKeys = firestore.getCollection("push-keys"); this.notifications = firestore.getCollection("notifications", ["_id"]); this.messaging = session.getMessaging(); } register(body, request) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c; const resp = yield backend_2.dispatch_queryRequestInfo.dispatchModuleAsync(request); const userId = ((_b = (_a = resp.find(e => e.key === "AccountsModule")) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b._id) || ((_c = resp.find(e => e.key === "RemoteProxy")) === null || _c === void 0 ? void 0 : _c.data); // if (!userId) // throw new ImplementationMissingException("Missing user from accounts Module"); const session = { firebaseToken: body.firebaseToken, pushSessionId: body.pushSessionId, timestamp: (0, ts_common_1.currentTimeMillies)() }; if (userId) session.userId = userId; const subscriptions = body.subscriptions.map((s) => { const sub = { pushSessionId: body.pushSessionId, pushKey: s.pushKey }; if (s.props) sub.props = s.props; return sub; }); return this.pushSessions.runInTransaction((transaction) => __awaiter(this, void 0, void 0, function* () { const pushKeys = subscriptions.map(_sub => _sub.pushKey); let subscriptionNotifications = pushKeys.length !== 0 ? yield (0, ts_common_1.batchAction)(pushKeys, 10, (elements) => __awaiter(this, void 0, void 0, function* () { return transaction.query(this.notifications, { where: { pushKey: { $in: elements } } }); })) : []; if (subscriptionNotifications.length > 0) subscriptionNotifications = subscriptionNotifications.filter(_notification => { var _a; const x = (_a = subscriptions.find(_sub => _sub.pushKey === _notification.pushKey)) === null || _a === void 0 ? void 0 : _a.props; return (0, ts_common_1.compare)(x, _notification.props) || _notification.userId; }); const userNotifications = userId ? yield transaction.query(this.notifications, { where: { userId } }) : []; const notifications = userNotifications.concat(subscriptionNotifications); const writePush = yield transaction.upsert_Read(this.pushSessions, session); const write = yield transaction.delete_Read(this.pushKeys, { where: { pushSessionId: body.pushSessionId } }); yield transaction.insertAll(this.pushKeys, subscriptions); yield Promise.all([write(), writePush()]); return (0, ts_common_1.filterDuplicates)(notifications); })); }); } pushToKey(key_1, props_1, data_1) { return __awaiter(this, arguments, void 0, function* (key, props, data, persistent = false, transaction) { const processor = (_transaction) => __awaiter(this, void 0, void 0, function* () { console.log("i am pushing to key...", key, props, data); let docs = yield _transaction.query(this.pushKeys, { where: { pushKey: key } }); if (props) docs = docs.filter(doc => !doc.props || (0, ts_common_1.compare)(doc.props, props)); const notification = this.buildNotification(key, persistent, data, props); // If we need to do read and write for a transaction move this to a callback and rename to pushToKey_Read if (persistent) { yield this.notifications.insertAll([notification]); } if (docs.length === 0) return; const sessionsIds = docs.map(d => d.pushSessionId); // I get the tokens relative to those sessions (query) const sessions = yield (0, ts_common_1.batchAction)(sessionsIds, 10, (elements) => __awaiter(this, void 0, void 0, function* () { return _transaction.query(this.pushSessions, { where: { pushSessionId: { $in: elements } } }); })); const _messages = docs.reduce((carry, db_pushKey) => { const session = sessions.find(s => s.pushSessionId === db_pushKey.pushSessionId); if (!session) return carry; carry[session.firebaseToken] = [notification]; return carry; }, {}); const resp = yield this.sendMessage(persistent, _messages); if (!resp) return this.logInfo('No messages to send. Empty subscriptions'); const { response, messages } = resp; this.logInfo(`${response.successCount} sent, ${response.failureCount} failed`, "messages", messages); // return this.cleanUp(response, messages); }); if (transaction) return processor(transaction); return this.pushKeys.runInTransaction(processor); }); } pushToUser(user_1, key_1, props_1, data_1) { return __awaiter(this, arguments, void 0, function* (user, key, props, data, persistent = false) { console.log("i am pushing to user...", user, props); const notification = this.buildNotification(key, persistent, data, props, user); const notifications = []; if (persistent) { notifications.push(notification); yield this.notifications.insertAll(notifications); } const docs = yield this.pushSessions.query({ where: { userId: user } }); if (docs.length === 0) return; const sessionsIds = docs.map(d => d.pushSessionId); const sessions = yield (0, ts_common_1.batchAction)(sessionsIds, 10, (elements) => __awaiter(this, void 0, void 0, function* () { return this.pushSessions.query({ where: { pushSessionId: { $in: elements } } }); })); const _messages = docs.reduce((carry, db_pushKey) => { const session = sessions.find(s => s.pushSessionId === db_pushKey.pushSessionId); if (!session) return carry; carry[session.firebaseToken] = [notification]; return carry; }, {}); yield this.sendMessage(persistent, _messages); }); } cleanUpImpl(_toDelete) { return __awaiter(this, void 0, void 0, function* () { if (_toDelete.length === 0) return; const toDelete = (0, ts_common_1.filterDuplicates)(_toDelete); const _sessions = yield (0, ts_common_1.batchActionParallel)(toDelete, 10, (elements) => __awaiter(this, void 0, void 0, function* () { return this.pushSessions.query({ where: { firebaseToken: { $in: elements } } }); })); const sessions = (0, ts_common_1.filterDuplicates)(_sessions.map(s => s.pushSessionId)); const async = [ (0, ts_common_1.batchActionParallel)(toDelete, 10, (elements) => __awaiter(this, void 0, void 0, function* () { return this.pushSessions.delete({ where: { firebaseToken: { $in: elements } } }); })), (0, ts_common_1.batchActionParallel)(sessions, 10, (elements) => __awaiter(this, void 0, void 0, function* () { return this.pushKeys.delete({ where: { pushSessionId: { $in: elements } } }); })) ]; yield Promise.all(async); }); } } exports.PushPubSubModule_Class = PushPubSubModule_Class; exports.PushPubSubModule = new PushPubSubModule_Class(); //# sourceMappingURL=PushPubSubModule.js.map