@intuitionrobotics/push-pub-sub
Version:
198 lines • 12.2 kB
JavaScript
"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