@repzo/widget-react-native 1.0.0 → 1.0.4
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/README.md +167 -74
- package/lib/commonjs/api/client.js +3 -1
- package/lib/commonjs/api/client.js.map +1 -1
- package/lib/commonjs/api/realtime.js +2 -0
- package/lib/commonjs/api/realtime.js.map +1 -1
- package/lib/commonjs/attachments/pickers.js +493 -0
- package/lib/commonjs/attachments/pickers.js.map +1 -0
- package/lib/commonjs/i18n.js +45 -0
- package/lib/commonjs/i18n.js.map +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/locales.js +21 -0
- package/lib/commonjs/locales.js.map +1 -1
- package/lib/commonjs/state/widget-store.js +256 -18
- package/lib/commonjs/state/widget-store.js.map +1 -1
- package/lib/commonjs/types.js +16 -0
- package/lib/commonjs/types.js.map +1 -1
- package/lib/commonjs/ui/WidgetProvider.js +70 -10
- package/lib/commonjs/ui/WidgetProvider.js.map +1 -1
- package/lib/commonjs/ui/components/AttachmentChips.js +135 -0
- package/lib/commonjs/ui/components/AttachmentChips.js.map +1 -0
- package/lib/commonjs/ui/components/AttachmentMenu.js +125 -0
- package/lib/commonjs/ui/components/AttachmentMenu.js.map +1 -0
- package/lib/commonjs/ui/components/ImageViewer.js +181 -0
- package/lib/commonjs/ui/components/ImageViewer.js.map +1 -0
- package/lib/commonjs/ui/components/InAppNotification.js +210 -0
- package/lib/commonjs/ui/components/InAppNotification.js.map +1 -0
- package/lib/commonjs/ui/components/TeamAvatars.js +63 -0
- package/lib/commonjs/ui/components/TeamAvatars.js.map +1 -0
- package/lib/commonjs/ui/components/icons.js +50 -0
- package/lib/commonjs/ui/components/icons.js.map +1 -1
- package/lib/commonjs/ui/screens/chat/ChatView.js +303 -24
- package/lib/commonjs/ui/screens/chat/ChatView.js.map +1 -1
- package/lib/commonjs/ui/screens/chat/ChatView.styles.js +70 -0
- package/lib/commonjs/ui/screens/chat/ChatView.styles.js.map +1 -1
- package/lib/module/api/client.js +3 -1
- package/lib/module/api/client.js.map +1 -1
- package/lib/module/api/realtime.js +2 -0
- package/lib/module/api/realtime.js.map +1 -1
- package/lib/module/attachments/pickers.js +479 -0
- package/lib/module/attachments/pickers.js.map +1 -0
- package/lib/module/i18n.js +44 -0
- package/lib/module/i18n.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/locales.js +21 -0
- package/lib/module/locales.js.map +1 -1
- package/lib/module/state/widget-store.js +256 -18
- package/lib/module/state/widget-store.js.map +1 -1
- package/lib/module/types.js +16 -0
- package/lib/module/types.js.map +1 -1
- package/lib/module/ui/WidgetProvider.js +71 -11
- package/lib/module/ui/WidgetProvider.js.map +1 -1
- package/lib/module/ui/components/AttachmentChips.js +131 -0
- package/lib/module/ui/components/AttachmentChips.js.map +1 -0
- package/lib/module/ui/components/AttachmentMenu.js +120 -0
- package/lib/module/ui/components/AttachmentMenu.js.map +1 -0
- package/lib/module/ui/components/ImageViewer.js +177 -0
- package/lib/module/ui/components/ImageViewer.js.map +1 -0
- package/lib/module/ui/components/InAppNotification.js +208 -0
- package/lib/module/ui/components/InAppNotification.js.map +1 -0
- package/lib/module/ui/components/TeamAvatars.js +59 -0
- package/lib/module/ui/components/TeamAvatars.js.map +1 -0
- package/lib/module/ui/components/icons.js +49 -0
- package/lib/module/ui/components/icons.js.map +1 -1
- package/lib/module/ui/screens/chat/ChatView.js +306 -27
- package/lib/module/ui/screens/chat/ChatView.js.map +1 -1
- package/lib/module/ui/screens/chat/ChatView.styles.js +70 -0
- package/lib/module/ui/screens/chat/ChatView.styles.js.map +1 -1
- package/lib/typescript/commonjs/api/client.d.ts +8 -2
- package/lib/typescript/commonjs/api/client.d.ts.map +1 -1
- package/lib/typescript/commonjs/api/realtime.d.ts.map +1 -1
- package/lib/typescript/commonjs/attachments/pickers.d.ts +81 -0
- package/lib/typescript/commonjs/attachments/pickers.d.ts.map +1 -0
- package/lib/typescript/commonjs/i18n.d.ts +23 -0
- package/lib/typescript/commonjs/i18n.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +1 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/locales.d.ts.map +1 -1
- package/lib/typescript/commonjs/state/widget-store.d.ts +40 -2
- package/lib/typescript/commonjs/state/widget-store.d.ts.map +1 -1
- package/lib/typescript/commonjs/types.d.ts +88 -1
- package/lib/typescript/commonjs/types.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/WidgetProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/AttachmentChips.d.ts +16 -0
- package/lib/typescript/commonjs/ui/components/AttachmentChips.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/AttachmentMenu.d.ts +28 -0
- package/lib/typescript/commonjs/ui/components/AttachmentMenu.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts +23 -0
- package/lib/typescript/commonjs/ui/components/ImageViewer.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/InAppNotification.d.ts +28 -0
- package/lib/typescript/commonjs/ui/components/InAppNotification.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/TeamAvatars.d.ts +17 -0
- package/lib/typescript/commonjs/ui/components/TeamAvatars.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/icons.d.ts +2 -0
- package/lib/typescript/commonjs/ui/components/icons.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts +3 -2
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts +69 -0
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts +41 -2
- package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts.map +1 -1
- package/lib/typescript/module/api/client.d.ts +8 -2
- package/lib/typescript/module/api/client.d.ts.map +1 -1
- package/lib/typescript/module/api/realtime.d.ts.map +1 -1
- package/lib/typescript/module/attachments/pickers.d.ts +81 -0
- package/lib/typescript/module/attachments/pickers.d.ts.map +1 -0
- package/lib/typescript/module/i18n.d.ts +23 -0
- package/lib/typescript/module/i18n.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +1 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/locales.d.ts.map +1 -1
- package/lib/typescript/module/state/widget-store.d.ts +40 -2
- package/lib/typescript/module/state/widget-store.d.ts.map +1 -1
- package/lib/typescript/module/types.d.ts +88 -1
- package/lib/typescript/module/types.d.ts.map +1 -1
- package/lib/typescript/module/ui/WidgetProvider.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/AttachmentChips.d.ts +16 -0
- package/lib/typescript/module/ui/components/AttachmentChips.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/AttachmentMenu.d.ts +28 -0
- package/lib/typescript/module/ui/components/AttachmentMenu.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/ImageViewer.d.ts +23 -0
- package/lib/typescript/module/ui/components/ImageViewer.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/InAppNotification.d.ts +28 -0
- package/lib/typescript/module/ui/components/InAppNotification.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/TeamAvatars.d.ts +17 -0
- package/lib/typescript/module/ui/components/TeamAvatars.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/icons.d.ts +2 -0
- package/lib/typescript/module/ui/components/icons.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.d.ts +3 -2
- package/lib/typescript/module/ui/screens/chat/ChatView.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts +69 -0
- package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts +41 -2
- package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts.map +1 -1
- package/metro.js +17 -32
- package/package.json +18 -1
- package/src/api/client.ts +5 -3
- package/src/api/realtime.ts +2 -0
- package/src/attachments/pickers.ts +515 -0
- package/src/i18n.ts +48 -0
- package/src/index.tsx +4 -0
- package/src/locales.ts +21 -0
- package/src/state/widget-store.ts +256 -19
- package/src/types.ts +95 -0
- package/src/ui/WidgetProvider.tsx +90 -9
- package/src/ui/components/AttachmentChips.tsx +135 -0
- package/src/ui/components/AttachmentMenu.tsx +132 -0
- package/src/ui/components/ImageViewer.tsx +178 -0
- package/src/ui/components/InAppNotification.tsx +225 -0
- package/src/ui/components/TeamAvatars.tsx +58 -0
- package/src/ui/components/icons.tsx +37 -0
- package/src/ui/screens/chat/ChatView.styles.ts +70 -0
- package/src/ui/screens/chat/ChatView.tsx +345 -35
- package/src/ui/screens/chat/ChatView.types.ts +51 -2
package/lib/module/locales.js
CHANGED
|
@@ -19,6 +19,18 @@ const ar = {
|
|
|
19
19
|
emptyConversation: 'أرسل رسالة لبدء المحادثة',
|
|
20
20
|
messagePlaceholder: 'اكتب رسالة...',
|
|
21
21
|
attachFile: 'إرفاق ملف',
|
|
22
|
+
attachPhotoLibrary: 'مكتبة الصور',
|
|
23
|
+
attachTakePhoto: 'التقاط صورة',
|
|
24
|
+
attachChooseFile: 'ملف',
|
|
25
|
+
removeAttachment: 'إزالة {name}',
|
|
26
|
+
attachmentTooLarge: 'الملف كبير جدًا (الحد الأقصى {max})',
|
|
27
|
+
attachmentTooMany: 'يمكنك إرفاق {count} ملفات كحد أقصى',
|
|
28
|
+
attachmentsTooLargeTogether: 'المرفقات كبيرة جدًا لإرسالها معًا — أرسل بعضها في رسالة أخرى',
|
|
29
|
+
attachmentFailed: 'تعذر إرفاق هذا الملف',
|
|
30
|
+
imageLoadFailed: 'تعذر تحميل هذه الصورة',
|
|
31
|
+
openImageExternally: 'فتح الصورة',
|
|
32
|
+
cameraPermissionDenied: 'اسمح بالوصول إلى الكاميرا من الإعدادات لالتقاط صورة',
|
|
33
|
+
cancel: 'إلغاء',
|
|
22
34
|
failedToSend: 'تعذر الإرسال',
|
|
23
35
|
thinking: 'يكتب',
|
|
24
36
|
messages: 'الرسائل',
|
|
@@ -55,6 +67,11 @@ const ar = {
|
|
|
55
67
|
today: 'اليوم',
|
|
56
68
|
yesterday: 'أمس',
|
|
57
69
|
conversationUpdated: 'تم تحديث المحادثة',
|
|
70
|
+
messageDeleted: 'تم حذف هذه الرسالة',
|
|
71
|
+
messageSent: 'تم الإرسال',
|
|
72
|
+
messageSeen: 'تمت المشاهدة',
|
|
73
|
+
waitingForTeammate: 'بانتظار أحد أعضاء الفريق',
|
|
74
|
+
joinedConversation: 'انضم إلى المحادثة',
|
|
58
75
|
procedureSend: 'إرسال التفاصيل',
|
|
59
76
|
procedureSent: 'تم الإرسال',
|
|
60
77
|
fieldRequired: '{field} مطلوب',
|
|
@@ -63,6 +80,10 @@ const ar = {
|
|
|
63
80
|
outboundTitle: 'تحديث جديد',
|
|
64
81
|
outboundCta: 'اعرف المزيد',
|
|
65
82
|
dismiss: 'تجاهل',
|
|
83
|
+
notificationReplyFrom: 'رد من {name}',
|
|
84
|
+
notificationNewReply: 'رد جديد',
|
|
85
|
+
notificationAttachment: 'أرسل مرفقًا',
|
|
86
|
+
notificationOpenHint: 'يفتح المحادثة',
|
|
66
87
|
blockService: 'خدمة',
|
|
67
88
|
blockProduct: 'منتج',
|
|
68
89
|
blockChooseTime: 'اختر وقتًا',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["ar","back","loading","loadEarlier","emptyConversation","messagePlaceholder","attachFile","failedToSend","thinking","messages","noMessages","historyHidden","startBelow","preChatTitle","preChatSubtitle","preChatSkip","preChatSubmit","viewPreviousConversations","homeRecentTitle","homeViewAllCount","homeNoConversations","homeStatusOnline","homeStatusAway","unreadBadge","team","home","close","retry","connectionError","connecting","chatUnavailable","identityVerificationError","widgetCrashed","timeJustNow","timeMinutes","timeHours","timeDays","timeMonths","timeYears","today","yesterday","conversationUpdated","procedureSend","procedureSent","fieldRequired","invalidEmail","working","outboundTitle","outboundCta","dismiss","blockService","blockProduct","blockChooseTime","blockProfessional","blockPriceOffer","BUNDLED_TRANSLATIONS","baseLanguage","locale","split","toLowerCase"],"sourceRoot":"../../src","sources":["locales.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAMA,MAAMA,EAAY,GAAG;EACnBC,IAAI,EAAE,MAAM;EACZC,OAAO,EAAE,iBAAiB;EAC1BC,WAAW,EAAE,uBAAuB;EACpCC,iBAAiB,EAAE,0BAA0B;EAC7CC,kBAAkB,EAAE,eAAe;EACnCC,UAAU,EAAE,WAAW;EACvBC,YAAY,EAAE,cAAc;EAC5BC,QAAQ,EAAE,MAAM;EAChBC,QAAQ,EAAE,SAAS;EACnBC,UAAU,EAAE,mBAAmB;EAC/BC,aAAa,EAAE,oBAAoB;EACnCC,UAAU,EAAE,uBAAuB;EACnCC,YAAY,EAAE,aAAa;EAC3BC,eAAe,EAAE,mDAAmD;EACpEC,WAAW,EAAE,MAAM;EACnBC,aAAa,EAAE,eAAe;EAC9BC,yBAAyB,EAAE,uBAAuB;EAClDC,eAAe,EAAE,QAAQ;EACzBC,gBAAgB,EAAE,oBAAoB;EACtCC,mBAAmB,EAAE,oBAAoB;EACzCC,gBAAgB,EAAE,YAAY;EAC9BC,cAAc,EAAE,gBAAgB;EAChCC,WAAW,EAAE,oBAAoB;EACjCC,IAAI,EAAE,QAAQ;EACdC,IAAI,EAAE,UAAU;EAChBC,KAAK,EAAE,OAAO;EACdC,KAAK,EAAE,gBAAgB;EACvBC,eAAe,EAAE,8CAA8C;EAC/DC,UAAU,EAAE,qBAAqB;EACjCC,eAAe,EAAE,4BAA4B;EAC7CC,yBAAyB,EAAE,mDAAmD;EAC9EC,aAAa,EAAE,aAAa;EAC5B;EACAC,WAAW,EAAE,MAAM;EACnBC,WAAW,EAAE,WAAW;EACxBC,SAAS,EAAE,WAAW;EACtBC,QAAQ,EAAE,WAAW;EACrBC,UAAU,EAAE,aAAa;EACzBC,SAAS,EAAE,aAAa;EACxBC,KAAK,EAAE,OAAO;EACdC,SAAS,EAAE,KAAK;EAChBC,mBAAmB,EAAE,mBAAmB;EACxCC,aAAa,EAAE,gBAAgB;EAC/BC,aAAa,EAAE,YAAY;EAC3BC,aAAa,EAAE,eAAe;EAC9BC,YAAY,EAAE,+BAA+B;EAC7CC,OAAO,EAAE,eAAe;EACxBC,aAAa,EAAE,YAAY;EAC3BC,WAAW,EAAE,aAAa;EAC1BC,OAAO,EAAE,OAAO;EAChBC,YAAY,EAAE,MAAM;EACpBC,YAAY,EAAE,MAAM;EACpBC,eAAe,EAAE,YAAY;EAC7BC,iBAAiB,EAAE,QAAQ;EAC3BC,eAAe,EAAE;AACnB,CAAC;AAED,OAAO,MAAMC,oBAA8C,GAAG;
|
|
1
|
+
{"version":3,"names":["ar","back","loading","loadEarlier","emptyConversation","messagePlaceholder","attachFile","attachPhotoLibrary","attachTakePhoto","attachChooseFile","removeAttachment","attachmentTooLarge","attachmentTooMany","attachmentsTooLargeTogether","attachmentFailed","imageLoadFailed","openImageExternally","cameraPermissionDenied","cancel","failedToSend","thinking","messages","noMessages","historyHidden","startBelow","preChatTitle","preChatSubtitle","preChatSkip","preChatSubmit","viewPreviousConversations","homeRecentTitle","homeViewAllCount","homeNoConversations","homeStatusOnline","homeStatusAway","unreadBadge","team","home","close","retry","connectionError","connecting","chatUnavailable","identityVerificationError","widgetCrashed","timeJustNow","timeMinutes","timeHours","timeDays","timeMonths","timeYears","today","yesterday","conversationUpdated","messageDeleted","messageSent","messageSeen","waitingForTeammate","joinedConversation","procedureSend","procedureSent","fieldRequired","invalidEmail","working","outboundTitle","outboundCta","dismiss","notificationReplyFrom","notificationNewReply","notificationAttachment","notificationOpenHint","blockService","blockProduct","blockChooseTime","blockProfessional","blockPriceOffer","BUNDLED_TRANSLATIONS","baseLanguage","locale","split","toLowerCase"],"sourceRoot":"../../src","sources":["locales.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -17,9 +17,10 @@ import { getDeviceId, rotateDeviceId } from "../device-id.js";
|
|
|
17
17
|
import { EventEmitter, RepzoWidgetEvents } from "../events.js";
|
|
18
18
|
import { PushManager } from "../push/push-notifications.js";
|
|
19
19
|
import { CONFIG_KEY, LEGACY_CONFIG_KEY, SecureStorage, Storage, UNREAD_KEY, USER_KEY } from "../storage.js";
|
|
20
|
+
import { setAttachmentOptions } from "../attachments/pickers.js";
|
|
20
21
|
import { DEFAULT_BASE_URL } from "../types.js";
|
|
21
22
|
import { setOpenUrlHandler } from "../ui/utils/safe-url.js";
|
|
22
|
-
const SDK_VERSION = '@repzo/widget-react-native@1.0.
|
|
23
|
+
const SDK_VERSION = '@repzo/widget-react-native@1.0.4';
|
|
23
24
|
|
|
24
25
|
/** The platform this session reports — mobile sessions get the Mobile SDKs overrides. */
|
|
25
26
|
function sessionPlatform() {
|
|
@@ -67,7 +68,8 @@ const INITIAL_STATE = {
|
|
|
67
68
|
locale: 'en',
|
|
68
69
|
prefillText: '',
|
|
69
70
|
outboundMessage: null,
|
|
70
|
-
visitorProfile: {}
|
|
71
|
+
visitorProfile: {},
|
|
72
|
+
inAppNotification: null
|
|
71
73
|
};
|
|
72
74
|
|
|
73
75
|
/** Everything in state that belongs to the signed-in visitor. */
|
|
@@ -86,7 +88,8 @@ const USER_SCOPED_STATE = {
|
|
|
86
88
|
unreadByConversation: {},
|
|
87
89
|
prefillText: '',
|
|
88
90
|
outboundMessage: null,
|
|
89
|
-
visitorProfile: {}
|
|
91
|
+
visitorProfile: {},
|
|
92
|
+
inAppNotification: null
|
|
90
93
|
};
|
|
91
94
|
export class WidgetStore {
|
|
92
95
|
client = new WidgetApiClient();
|
|
@@ -100,6 +103,12 @@ export class WidgetStore {
|
|
|
100
103
|
typingTimer = null;
|
|
101
104
|
pendingAgentByConversation = {};
|
|
102
105
|
bootConfig = null;
|
|
106
|
+
|
|
107
|
+
/** `boot({ inAppNotifications })` — the card over the app for new replies. */
|
|
108
|
+
inAppNotificationsEnabled = true;
|
|
109
|
+
|
|
110
|
+
/** The launch card (see showUnreadOnLaunch) is offered once per visitor session. */
|
|
111
|
+
launchCardOffered = false;
|
|
103
112
|
bootPromise = null;
|
|
104
113
|
|
|
105
114
|
/** Identity key of the in-flight boot — a boot for someone else is not deduped. */
|
|
@@ -116,6 +125,8 @@ export class WidgetStore {
|
|
|
116
125
|
lastTypingPingAt = 0;
|
|
117
126
|
visitorTypingActive = false;
|
|
118
127
|
seenMessageIds = [];
|
|
128
|
+
/** Statuses (e.g. "read") that arrived for an id before a send response revealed it. */
|
|
129
|
+
earlyStatuses = new Map();
|
|
119
130
|
|
|
120
131
|
/**
|
|
121
132
|
* Identity generation. Bumped whenever boot/login/logout is requested; every
|
|
@@ -184,6 +195,9 @@ export class WidgetStore {
|
|
|
184
195
|
};
|
|
185
196
|
};
|
|
186
197
|
getState = () => this.state;
|
|
198
|
+
|
|
199
|
+
/** Composer identity: explicit opens reset it; a draft receiving its server ID does not. */
|
|
200
|
+
getThreadGeneration = () => this.threadGeneration;
|
|
187
201
|
setState(patch) {
|
|
188
202
|
const previous = this.state;
|
|
189
203
|
const resolved = typeof patch === 'function' ? patch(previous) : patch;
|
|
@@ -194,6 +208,13 @@ export class WidgetStore {
|
|
|
194
208
|
// The total is always derived from the per-thread map, so no surface can
|
|
195
209
|
// zero the badge while threads stay unread.
|
|
196
210
|
next.unreadCount = sumUnread(next.unreadByConversation);
|
|
211
|
+
// The in-app card only lives while the messenger is closed and its thread
|
|
212
|
+
// is unread — opening the widget, reading the thread (here or on another
|
|
213
|
+
// device) or logging out all take it down without each path clearing it.
|
|
214
|
+
const card = next.inAppNotification;
|
|
215
|
+
if (card && (next.isOpen || !next.unreadByConversation[card.conversationId])) {
|
|
216
|
+
next.inAppNotification = null;
|
|
217
|
+
}
|
|
197
218
|
this.state = next;
|
|
198
219
|
if (next.unreadCount !== previous.unreadCount) {
|
|
199
220
|
this.events.emit(RepzoWidgetEvents.UnreadCountDidChange, {
|
|
@@ -237,6 +258,10 @@ export class WidgetStore {
|
|
|
237
258
|
}
|
|
238
259
|
const baseUrl = assertSecureBaseUrl(options.baseUrl?.trim() || DEFAULT_BASE_URL);
|
|
239
260
|
if (options.onOpenUrl !== undefined) setOpenUrlHandler(options.onOpenUrl ?? null);
|
|
261
|
+
if (options.attachments !== undefined) setAttachmentOptions(options.attachments ?? null);
|
|
262
|
+
if (options.inAppNotifications !== undefined) {
|
|
263
|
+
this.inAppNotificationsEnabled = options.inAppNotifications !== false;
|
|
264
|
+
}
|
|
240
265
|
const sameTarget = this.bootConfig?.baseUrl === baseUrl && this.bootConfig?.channelAccountId === options.channelAccountId;
|
|
241
266
|
const userKey = options.user ? identityKey(options.user) : null;
|
|
242
267
|
if (sameTarget && this.bootPromise && (!userKey || userKey === this.bootingUserKey)) {
|
|
@@ -441,8 +466,12 @@ export class WidgetStore {
|
|
|
441
466
|
if (this.pendingConversationId) {
|
|
442
467
|
const conversationId = this.pendingConversationId;
|
|
443
468
|
this.pendingConversationId = null;
|
|
469
|
+
// Launched into a thread (push tap): that is this session's launch, so
|
|
470
|
+
// a later silent re-init must not offer the card either.
|
|
471
|
+
this.launchCardOffered = true;
|
|
444
472
|
void this.openConversation(conversationId);
|
|
445
473
|
} else {
|
|
474
|
+
this.showUnreadOnLaunch();
|
|
446
475
|
// A re-init after a 401 means the stream was down: catch the open
|
|
447
476
|
// thread up (a no-op when nothing is on screen).
|
|
448
477
|
void this.refreshOpenThread();
|
|
@@ -656,6 +685,7 @@ export class WidgetStore {
|
|
|
656
685
|
this.seenMessageIds = [];
|
|
657
686
|
this.visitorTypingActive = false;
|
|
658
687
|
this.lastTypingPingAt = 0;
|
|
688
|
+
this.launchCardOffered = false;
|
|
659
689
|
this.setState({
|
|
660
690
|
...USER_SCOPED_STATE
|
|
661
691
|
});
|
|
@@ -794,7 +824,7 @@ export class WidgetStore {
|
|
|
794
824
|
}
|
|
795
825
|
}
|
|
796
826
|
handleRealtimeEvent(event, data) {
|
|
797
|
-
if ((event === 'message.new' || event === 'conversation.event') && this.shouldHoldForDraft(data?.conversationId)) {
|
|
827
|
+
if ((event === 'message.new' || event === 'message.deleted' || event === 'message.status' || event === 'conversation.event') && this.shouldHoldForDraft(data?.conversationId)) {
|
|
798
828
|
this.draftEvents.push({
|
|
799
829
|
event,
|
|
800
830
|
data
|
|
@@ -818,13 +848,20 @@ export class WidgetStore {
|
|
|
818
848
|
case 'message.new':
|
|
819
849
|
this.handleNewMessage(data);
|
|
820
850
|
break;
|
|
851
|
+
case 'message.deleted':
|
|
852
|
+
this.handleDeletedMessage(data);
|
|
853
|
+
break;
|
|
854
|
+
case 'message.status':
|
|
855
|
+
this.handleMessageStatus(data);
|
|
856
|
+
break;
|
|
821
857
|
case 'conversation.updated':
|
|
822
858
|
this.rememberConversationAgent(data);
|
|
823
859
|
this.setState(current => ({
|
|
824
860
|
conversations: current.conversations.map(conversation => conversation.id === data?.conversationId ? {
|
|
825
861
|
...conversation,
|
|
826
862
|
agentName: data.agentName === undefined ? conversation.agentName : data.agentName,
|
|
827
|
-
agentAvatar: data.agentAvatar === undefined ? conversation.agentAvatar : this.client.resolveUrl(data.agentAvatar)
|
|
863
|
+
agentAvatar: data.agentAvatar === undefined ? conversation.agentAvatar : this.client.resolveUrl(data.agentAvatar),
|
|
864
|
+
agentOnline: data.agentOnline === undefined ? conversation.agentOnline : data.agentOnline
|
|
828
865
|
} : conversation)
|
|
829
866
|
}));
|
|
830
867
|
break;
|
|
@@ -922,6 +959,50 @@ export class WidgetStore {
|
|
|
922
959
|
if (this.seenMessageIds.length > SEEN_MESSAGE_LIMIT) this.seenMessageIds.shift();
|
|
923
960
|
return false;
|
|
924
961
|
}
|
|
962
|
+
|
|
963
|
+
/**
|
|
964
|
+
* A teammate deleted a message: the visitor sees "This message was deleted"
|
|
965
|
+
* in its place (they can't delete anything themselves), and the list is
|
|
966
|
+
* refreshed so its preview stops showing the text. The id stays in
|
|
967
|
+
* `seenMessageIds`, so a replayed `message.new` can't restore the content.
|
|
968
|
+
*/
|
|
969
|
+
/**
|
|
970
|
+
* A teammate opened the conversation: the visitor's messages are now Seen.
|
|
971
|
+
* The ids are also remembered, because the event can arrive before the send
|
|
972
|
+
* response swaps a pending bubble's temporary id for the server id.
|
|
973
|
+
*/
|
|
974
|
+
handleMessageStatus(data) {
|
|
975
|
+
const ids = Array.isArray(data?.messageIds) ? data.messageIds : [];
|
|
976
|
+
const status = typeof data?.status === 'string' ? data.status : undefined;
|
|
977
|
+
if (!status || ids.length === 0) return;
|
|
978
|
+
for (const id of ids) this.earlyStatuses.set(id, status);
|
|
979
|
+
if (this.earlyStatuses.size > SEEN_MESSAGE_LIMIT) {
|
|
980
|
+
this.earlyStatuses.delete(this.earlyStatuses.keys().next().value);
|
|
981
|
+
}
|
|
982
|
+
if (!this.isThreadOnScreen(data?.conversationId)) return;
|
|
983
|
+
this.setState(current => ({
|
|
984
|
+
messages: current.messages.map(message => ids.includes(message.id) ? {
|
|
985
|
+
...message,
|
|
986
|
+
status
|
|
987
|
+
} : message)
|
|
988
|
+
}));
|
|
989
|
+
}
|
|
990
|
+
handleDeletedMessage(data) {
|
|
991
|
+
const messageId = data?.messageId;
|
|
992
|
+
if (!messageId) return;
|
|
993
|
+
if (!this.seenMessageIds.includes(messageId)) this.isReplayedMessage(messageId);
|
|
994
|
+
if (this.isThreadOnScreen(data?.conversationId)) {
|
|
995
|
+
this.setState(current => ({
|
|
996
|
+
messages: current.messages.map(message => message.id === messageId ? asDeletedPlaceholder(message, new Date().toISOString()) : message)
|
|
997
|
+
}));
|
|
998
|
+
}
|
|
999
|
+
if (this.state.inAppNotification?.messageId === messageId) {
|
|
1000
|
+
this.setState({
|
|
1001
|
+
inAppNotification: null
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
void this.refreshConversations();
|
|
1005
|
+
}
|
|
925
1006
|
handleNewMessage(data) {
|
|
926
1007
|
if (this.isReplayedMessage(data?.messageId)) return;
|
|
927
1008
|
|
|
@@ -937,7 +1018,7 @@ export class WidgetStore {
|
|
|
937
1018
|
body: data?.body || null,
|
|
938
1019
|
bodyPlain: data?.bodyPlain || null,
|
|
939
1020
|
attachments: this.hydrateAttachments(data?.attachments, messageId),
|
|
940
|
-
metadata: data?.metadata || null,
|
|
1021
|
+
metadata: this.hydrateMetadata(data?.metadata || null),
|
|
941
1022
|
status: 'delivered',
|
|
942
1023
|
createdAt: data?.createdAt || new Date().toISOString()
|
|
943
1024
|
};
|
|
@@ -962,7 +1043,7 @@ export class WidgetStore {
|
|
|
962
1043
|
const alreadyPresent = current.messages.some(existing => existing.id === message.id);
|
|
963
1044
|
return {
|
|
964
1045
|
conversations,
|
|
965
|
-
messages: alreadyPresent ? current.messages : [...current.messages, message],
|
|
1046
|
+
messages: alreadyPresent ? adoptServerAttachments(current.messages, [message]) : [...current.messages, message],
|
|
966
1047
|
agentTyping: clearsTyping ? false : current.agentTyping
|
|
967
1048
|
};
|
|
968
1049
|
}
|
|
@@ -975,9 +1056,25 @@ export class WidgetStore {
|
|
|
975
1056
|
};
|
|
976
1057
|
this.persistUnread(unreadByConversation);
|
|
977
1058
|
countedAsUnread = true;
|
|
1059
|
+
if (!this.inAppNotificationsEnabled || current.isOpen) {
|
|
1060
|
+
return {
|
|
1061
|
+
conversations,
|
|
1062
|
+
unreadByConversation
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
// Newest reply wins: one card, not a stack, like the launcher badge.
|
|
1066
|
+
const conversation = conversations.find(item => item.id === conversationId);
|
|
978
1067
|
return {
|
|
979
1068
|
conversations,
|
|
980
|
-
unreadByConversation
|
|
1069
|
+
unreadByConversation,
|
|
1070
|
+
inAppNotification: {
|
|
1071
|
+
conversationId,
|
|
1072
|
+
messageId,
|
|
1073
|
+
senderName: message.senderName || conversation?.agentName || null,
|
|
1074
|
+
senderAvatar: conversation?.agentAvatar ?? null,
|
|
1075
|
+
preview: (message.bodyPlain ?? '').trim(),
|
|
1076
|
+
hasAttachments: (message.attachments?.length ?? 0) > 0
|
|
1077
|
+
}
|
|
981
1078
|
};
|
|
982
1079
|
});
|
|
983
1080
|
|
|
@@ -1215,6 +1312,44 @@ export class WidgetStore {
|
|
|
1215
1312
|
prefillText: prefill
|
|
1216
1313
|
});
|
|
1217
1314
|
}
|
|
1315
|
+
|
|
1316
|
+
/**
|
|
1317
|
+
* Replies that arrived while the app was closed get the same card a live
|
|
1318
|
+
* reply would, once per launch — so a visitor without push (not set up on
|
|
1319
|
+
* the channel, or permission denied) still sees them. Only the newest unread
|
|
1320
|
+
* thread; silent re-inits and foreground returns never re-offer it.
|
|
1321
|
+
*/
|
|
1322
|
+
showUnreadOnLaunch() {
|
|
1323
|
+
if (this.launchCardOffered || !this.inAppNotificationsEnabled) return;
|
|
1324
|
+
this.launchCardOffered = true;
|
|
1325
|
+
const {
|
|
1326
|
+
isOpen,
|
|
1327
|
+
inAppNotification,
|
|
1328
|
+
conversations,
|
|
1329
|
+
unreadByConversation
|
|
1330
|
+
} = this.state;
|
|
1331
|
+
if (isOpen || inAppNotification) return;
|
|
1332
|
+
const time = conversation => Date.parse(conversation.lastMessageAt ?? conversation.createdAt) || 0;
|
|
1333
|
+
const newest = conversations.filter(conversation => (unreadByConversation[conversation.id] ?? 0) > 0).sort((a, b) => time(b) - time(a))[0];
|
|
1334
|
+
if (!newest) return;
|
|
1335
|
+
this.setState({
|
|
1336
|
+
inAppNotification: {
|
|
1337
|
+
conversationId: newest.id,
|
|
1338
|
+
messageId: `launch-${newest.id}`,
|
|
1339
|
+
senderName: newest.agentName,
|
|
1340
|
+
senderAvatar: newest.agentAvatar,
|
|
1341
|
+
preview: (newest.snippet ?? '').trim(),
|
|
1342
|
+
hasAttachments: false
|
|
1343
|
+
}
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
/** The visitor swiped the in-app card away; the thread stays unread. */
|
|
1348
|
+
dismissInAppNotification() {
|
|
1349
|
+
if (this.state.inAppNotification) this.setState({
|
|
1350
|
+
inAppNotification: null
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1218
1353
|
dismissOutboundMessage() {
|
|
1219
1354
|
const outbound = this.state.outboundMessage;
|
|
1220
1355
|
if (outbound?.id) this.track('widget_outbound_dismissed', {
|
|
@@ -1294,9 +1429,10 @@ export class WidgetStore {
|
|
|
1294
1429
|
// Live replies and optimistic sends may have arrived since this page
|
|
1295
1430
|
// was requested. Keep them, preferring the live copy for shared ids.
|
|
1296
1431
|
const liveIds = new Set(current.messages.map(message => message.id));
|
|
1297
|
-
const
|
|
1432
|
+
const page = this.hydrateMessages(result.data);
|
|
1433
|
+
const history = page.filter(message => !liveIds.has(message.id));
|
|
1298
1434
|
return {
|
|
1299
|
-
messages: sortByCreatedAt([...history, ...current.messages]),
|
|
1435
|
+
messages: sortByCreatedAt([...history, ...adoptServerAttachments(current.messages, page)]),
|
|
1300
1436
|
messagesLoading: false,
|
|
1301
1437
|
hasMoreMessages: result.meta.hasMore,
|
|
1302
1438
|
messageCursor: result.meta.nextCursor
|
|
@@ -1366,7 +1502,10 @@ export class WidgetStore {
|
|
|
1366
1502
|
this.setState(current => {
|
|
1367
1503
|
const known = new Set(current.messages.map(message => message.id));
|
|
1368
1504
|
const missing = fresh.filter(message => !known.has(message.id));
|
|
1369
|
-
|
|
1505
|
+
const upgraded = adoptServerStatuses(adoptServerDeletions(adoptServerAttachments(current.messages, fresh), fresh), fresh);
|
|
1506
|
+
if (missing.length === 0) return upgraded === current.messages ? {} : {
|
|
1507
|
+
messages: upgraded
|
|
1508
|
+
};
|
|
1370
1509
|
const local = current.messages.filter(isLocalMessage);
|
|
1371
1510
|
const hasServerMessages = local.length < current.messages.length;
|
|
1372
1511
|
// No overlap means the gap is longer than a page: restart from the
|
|
@@ -1379,7 +1518,7 @@ export class WidgetStore {
|
|
|
1379
1518
|
};
|
|
1380
1519
|
}
|
|
1381
1520
|
return {
|
|
1382
|
-
messages: sortByCreatedAt([...
|
|
1521
|
+
messages: sortByCreatedAt([...upgraded, ...missing])
|
|
1383
1522
|
};
|
|
1384
1523
|
});
|
|
1385
1524
|
// The server just moved the read cursor — mirror it locally.
|
|
@@ -1397,9 +1536,26 @@ export class WidgetStore {
|
|
|
1397
1536
|
hydrateMessages(page) {
|
|
1398
1537
|
return page.map(message => ({
|
|
1399
1538
|
...message,
|
|
1400
|
-
attachments: this.hydrateAttachments(message.attachments, message.id)
|
|
1539
|
+
attachments: this.hydrateAttachments(message.attachments, message.id),
|
|
1540
|
+
metadata: this.hydrateMetadata(message.metadata)
|
|
1401
1541
|
})).reverse();
|
|
1402
1542
|
}
|
|
1543
|
+
|
|
1544
|
+
/** The "joined" line's avatar can be a server-relative media path. */
|
|
1545
|
+
hydrateMetadata(metadata) {
|
|
1546
|
+
if (!metadata || typeof metadata.agentAvatar !== 'string') return metadata ?? null;
|
|
1547
|
+
return {
|
|
1548
|
+
...metadata,
|
|
1549
|
+
agentAvatar: this.client.resolveUrl(metadata.agentAvatar)
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
/**
|
|
1554
|
+
* Rejects when the message wasn't sent, so the composer can keep
|
|
1555
|
+
* attachments for a retry. A text-only bubble is marked failed; one with
|
|
1556
|
+
* attachments is removed instead — the composer's chips are the retry, and
|
|
1557
|
+
* keeping both would leave a duplicate after a successful resend.
|
|
1558
|
+
*/
|
|
1403
1559
|
async sendMessage(body, attachments = [], agentUiAction) {
|
|
1404
1560
|
const trimmed = body.trim();
|
|
1405
1561
|
if (!trimmed && attachments.length === 0) return;
|
|
@@ -1416,7 +1572,16 @@ export class WidgetStore {
|
|
|
1416
1572
|
senderName: visitorProfile.name || 'You',
|
|
1417
1573
|
body: trimmed ? `<p>${trimmed}</p>` : '',
|
|
1418
1574
|
bodyPlain: trimmed,
|
|
1419
|
-
|
|
1575
|
+
// The local preview, never a data URL: the bubble shows up at once and
|
|
1576
|
+
// state never holds megabytes of base64. Without a local file the
|
|
1577
|
+
// bubble shows the name until the server copy replaces it.
|
|
1578
|
+
attachments: attachments.length > 0 ? attachments.map((attachment, index) => ({
|
|
1579
|
+
id: `${tempId}-${index}`,
|
|
1580
|
+
name: attachment.name,
|
|
1581
|
+
url: attachment.previewUrl && !/^data:/i.test(attachment.previewUrl) ? attachment.previewUrl : '',
|
|
1582
|
+
contentType: attachment.contentType,
|
|
1583
|
+
size: attachment.size
|
|
1584
|
+
})) : null,
|
|
1420
1585
|
metadata: null,
|
|
1421
1586
|
status: 'pending',
|
|
1422
1587
|
createdAt: new Date().toISOString()
|
|
@@ -1430,7 +1595,7 @@ export class WidgetStore {
|
|
|
1430
1595
|
attachmentCount: attachments.length
|
|
1431
1596
|
});
|
|
1432
1597
|
const markFailed = () => this.setState(current => ({
|
|
1433
|
-
messages: current.messages.map(message => message.id === tempId ? {
|
|
1598
|
+
messages: attachments.length > 0 ? current.messages.filter(message => message.id !== tempId) : current.messages.map(message => message.id === tempId ? {
|
|
1434
1599
|
...message,
|
|
1435
1600
|
status: 'failed'
|
|
1436
1601
|
} : message)
|
|
@@ -1455,7 +1620,7 @@ export class WidgetStore {
|
|
|
1455
1620
|
// don't open a thread nobody is looking at.
|
|
1456
1621
|
if (!conversationId && this.threadGeneration !== generation) {
|
|
1457
1622
|
markFailed();
|
|
1458
|
-
|
|
1623
|
+
throw new Error('The conversation this message was queued for was never created');
|
|
1459
1624
|
}
|
|
1460
1625
|
}
|
|
1461
1626
|
let createdId = null;
|
|
@@ -1464,7 +1629,17 @@ export class WidgetStore {
|
|
|
1464
1629
|
conversationId: conversationId || undefined,
|
|
1465
1630
|
forceNewConversation: !conversationId,
|
|
1466
1631
|
body: trimmed,
|
|
1467
|
-
attachments: attachments.length > 0 ? attachments
|
|
1632
|
+
attachments: attachments.length > 0 ? attachments.map(({
|
|
1633
|
+
name,
|
|
1634
|
+
url,
|
|
1635
|
+
contentType,
|
|
1636
|
+
size
|
|
1637
|
+
}) => ({
|
|
1638
|
+
name,
|
|
1639
|
+
url,
|
|
1640
|
+
contentType,
|
|
1641
|
+
size
|
|
1642
|
+
})) : undefined,
|
|
1468
1643
|
visitorName: visitorProfile.name,
|
|
1469
1644
|
visitorEmail: visitorProfile.email,
|
|
1470
1645
|
agentUiAction
|
|
@@ -1478,7 +1653,7 @@ export class WidgetStore {
|
|
|
1478
1653
|
const messages = delivered ? current.messages.filter(message => message.id !== tempId) : current.messages.map(message => message.id === tempId ? {
|
|
1479
1654
|
...message,
|
|
1480
1655
|
id: result.messageId,
|
|
1481
|
-
status: 'sent'
|
|
1656
|
+
status: this.earlyStatuses.get(result.messageId) ?? 'sent'
|
|
1482
1657
|
} : message);
|
|
1483
1658
|
if (!createdId) return {
|
|
1484
1659
|
messages
|
|
@@ -1612,6 +1787,69 @@ function isLocalMessage(message) {
|
|
|
1612
1787
|
return message.id.startsWith('temp-');
|
|
1613
1788
|
}
|
|
1614
1789
|
|
|
1790
|
+
/**
|
|
1791
|
+
* A sent visitor message keeps its local previews (a `file://` URI, or no URL
|
|
1792
|
+
* at all) until the server copy shows up — the stream echo or a history page.
|
|
1793
|
+
* Its attachments, the only ones that can be opened, then replace them.
|
|
1794
|
+
*/
|
|
1795
|
+
/** A "Seen" missed while the stream was down shows up on the next re-read. */
|
|
1796
|
+
function adoptServerStatuses(messages, server) {
|
|
1797
|
+
const seen = new Set(server.filter(message => message.status === 'read').map(message => message.id));
|
|
1798
|
+
if (seen.size === 0) return messages;
|
|
1799
|
+
let changed = false;
|
|
1800
|
+
const next = messages.map(message => {
|
|
1801
|
+
if (!seen.has(message.id) || message.status === 'read') return message;
|
|
1802
|
+
changed = true;
|
|
1803
|
+
return {
|
|
1804
|
+
...message,
|
|
1805
|
+
status: 'read'
|
|
1806
|
+
};
|
|
1807
|
+
});
|
|
1808
|
+
return changed ? next : messages;
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
/** Keeps who/when, drops everything the visitor could read. */
|
|
1812
|
+
function asDeletedPlaceholder(message, deletedAt) {
|
|
1813
|
+
return {
|
|
1814
|
+
...message,
|
|
1815
|
+
body: null,
|
|
1816
|
+
bodyPlain: null,
|
|
1817
|
+
attachments: null,
|
|
1818
|
+
metadata: null,
|
|
1819
|
+
deletedAt
|
|
1820
|
+
};
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
/** A deletion missed while the stream was down shows up on the next re-read. */
|
|
1824
|
+
function adoptServerDeletions(messages, server) {
|
|
1825
|
+
const deleted = new Map(server.filter(message => message.deletedAt).map(message => [message.id, message.deletedAt]));
|
|
1826
|
+
if (deleted.size === 0) return messages;
|
|
1827
|
+
let changed = false;
|
|
1828
|
+
const next = messages.map(message => {
|
|
1829
|
+
const deletedAt = deleted.get(message.id);
|
|
1830
|
+
if (!deletedAt || message.deletedAt) return message;
|
|
1831
|
+
changed = true;
|
|
1832
|
+
return asDeletedPlaceholder(message, deletedAt);
|
|
1833
|
+
});
|
|
1834
|
+
return changed ? next : messages;
|
|
1835
|
+
}
|
|
1836
|
+
function adoptServerAttachments(messages, server) {
|
|
1837
|
+
const serverCopies = new Map(server.filter(message => message.attachments?.length).map(message => [message.id, message]));
|
|
1838
|
+
if (serverCopies.size === 0) return messages;
|
|
1839
|
+
let changed = false;
|
|
1840
|
+
const next = messages.map(message => {
|
|
1841
|
+
const copy = serverCopies.get(message.id);
|
|
1842
|
+
const hasLocal = message.attachments?.some(attachment => !/^https?:\/\//i.test(attachment.url));
|
|
1843
|
+
if (!copy || !hasLocal) return message;
|
|
1844
|
+
changed = true;
|
|
1845
|
+
return {
|
|
1846
|
+
...message,
|
|
1847
|
+
attachments: copy.attachments
|
|
1848
|
+
};
|
|
1849
|
+
});
|
|
1850
|
+
return changed ? next : messages;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1615
1853
|
/** Stable, so same-instant rows keep their order; bad dates sort as oldest. */
|
|
1616
1854
|
function sortByCreatedAt(messages) {
|
|
1617
1855
|
const time = message => Date.parse(message.createdAt) || 0;
|