sahab-utils 1.7.7 → 1.7.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants/types.d.ts +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/ts/broadcast.d.ts +69 -0
- package/dist/ts/broadcast.js +77 -0
- package/dist/ts/orderActivity.d.ts +1 -1
- package/dist/ts/orderActivity.js +1 -1
- package/dist/ts/push.d.ts +80 -0
- package/dist/ts/push.js +37 -0
- package/package.json +2 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DocumentReference, GeoPoint, Timestamp } from 'firebase/firestore';
|
|
2
|
+
import type { PushTransport } from '../ts/push';
|
|
2
3
|
export type GeoPointType = GeoPoint;
|
|
3
4
|
export type TimestampType = Timestamp;
|
|
4
5
|
export type DistenceDueration = {
|
|
@@ -813,6 +814,10 @@ export type AppSettings = {
|
|
|
813
814
|
cashback?: CashbackOffer;
|
|
814
815
|
/** Kill switch for the order Live Activity / Android Live Update pushes. Off unless true. */
|
|
815
816
|
liveActivitiesEnabled?: boolean;
|
|
817
|
+
/** Which transport delivers push: OneSignal (default when unset), FCM/APNs per recipient that has a token, or FCM only. */
|
|
818
|
+
pushTransport?: PushTransport;
|
|
819
|
+
/** Kill switch for staff push broadcasts. Off unless true; test sends to the dev team still work. */
|
|
820
|
+
broadcastEnabled?: boolean;
|
|
816
821
|
};
|
|
817
822
|
export type OrderReview = {
|
|
818
823
|
ratingId: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
export * from './constants/types';
|
|
2
|
+
export * from './ts/broadcast';
|
|
2
3
|
export * from './ts/calcs';
|
|
3
4
|
export * from './ts/cashback';
|
|
4
5
|
export * from './ts/gifts';
|
|
5
6
|
export * from './ts/loyalty';
|
|
6
7
|
export * from './ts/orderActivity';
|
|
8
|
+
export * from './ts/push';
|
|
7
9
|
export * from './ts/utils';
|
|
8
10
|
export * from './ts/wheel';
|
package/dist/index.js
CHANGED
|
@@ -15,10 +15,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./constants/types"), exports);
|
|
18
|
+
__exportStar(require("./ts/broadcast"), exports);
|
|
18
19
|
__exportStar(require("./ts/calcs"), exports);
|
|
19
20
|
__exportStar(require("./ts/cashback"), exports);
|
|
20
21
|
__exportStar(require("./ts/gifts"), exports);
|
|
21
22
|
__exportStar(require("./ts/loyalty"), exports);
|
|
22
23
|
__exportStar(require("./ts/orderActivity"), exports);
|
|
24
|
+
__exportStar(require("./ts/push"), exports);
|
|
23
25
|
__exportStar(require("./ts/utils"), exports);
|
|
24
26
|
__exportStar(require("./ts/wheel"), exports);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { AppSettings, TimestampType } from '../constants/types';
|
|
2
|
+
export type BilingualText = {
|
|
3
|
+
en: string;
|
|
4
|
+
ar: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const BROADCAST_LIMITS: {
|
|
7
|
+
readonly titleMax: 50;
|
|
8
|
+
readonly bodyMax: 180;
|
|
9
|
+
readonly minLeadMs: number;
|
|
10
|
+
readonly maxLeadMs: number;
|
|
11
|
+
readonly hourlyCapPerStaff: 10;
|
|
12
|
+
};
|
|
13
|
+
export type BroadcastProviderRole = 'owner' | 'driver';
|
|
14
|
+
export type BroadcastAudience = {
|
|
15
|
+
app: 'client';
|
|
16
|
+
language?: 'ar' | 'en';
|
|
17
|
+
} | {
|
|
18
|
+
app: 'provider';
|
|
19
|
+
roles: BroadcastProviderRole[];
|
|
20
|
+
};
|
|
21
|
+
export type BroadcastMessage = {
|
|
22
|
+
title: BilingualText;
|
|
23
|
+
body: BilingualText;
|
|
24
|
+
};
|
|
25
|
+
export type BroadcastRequest = {
|
|
26
|
+
/** A UUID the page makes per send; the server uses it as the record id, so a retry is idempotent. */
|
|
27
|
+
clientRequestId: string;
|
|
28
|
+
audience: BroadcastAudience;
|
|
29
|
+
message: BroadcastMessage;
|
|
30
|
+
scheduleAtMs?: number;
|
|
31
|
+
/** Device count from the preview; the server refuses when the audience has moved past the tolerance. */
|
|
32
|
+
expectedDeviceCount: number;
|
|
33
|
+
};
|
|
34
|
+
/** Counts only, never uids or tokens. */
|
|
35
|
+
export type BroadcastPreview = {
|
|
36
|
+
deviceCount: number;
|
|
37
|
+
userCount: number;
|
|
38
|
+
};
|
|
39
|
+
export type BroadcastStatus = 'scheduled' | 'sending' | 'sent' | 'cancelled' | 'failed';
|
|
40
|
+
/** `push_broadcasts/{clientRequestId}` — also the audit log. */
|
|
41
|
+
export type BroadcastRecord = {
|
|
42
|
+
id: string;
|
|
43
|
+
audience: BroadcastAudience;
|
|
44
|
+
message: BroadcastMessage;
|
|
45
|
+
status: BroadcastStatus;
|
|
46
|
+
createdBy: string;
|
|
47
|
+
createdAt: TimestampType;
|
|
48
|
+
scheduleAt?: TimestampType;
|
|
49
|
+
startedAt?: TimestampType;
|
|
50
|
+
finishedAt?: TimestampType;
|
|
51
|
+
deviceCount: number;
|
|
52
|
+
userCount: number;
|
|
53
|
+
/** FCM accepted the message; not proof a device displayed it. */
|
|
54
|
+
successCount?: number;
|
|
55
|
+
failureCount?: number;
|
|
56
|
+
isTest?: boolean;
|
|
57
|
+
/** Scheduled sends only; never returned to the browser. */
|
|
58
|
+
runToken?: string;
|
|
59
|
+
taskName?: string;
|
|
60
|
+
error?: string;
|
|
61
|
+
};
|
|
62
|
+
/** Returns human-readable errors; an empty array means valid. Never throws on bad input. */
|
|
63
|
+
export declare const validateBroadcastRequest: (req: unknown, nowMs: number) => string[];
|
|
64
|
+
/** A preview that has gone stale is refused: within 2% of the expected count, never tighter than 5 devices. */
|
|
65
|
+
export declare const broadcastCountTolerated: (expected: number, actual: number) => boolean;
|
|
66
|
+
export declare const broadcastConfirmPhrase: (deviceCount: number) => string;
|
|
67
|
+
/** Broadcasts never use the provider new-order channel, which plays the horn. */
|
|
68
|
+
export declare const broadcastAndroidChannel: (app: "client" | "provider") => string;
|
|
69
|
+
export declare const isBroadcastEnabled: (settings?: Pick<AppSettings, "broadcastEnabled"> | null) => boolean;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isBroadcastEnabled = exports.broadcastAndroidChannel = exports.broadcastConfirmPhrase = exports.broadcastCountTolerated = exports.validateBroadcastRequest = exports.BROADCAST_LIMITS = void 0;
|
|
4
|
+
const push_1 = require("./push");
|
|
5
|
+
// maxLeadMs is Cloud Tasks' 30-day scheduling horizon; title/body sized for lock-screen truncation.
|
|
6
|
+
exports.BROADCAST_LIMITS = { titleMax: 50, bodyMax: 180, minLeadMs: 5 * 60000, maxLeadMs: 30 * 24 * 3600000, hourlyCapPerStaff: 10 };
|
|
7
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
8
|
+
// eslint-disable-next-line no-control-regex
|
|
9
|
+
const CONTROL_ANY = /[\u0000-\u001f\u007f]/;
|
|
10
|
+
// eslint-disable-next-line no-control-regex
|
|
11
|
+
const CONTROL_NOT_NEWLINE = /[\u0000-\u0009\u000b-\u001f\u007f]/;
|
|
12
|
+
const isObject = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
13
|
+
function validateText(label, value, max, allowNewline, errors) {
|
|
14
|
+
if (typeof value !== 'string' || value.trim() === '')
|
|
15
|
+
return errors.push(`${label} is required.`);
|
|
16
|
+
if (value.trim().length > max)
|
|
17
|
+
errors.push(`${label} is longer than ${max} characters.`);
|
|
18
|
+
if ((allowNewline ? CONTROL_NOT_NEWLINE : CONTROL_ANY).test(value))
|
|
19
|
+
errors.push(`${label} contains control characters.`);
|
|
20
|
+
}
|
|
21
|
+
function validateAudience(audience, errors) {
|
|
22
|
+
if (!isObject(audience))
|
|
23
|
+
return errors.push('Audience is required.');
|
|
24
|
+
if (audience.app === 'client') {
|
|
25
|
+
if (audience.language !== undefined && audience.language !== 'ar' && audience.language !== 'en')
|
|
26
|
+
errors.push('Language must be ar or en.');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (audience.app === 'provider') {
|
|
30
|
+
const roles = audience.roles;
|
|
31
|
+
const valid = Array.isArray(roles) && roles.length >= 1 && roles.length <= 2 && roles.every((r) => r === 'owner' || r === 'driver') && new Set(roles).size === roles.length;
|
|
32
|
+
if (!valid)
|
|
33
|
+
errors.push('Choose owners, drivers, or both.');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
errors.push('Audience must be customers or providers.');
|
|
37
|
+
}
|
|
38
|
+
/** Returns human-readable errors; an empty array means valid. Never throws on bad input. */
|
|
39
|
+
const validateBroadcastRequest = (req, nowMs) => {
|
|
40
|
+
if (!isObject(req))
|
|
41
|
+
return ['Request is required.'];
|
|
42
|
+
const errors = [];
|
|
43
|
+
if (typeof req.clientRequestId !== 'string' || !UUID.test(req.clientRequestId))
|
|
44
|
+
errors.push('Request id must be a UUID.');
|
|
45
|
+
validateAudience(req.audience, errors);
|
|
46
|
+
const message = isObject(req.message) ? req.message : undefined;
|
|
47
|
+
const title = message && isObject(message.title) ? message.title : undefined;
|
|
48
|
+
const body = message && isObject(message.body) ? message.body : undefined;
|
|
49
|
+
validateText('English title', title === null || title === void 0 ? void 0 : title.en, exports.BROADCAST_LIMITS.titleMax, false, errors);
|
|
50
|
+
validateText('Arabic title', title === null || title === void 0 ? void 0 : title.ar, exports.BROADCAST_LIMITS.titleMax, false, errors);
|
|
51
|
+
validateText('English body', body === null || body === void 0 ? void 0 : body.en, exports.BROADCAST_LIMITS.bodyMax, true, errors);
|
|
52
|
+
validateText('Arabic body', body === null || body === void 0 ? void 0 : body.ar, exports.BROADCAST_LIMITS.bodyMax, true, errors);
|
|
53
|
+
const count = req.expectedDeviceCount;
|
|
54
|
+
if (typeof count !== 'number' || !Number.isInteger(count) || count < 1)
|
|
55
|
+
errors.push('Preview the audience first: it has no devices to reach.');
|
|
56
|
+
if (req.scheduleAtMs !== undefined) {
|
|
57
|
+
const at = req.scheduleAtMs;
|
|
58
|
+
if (typeof at !== 'number' || !Number.isFinite(at))
|
|
59
|
+
errors.push('Schedule time is invalid.');
|
|
60
|
+
else if (at < nowMs + exports.BROADCAST_LIMITS.minLeadMs)
|
|
61
|
+
errors.push('Schedule at least 5 minutes from now.');
|
|
62
|
+
else if (at > nowMs + exports.BROADCAST_LIMITS.maxLeadMs)
|
|
63
|
+
errors.push('Schedule at most 30 days ahead.');
|
|
64
|
+
}
|
|
65
|
+
return errors;
|
|
66
|
+
};
|
|
67
|
+
exports.validateBroadcastRequest = validateBroadcastRequest;
|
|
68
|
+
/** A preview that has gone stale is refused: within 2% of the expected count, never tighter than 5 devices. */
|
|
69
|
+
const broadcastCountTolerated = (expected, actual) => Math.abs(expected - actual) <= Math.max(5, Math.floor(expected * 0.02));
|
|
70
|
+
exports.broadcastCountTolerated = broadcastCountTolerated;
|
|
71
|
+
const broadcastConfirmPhrase = (deviceCount) => `SEND ${deviceCount}`;
|
|
72
|
+
exports.broadcastConfirmPhrase = broadcastConfirmPhrase;
|
|
73
|
+
/** Broadcasts never use the provider new-order channel, which plays the horn. */
|
|
74
|
+
const broadcastAndroidChannel = (app) => (app === 'client' ? push_1.ANDROID_CHANNEL.client : push_1.ANDROID_CHANNEL.provider);
|
|
75
|
+
exports.broadcastAndroidChannel = broadcastAndroidChannel;
|
|
76
|
+
const isBroadcastEnabled = (settings) => (settings === null || settings === void 0 ? void 0 : settings.broadcastEnabled) === true;
|
|
77
|
+
exports.isBroadcastEnabled = isBroadcastEnabled;
|
|
@@ -9,7 +9,7 @@ export declare const ORDER_DONE_VISIBLE_MS: number;
|
|
|
9
9
|
export declare const ORDER_ACTIVITY_STALE_MS: number;
|
|
10
10
|
/** How long the widget trusts a status it was last told while the app was closed; after that it asks the customer to open the app. */
|
|
11
11
|
export declare const ORDER_WIDGET_FRESH_MS: number;
|
|
12
|
-
/** The id both
|
|
12
|
+
/** The id both the server and the app's token registration use for one order's activity. */
|
|
13
13
|
export declare const orderActivityId: (orderId: string) => string;
|
|
14
14
|
/** The deep link a push-started activity carries in its static attributes; the app reads the order id back out of it. */
|
|
15
15
|
export declare const orderDeepLink: (orderId: string) => string;
|
package/dist/ts/orderActivity.js
CHANGED
|
@@ -20,7 +20,7 @@ exports.ORDER_DONE_VISIBLE_MS = 2 * 60 * 60 * 1000;
|
|
|
20
20
|
exports.ORDER_ACTIVITY_STALE_MS = 3 * 60 * 60 * 1000;
|
|
21
21
|
/** How long the widget trusts a status it was last told while the app was closed; after that it asks the customer to open the app. */
|
|
22
22
|
exports.ORDER_WIDGET_FRESH_MS = 45 * 60 * 1000;
|
|
23
|
-
/** The id both
|
|
23
|
+
/** The id both the server and the app's token registration use for one order's activity. */
|
|
24
24
|
const orderActivityId = (orderId) => `order-${orderId}`;
|
|
25
25
|
exports.orderActivityId = orderActivityId;
|
|
26
26
|
/** The deep link a push-started activity carries in its static attributes; the app reads the order id back out of it. */
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { AppSettings, TimestampType } from '../constants/types';
|
|
2
|
+
import { OrderActivityContent } from './orderActivity';
|
|
3
|
+
export type PushApp = 'client' | 'provider';
|
|
4
|
+
export type PushRole = 'customer' | 'owner' | 'driver';
|
|
5
|
+
export type PushPlatform = 'ios' | 'android';
|
|
6
|
+
export type PushTransport = 'onesignal' | 'auto' | 'fcm';
|
|
7
|
+
export type ApnsEnvironment = 'sandbox' | 'production';
|
|
8
|
+
export type LiveNotificationKind = 'start' | 'update' | 'end';
|
|
9
|
+
export type PushActivityEntry = {
|
|
10
|
+
state: 'pending' | 'started' | 'ended';
|
|
11
|
+
leaseUntil?: TimestampType;
|
|
12
|
+
pushToken?: string;
|
|
13
|
+
startedAt?: TimestampType;
|
|
14
|
+
endedAt?: TimestampType;
|
|
15
|
+
};
|
|
16
|
+
/** `pushTokens/{sha256(token)}`. `uid` is what the server addresses; `authUid` is the verified Firebase uid that registered it. */
|
|
17
|
+
export type PushTokenDoc = {
|
|
18
|
+
token: string;
|
|
19
|
+
app: PushApp;
|
|
20
|
+
role: PushRole;
|
|
21
|
+
platform: PushPlatform;
|
|
22
|
+
language: 'ar' | 'en';
|
|
23
|
+
uid: string;
|
|
24
|
+
/** Drivers only: the carwash they belong to, so a staff broadcast can check the driver is still active. */
|
|
25
|
+
providerUid?: string;
|
|
26
|
+
authUid: string;
|
|
27
|
+
apnsEnvironment?: ApnsEnvironment;
|
|
28
|
+
pushToStartToken?: string;
|
|
29
|
+
appVersion?: string;
|
|
30
|
+
/** Keyed by `orderActivityId`; entries ended more than 7 days ago are dropped on write. */
|
|
31
|
+
activities?: Record<string, PushActivityEntry>;
|
|
32
|
+
createdAt: TimestampType;
|
|
33
|
+
updatedAt: TimestampType;
|
|
34
|
+
/** Firestore TTL field: now + 90 days, refreshed on every register. */
|
|
35
|
+
expireAt: TimestampType;
|
|
36
|
+
};
|
|
37
|
+
export type RegisterPushTokenBody = Pick<PushTokenDoc, 'token' | 'app' | 'uid' | 'role' | 'platform' | 'language' | 'apnsEnvironment' | 'pushToStartToken' | 'appVersion'> & {
|
|
38
|
+
/** Required when `role` is `driver`: the carwash the driver belongs to, so the server can phone-match them. */
|
|
39
|
+
providerUid?: string;
|
|
40
|
+
/** The token this device held before a refresh; the server deletes it when the same caller owns it. */
|
|
41
|
+
previousToken?: string;
|
|
42
|
+
};
|
|
43
|
+
export type UnregisterPushTokenBody = {
|
|
44
|
+
token: string;
|
|
45
|
+
uid: string;
|
|
46
|
+
};
|
|
47
|
+
export type RegisterActivityTokenBody = {
|
|
48
|
+
token: string;
|
|
49
|
+
activityId: string;
|
|
50
|
+
pushToken: string;
|
|
51
|
+
};
|
|
52
|
+
export declare const PUSH_TOKENS_COLLECTION = "pushTokens";
|
|
53
|
+
export declare const ANDROID_CHANNEL: {
|
|
54
|
+
readonly client: "orders";
|
|
55
|
+
readonly providerNewOrder: "new-orders";
|
|
56
|
+
readonly provider: "general";
|
|
57
|
+
};
|
|
58
|
+
export declare const APNS_LIVE_ACTIVITY_TOPIC_SUFFIX = ".push-type.liveactivity";
|
|
59
|
+
export declare const LIVE_NOTIFICATION_DATA_KEY = "live_notification";
|
|
60
|
+
/** An absent or unrecognised value is `onesignal`, so the switch defaults to today's behaviour. */
|
|
61
|
+
export declare const pushTransportOf: (settings?: Pick<AppSettings, "pushTransport"> | null) => PushTransport;
|
|
62
|
+
export declare const choosePushTransport: (mode: PushTransport, hasTokens: boolean) => "onesignal" | "fcm";
|
|
63
|
+
export declare const buildLiveNotification: (kind: LiveNotificationKind, orderId: string, content: OrderActivityContent) => {
|
|
64
|
+
key: string;
|
|
65
|
+
event: LiveNotificationKind;
|
|
66
|
+
event_attributes: {
|
|
67
|
+
orderId: string;
|
|
68
|
+
url: string;
|
|
69
|
+
};
|
|
70
|
+
event_updates: {
|
|
71
|
+
stepIndex: number;
|
|
72
|
+
stepCount: number;
|
|
73
|
+
progress: number;
|
|
74
|
+
title: string;
|
|
75
|
+
subtitle: string;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
/** FCM data values must be strings, so the notification travels as one JSON string. */
|
|
79
|
+
export declare const buildLiveNotificationData: (kind: LiveNotificationKind, orderId: string, content: OrderActivityContent) => Record<string, string>;
|
|
80
|
+
export declare const sanitizePushUids: (uids: ReadonlyArray<string | null | undefined>) => string[];
|
package/dist/ts/push.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sanitizePushUids = exports.buildLiveNotificationData = exports.buildLiveNotification = exports.choosePushTransport = exports.pushTransportOf = exports.LIVE_NOTIFICATION_DATA_KEY = exports.APNS_LIVE_ACTIVITY_TOPIC_SUFFIX = exports.ANDROID_CHANNEL = exports.PUSH_TOKENS_COLLECTION = void 0;
|
|
4
|
+
const orderActivity_1 = require("./orderActivity");
|
|
5
|
+
exports.PUSH_TOKENS_COLLECTION = 'pushTokens';
|
|
6
|
+
exports.ANDROID_CHANNEL = { client: 'orders', providerNewOrder: 'new-orders', provider: 'general' };
|
|
7
|
+
exports.APNS_LIVE_ACTIVITY_TOPIC_SUFFIX = '.push-type.liveactivity';
|
|
8
|
+
exports.LIVE_NOTIFICATION_DATA_KEY = 'live_notification';
|
|
9
|
+
const PUSH_TRANSPORTS = ['onesignal', 'auto', 'fcm'];
|
|
10
|
+
/** An absent or unrecognised value is `onesignal`, so the switch defaults to today's behaviour. */
|
|
11
|
+
const pushTransportOf = (settings) => {
|
|
12
|
+
const mode = settings === null || settings === void 0 ? void 0 : settings.pushTransport;
|
|
13
|
+
return mode && PUSH_TRANSPORTS.includes(mode) ? mode : 'onesignal';
|
|
14
|
+
};
|
|
15
|
+
exports.pushTransportOf = pushTransportOf;
|
|
16
|
+
const choosePushTransport = (mode, hasTokens) => {
|
|
17
|
+
if (mode === 'fcm')
|
|
18
|
+
return 'fcm';
|
|
19
|
+
if (mode === 'auto' && hasTokens)
|
|
20
|
+
return 'fcm';
|
|
21
|
+
return 'onesignal';
|
|
22
|
+
};
|
|
23
|
+
exports.choosePushTransport = choosePushTransport;
|
|
24
|
+
const buildLiveNotification = (kind, orderId, content) => ({
|
|
25
|
+
key: (0, orderActivity_1.orderActivityId)(orderId),
|
|
26
|
+
event: kind,
|
|
27
|
+
event_attributes: { orderId, url: (0, orderActivity_1.orderDeepLink)(orderId) },
|
|
28
|
+
event_updates: { stepIndex: content.stepIndex, stepCount: orderActivity_1.ORDER_ACTIVITY_STEP_COUNT, progress: content.progress, title: content.title, subtitle: content.subtitle },
|
|
29
|
+
});
|
|
30
|
+
exports.buildLiveNotification = buildLiveNotification;
|
|
31
|
+
/** FCM data values must be strings, so the notification travels as one JSON string. */
|
|
32
|
+
const buildLiveNotificationData = (kind, orderId, content) => ({
|
|
33
|
+
[exports.LIVE_NOTIFICATION_DATA_KEY]: JSON.stringify((0, exports.buildLiveNotification)(kind, orderId, content)),
|
|
34
|
+
});
|
|
35
|
+
exports.buildLiveNotificationData = buildLiveNotificationData;
|
|
36
|
+
const sanitizePushUids = (uids) => [...new Set(uids.filter((uid) => typeof uid === 'string' && uid.length > 0))];
|
|
37
|
+
exports.sanitizePushUids = sanitizePushUids;
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sahab-utils",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.9",
|
|
4
4
|
"description": "This package contains types and utils functions",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"build": "tsc",
|
|
9
9
|
"deploy": "npm run build && npm publish",
|
|
10
|
-
"test": "npm run build && node tests/loyalty.test.js && node tests/gifts.test.js && node tests/cashback.test.js && node tests/utils.test.js && node tests/wheel.test.js && node tests/calcs.test.js && node tests/orderActivity.test.js"
|
|
10
|
+
"test": "npm run build && node tests/loyalty.test.js && node tests/gifts.test.js && node tests/cashback.test.js && node tests/utils.test.js && node tests/wheel.test.js && node tests/calcs.test.js && node tests/orderActivity.test.js && node tests/push.test.js && node tests/broadcast.test.js"
|
|
11
11
|
},
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|