@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/commonjs/locales.js
CHANGED
|
@@ -24,6 +24,18 @@ const ar = {
|
|
|
24
24
|
emptyConversation: 'أرسل رسالة لبدء المحادثة',
|
|
25
25
|
messagePlaceholder: 'اكتب رسالة...',
|
|
26
26
|
attachFile: 'إرفاق ملف',
|
|
27
|
+
attachPhotoLibrary: 'مكتبة الصور',
|
|
28
|
+
attachTakePhoto: 'التقاط صورة',
|
|
29
|
+
attachChooseFile: 'ملف',
|
|
30
|
+
removeAttachment: 'إزالة {name}',
|
|
31
|
+
attachmentTooLarge: 'الملف كبير جدًا (الحد الأقصى {max})',
|
|
32
|
+
attachmentTooMany: 'يمكنك إرفاق {count} ملفات كحد أقصى',
|
|
33
|
+
attachmentsTooLargeTogether: 'المرفقات كبيرة جدًا لإرسالها معًا — أرسل بعضها في رسالة أخرى',
|
|
34
|
+
attachmentFailed: 'تعذر إرفاق هذا الملف',
|
|
35
|
+
imageLoadFailed: 'تعذر تحميل هذه الصورة',
|
|
36
|
+
openImageExternally: 'فتح الصورة',
|
|
37
|
+
cameraPermissionDenied: 'اسمح بالوصول إلى الكاميرا من الإعدادات لالتقاط صورة',
|
|
38
|
+
cancel: 'إلغاء',
|
|
27
39
|
failedToSend: 'تعذر الإرسال',
|
|
28
40
|
thinking: 'يكتب',
|
|
29
41
|
messages: 'الرسائل',
|
|
@@ -60,6 +72,11 @@ const ar = {
|
|
|
60
72
|
today: 'اليوم',
|
|
61
73
|
yesterday: 'أمس',
|
|
62
74
|
conversationUpdated: 'تم تحديث المحادثة',
|
|
75
|
+
messageDeleted: 'تم حذف هذه الرسالة',
|
|
76
|
+
messageSent: 'تم الإرسال',
|
|
77
|
+
messageSeen: 'تمت المشاهدة',
|
|
78
|
+
waitingForTeammate: 'بانتظار أحد أعضاء الفريق',
|
|
79
|
+
joinedConversation: 'انضم إلى المحادثة',
|
|
63
80
|
procedureSend: 'إرسال التفاصيل',
|
|
64
81
|
procedureSent: 'تم الإرسال',
|
|
65
82
|
fieldRequired: '{field} مطلوب',
|
|
@@ -68,6 +85,10 @@ const ar = {
|
|
|
68
85
|
outboundTitle: 'تحديث جديد',
|
|
69
86
|
outboundCta: 'اعرف المزيد',
|
|
70
87
|
dismiss: 'تجاهل',
|
|
88
|
+
notificationReplyFrom: 'رد من {name}',
|
|
89
|
+
notificationNewReply: 'رد جديد',
|
|
90
|
+
notificationAttachment: 'أرسل مرفقًا',
|
|
91
|
+
notificationOpenHint: 'يفتح المحادثة',
|
|
71
92
|
blockService: 'خدمة',
|
|
72
93
|
blockProduct: 'منتج',
|
|
73
94
|
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","exports","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;AAEM,MAAMC,oBAA8C,GAAAC,OAAA,CAAAD,oBAAA,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","exports","baseLanguage","locale","split","toLowerCase"],"sourceRoot":"../../src","sources":["locales.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -11,6 +11,7 @@ var _deviceId = require("../device-id.js");
|
|
|
11
11
|
var _events = require("../events.js");
|
|
12
12
|
var _pushNotifications = require("../push/push-notifications.js");
|
|
13
13
|
var _storage = require("../storage.js");
|
|
14
|
+
var _pickers = require("../attachments/pickers.js");
|
|
14
15
|
var _types = require("../types.js");
|
|
15
16
|
var _safeUrl = require("../ui/utils/safe-url.js");
|
|
16
17
|
/**
|
|
@@ -23,7 +24,7 @@ var _safeUrl = require("../ui/utils/safe-url.js");
|
|
|
23
24
|
* only a subscriber.
|
|
24
25
|
*/
|
|
25
26
|
|
|
26
|
-
const SDK_VERSION = '@repzo/widget-react-native@1.0.
|
|
27
|
+
const SDK_VERSION = '@repzo/widget-react-native@1.0.4';
|
|
27
28
|
|
|
28
29
|
/** The platform this session reports — mobile sessions get the Mobile SDKs overrides. */
|
|
29
30
|
function sessionPlatform() {
|
|
@@ -71,7 +72,8 @@ const INITIAL_STATE = {
|
|
|
71
72
|
locale: 'en',
|
|
72
73
|
prefillText: '',
|
|
73
74
|
outboundMessage: null,
|
|
74
|
-
visitorProfile: {}
|
|
75
|
+
visitorProfile: {},
|
|
76
|
+
inAppNotification: null
|
|
75
77
|
};
|
|
76
78
|
|
|
77
79
|
/** Everything in state that belongs to the signed-in visitor. */
|
|
@@ -90,7 +92,8 @@ const USER_SCOPED_STATE = {
|
|
|
90
92
|
unreadByConversation: {},
|
|
91
93
|
prefillText: '',
|
|
92
94
|
outboundMessage: null,
|
|
93
|
-
visitorProfile: {}
|
|
95
|
+
visitorProfile: {},
|
|
96
|
+
inAppNotification: null
|
|
94
97
|
};
|
|
95
98
|
class WidgetStore {
|
|
96
99
|
client = new _client.WidgetApiClient();
|
|
@@ -104,6 +107,12 @@ class WidgetStore {
|
|
|
104
107
|
typingTimer = null;
|
|
105
108
|
pendingAgentByConversation = {};
|
|
106
109
|
bootConfig = null;
|
|
110
|
+
|
|
111
|
+
/** `boot({ inAppNotifications })` — the card over the app for new replies. */
|
|
112
|
+
inAppNotificationsEnabled = true;
|
|
113
|
+
|
|
114
|
+
/** The launch card (see showUnreadOnLaunch) is offered once per visitor session. */
|
|
115
|
+
launchCardOffered = false;
|
|
107
116
|
bootPromise = null;
|
|
108
117
|
|
|
109
118
|
/** Identity key of the in-flight boot — a boot for someone else is not deduped. */
|
|
@@ -120,6 +129,8 @@ class WidgetStore {
|
|
|
120
129
|
lastTypingPingAt = 0;
|
|
121
130
|
visitorTypingActive = false;
|
|
122
131
|
seenMessageIds = [];
|
|
132
|
+
/** Statuses (e.g. "read") that arrived for an id before a send response revealed it. */
|
|
133
|
+
earlyStatuses = new Map();
|
|
123
134
|
|
|
124
135
|
/**
|
|
125
136
|
* Identity generation. Bumped whenever boot/login/logout is requested; every
|
|
@@ -188,6 +199,9 @@ class WidgetStore {
|
|
|
188
199
|
};
|
|
189
200
|
};
|
|
190
201
|
getState = () => this.state;
|
|
202
|
+
|
|
203
|
+
/** Composer identity: explicit opens reset it; a draft receiving its server ID does not. */
|
|
204
|
+
getThreadGeneration = () => this.threadGeneration;
|
|
191
205
|
setState(patch) {
|
|
192
206
|
const previous = this.state;
|
|
193
207
|
const resolved = typeof patch === 'function' ? patch(previous) : patch;
|
|
@@ -198,6 +212,13 @@ class WidgetStore {
|
|
|
198
212
|
// The total is always derived from the per-thread map, so no surface can
|
|
199
213
|
// zero the badge while threads stay unread.
|
|
200
214
|
next.unreadCount = sumUnread(next.unreadByConversation);
|
|
215
|
+
// The in-app card only lives while the messenger is closed and its thread
|
|
216
|
+
// is unread — opening the widget, reading the thread (here or on another
|
|
217
|
+
// device) or logging out all take it down without each path clearing it.
|
|
218
|
+
const card = next.inAppNotification;
|
|
219
|
+
if (card && (next.isOpen || !next.unreadByConversation[card.conversationId])) {
|
|
220
|
+
next.inAppNotification = null;
|
|
221
|
+
}
|
|
201
222
|
this.state = next;
|
|
202
223
|
if (next.unreadCount !== previous.unreadCount) {
|
|
203
224
|
this.events.emit(_events.RepzoWidgetEvents.UnreadCountDidChange, {
|
|
@@ -241,6 +262,10 @@ class WidgetStore {
|
|
|
241
262
|
}
|
|
242
263
|
const baseUrl = assertSecureBaseUrl(options.baseUrl?.trim() || _types.DEFAULT_BASE_URL);
|
|
243
264
|
if (options.onOpenUrl !== undefined) (0, _safeUrl.setOpenUrlHandler)(options.onOpenUrl ?? null);
|
|
265
|
+
if (options.attachments !== undefined) (0, _pickers.setAttachmentOptions)(options.attachments ?? null);
|
|
266
|
+
if (options.inAppNotifications !== undefined) {
|
|
267
|
+
this.inAppNotificationsEnabled = options.inAppNotifications !== false;
|
|
268
|
+
}
|
|
244
269
|
const sameTarget = this.bootConfig?.baseUrl === baseUrl && this.bootConfig?.channelAccountId === options.channelAccountId;
|
|
245
270
|
const userKey = options.user ? identityKey(options.user) : null;
|
|
246
271
|
if (sameTarget && this.bootPromise && (!userKey || userKey === this.bootingUserKey)) {
|
|
@@ -445,8 +470,12 @@ class WidgetStore {
|
|
|
445
470
|
if (this.pendingConversationId) {
|
|
446
471
|
const conversationId = this.pendingConversationId;
|
|
447
472
|
this.pendingConversationId = null;
|
|
473
|
+
// Launched into a thread (push tap): that is this session's launch, so
|
|
474
|
+
// a later silent re-init must not offer the card either.
|
|
475
|
+
this.launchCardOffered = true;
|
|
448
476
|
void this.openConversation(conversationId);
|
|
449
477
|
} else {
|
|
478
|
+
this.showUnreadOnLaunch();
|
|
450
479
|
// A re-init after a 401 means the stream was down: catch the open
|
|
451
480
|
// thread up (a no-op when nothing is on screen).
|
|
452
481
|
void this.refreshOpenThread();
|
|
@@ -660,6 +689,7 @@ class WidgetStore {
|
|
|
660
689
|
this.seenMessageIds = [];
|
|
661
690
|
this.visitorTypingActive = false;
|
|
662
691
|
this.lastTypingPingAt = 0;
|
|
692
|
+
this.launchCardOffered = false;
|
|
663
693
|
this.setState({
|
|
664
694
|
...USER_SCOPED_STATE
|
|
665
695
|
});
|
|
@@ -798,7 +828,7 @@ class WidgetStore {
|
|
|
798
828
|
}
|
|
799
829
|
}
|
|
800
830
|
handleRealtimeEvent(event, data) {
|
|
801
|
-
if ((event === 'message.new' || event === 'conversation.event') && this.shouldHoldForDraft(data?.conversationId)) {
|
|
831
|
+
if ((event === 'message.new' || event === 'message.deleted' || event === 'message.status' || event === 'conversation.event') && this.shouldHoldForDraft(data?.conversationId)) {
|
|
802
832
|
this.draftEvents.push({
|
|
803
833
|
event,
|
|
804
834
|
data
|
|
@@ -822,13 +852,20 @@ class WidgetStore {
|
|
|
822
852
|
case 'message.new':
|
|
823
853
|
this.handleNewMessage(data);
|
|
824
854
|
break;
|
|
855
|
+
case 'message.deleted':
|
|
856
|
+
this.handleDeletedMessage(data);
|
|
857
|
+
break;
|
|
858
|
+
case 'message.status':
|
|
859
|
+
this.handleMessageStatus(data);
|
|
860
|
+
break;
|
|
825
861
|
case 'conversation.updated':
|
|
826
862
|
this.rememberConversationAgent(data);
|
|
827
863
|
this.setState(current => ({
|
|
828
864
|
conversations: current.conversations.map(conversation => conversation.id === data?.conversationId ? {
|
|
829
865
|
...conversation,
|
|
830
866
|
agentName: data.agentName === undefined ? conversation.agentName : data.agentName,
|
|
831
|
-
agentAvatar: data.agentAvatar === undefined ? conversation.agentAvatar : this.client.resolveUrl(data.agentAvatar)
|
|
867
|
+
agentAvatar: data.agentAvatar === undefined ? conversation.agentAvatar : this.client.resolveUrl(data.agentAvatar),
|
|
868
|
+
agentOnline: data.agentOnline === undefined ? conversation.agentOnline : data.agentOnline
|
|
832
869
|
} : conversation)
|
|
833
870
|
}));
|
|
834
871
|
break;
|
|
@@ -926,6 +963,50 @@ class WidgetStore {
|
|
|
926
963
|
if (this.seenMessageIds.length > SEEN_MESSAGE_LIMIT) this.seenMessageIds.shift();
|
|
927
964
|
return false;
|
|
928
965
|
}
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* A teammate deleted a message: the visitor sees "This message was deleted"
|
|
969
|
+
* in its place (they can't delete anything themselves), and the list is
|
|
970
|
+
* refreshed so its preview stops showing the text. The id stays in
|
|
971
|
+
* `seenMessageIds`, so a replayed `message.new` can't restore the content.
|
|
972
|
+
*/
|
|
973
|
+
/**
|
|
974
|
+
* A teammate opened the conversation: the visitor's messages are now Seen.
|
|
975
|
+
* The ids are also remembered, because the event can arrive before the send
|
|
976
|
+
* response swaps a pending bubble's temporary id for the server id.
|
|
977
|
+
*/
|
|
978
|
+
handleMessageStatus(data) {
|
|
979
|
+
const ids = Array.isArray(data?.messageIds) ? data.messageIds : [];
|
|
980
|
+
const status = typeof data?.status === 'string' ? data.status : undefined;
|
|
981
|
+
if (!status || ids.length === 0) return;
|
|
982
|
+
for (const id of ids) this.earlyStatuses.set(id, status);
|
|
983
|
+
if (this.earlyStatuses.size > SEEN_MESSAGE_LIMIT) {
|
|
984
|
+
this.earlyStatuses.delete(this.earlyStatuses.keys().next().value);
|
|
985
|
+
}
|
|
986
|
+
if (!this.isThreadOnScreen(data?.conversationId)) return;
|
|
987
|
+
this.setState(current => ({
|
|
988
|
+
messages: current.messages.map(message => ids.includes(message.id) ? {
|
|
989
|
+
...message,
|
|
990
|
+
status
|
|
991
|
+
} : message)
|
|
992
|
+
}));
|
|
993
|
+
}
|
|
994
|
+
handleDeletedMessage(data) {
|
|
995
|
+
const messageId = data?.messageId;
|
|
996
|
+
if (!messageId) return;
|
|
997
|
+
if (!this.seenMessageIds.includes(messageId)) this.isReplayedMessage(messageId);
|
|
998
|
+
if (this.isThreadOnScreen(data?.conversationId)) {
|
|
999
|
+
this.setState(current => ({
|
|
1000
|
+
messages: current.messages.map(message => message.id === messageId ? asDeletedPlaceholder(message, new Date().toISOString()) : message)
|
|
1001
|
+
}));
|
|
1002
|
+
}
|
|
1003
|
+
if (this.state.inAppNotification?.messageId === messageId) {
|
|
1004
|
+
this.setState({
|
|
1005
|
+
inAppNotification: null
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
void this.refreshConversations();
|
|
1009
|
+
}
|
|
929
1010
|
handleNewMessage(data) {
|
|
930
1011
|
if (this.isReplayedMessage(data?.messageId)) return;
|
|
931
1012
|
|
|
@@ -941,7 +1022,7 @@ class WidgetStore {
|
|
|
941
1022
|
body: data?.body || null,
|
|
942
1023
|
bodyPlain: data?.bodyPlain || null,
|
|
943
1024
|
attachments: this.hydrateAttachments(data?.attachments, messageId),
|
|
944
|
-
metadata: data?.metadata || null,
|
|
1025
|
+
metadata: this.hydrateMetadata(data?.metadata || null),
|
|
945
1026
|
status: 'delivered',
|
|
946
1027
|
createdAt: data?.createdAt || new Date().toISOString()
|
|
947
1028
|
};
|
|
@@ -966,7 +1047,7 @@ class WidgetStore {
|
|
|
966
1047
|
const alreadyPresent = current.messages.some(existing => existing.id === message.id);
|
|
967
1048
|
return {
|
|
968
1049
|
conversations,
|
|
969
|
-
messages: alreadyPresent ? current.messages : [...current.messages, message],
|
|
1050
|
+
messages: alreadyPresent ? adoptServerAttachments(current.messages, [message]) : [...current.messages, message],
|
|
970
1051
|
agentTyping: clearsTyping ? false : current.agentTyping
|
|
971
1052
|
};
|
|
972
1053
|
}
|
|
@@ -979,9 +1060,25 @@ class WidgetStore {
|
|
|
979
1060
|
};
|
|
980
1061
|
this.persistUnread(unreadByConversation);
|
|
981
1062
|
countedAsUnread = true;
|
|
1063
|
+
if (!this.inAppNotificationsEnabled || current.isOpen) {
|
|
1064
|
+
return {
|
|
1065
|
+
conversations,
|
|
1066
|
+
unreadByConversation
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
// Newest reply wins: one card, not a stack, like the launcher badge.
|
|
1070
|
+
const conversation = conversations.find(item => item.id === conversationId);
|
|
982
1071
|
return {
|
|
983
1072
|
conversations,
|
|
984
|
-
unreadByConversation
|
|
1073
|
+
unreadByConversation,
|
|
1074
|
+
inAppNotification: {
|
|
1075
|
+
conversationId,
|
|
1076
|
+
messageId,
|
|
1077
|
+
senderName: message.senderName || conversation?.agentName || null,
|
|
1078
|
+
senderAvatar: conversation?.agentAvatar ?? null,
|
|
1079
|
+
preview: (message.bodyPlain ?? '').trim(),
|
|
1080
|
+
hasAttachments: (message.attachments?.length ?? 0) > 0
|
|
1081
|
+
}
|
|
985
1082
|
};
|
|
986
1083
|
});
|
|
987
1084
|
|
|
@@ -1219,6 +1316,44 @@ class WidgetStore {
|
|
|
1219
1316
|
prefillText: prefill
|
|
1220
1317
|
});
|
|
1221
1318
|
}
|
|
1319
|
+
|
|
1320
|
+
/**
|
|
1321
|
+
* Replies that arrived while the app was closed get the same card a live
|
|
1322
|
+
* reply would, once per launch — so a visitor without push (not set up on
|
|
1323
|
+
* the channel, or permission denied) still sees them. Only the newest unread
|
|
1324
|
+
* thread; silent re-inits and foreground returns never re-offer it.
|
|
1325
|
+
*/
|
|
1326
|
+
showUnreadOnLaunch() {
|
|
1327
|
+
if (this.launchCardOffered || !this.inAppNotificationsEnabled) return;
|
|
1328
|
+
this.launchCardOffered = true;
|
|
1329
|
+
const {
|
|
1330
|
+
isOpen,
|
|
1331
|
+
inAppNotification,
|
|
1332
|
+
conversations,
|
|
1333
|
+
unreadByConversation
|
|
1334
|
+
} = this.state;
|
|
1335
|
+
if (isOpen || inAppNotification) return;
|
|
1336
|
+
const time = conversation => Date.parse(conversation.lastMessageAt ?? conversation.createdAt) || 0;
|
|
1337
|
+
const newest = conversations.filter(conversation => (unreadByConversation[conversation.id] ?? 0) > 0).sort((a, b) => time(b) - time(a))[0];
|
|
1338
|
+
if (!newest) return;
|
|
1339
|
+
this.setState({
|
|
1340
|
+
inAppNotification: {
|
|
1341
|
+
conversationId: newest.id,
|
|
1342
|
+
messageId: `launch-${newest.id}`,
|
|
1343
|
+
senderName: newest.agentName,
|
|
1344
|
+
senderAvatar: newest.agentAvatar,
|
|
1345
|
+
preview: (newest.snippet ?? '').trim(),
|
|
1346
|
+
hasAttachments: false
|
|
1347
|
+
}
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
/** The visitor swiped the in-app card away; the thread stays unread. */
|
|
1352
|
+
dismissInAppNotification() {
|
|
1353
|
+
if (this.state.inAppNotification) this.setState({
|
|
1354
|
+
inAppNotification: null
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1222
1357
|
dismissOutboundMessage() {
|
|
1223
1358
|
const outbound = this.state.outboundMessage;
|
|
1224
1359
|
if (outbound?.id) this.track('widget_outbound_dismissed', {
|
|
@@ -1298,9 +1433,10 @@ class WidgetStore {
|
|
|
1298
1433
|
// Live replies and optimistic sends may have arrived since this page
|
|
1299
1434
|
// was requested. Keep them, preferring the live copy for shared ids.
|
|
1300
1435
|
const liveIds = new Set(current.messages.map(message => message.id));
|
|
1301
|
-
const
|
|
1436
|
+
const page = this.hydrateMessages(result.data);
|
|
1437
|
+
const history = page.filter(message => !liveIds.has(message.id));
|
|
1302
1438
|
return {
|
|
1303
|
-
messages: sortByCreatedAt([...history, ...current.messages]),
|
|
1439
|
+
messages: sortByCreatedAt([...history, ...adoptServerAttachments(current.messages, page)]),
|
|
1304
1440
|
messagesLoading: false,
|
|
1305
1441
|
hasMoreMessages: result.meta.hasMore,
|
|
1306
1442
|
messageCursor: result.meta.nextCursor
|
|
@@ -1370,7 +1506,10 @@ class WidgetStore {
|
|
|
1370
1506
|
this.setState(current => {
|
|
1371
1507
|
const known = new Set(current.messages.map(message => message.id));
|
|
1372
1508
|
const missing = fresh.filter(message => !known.has(message.id));
|
|
1373
|
-
|
|
1509
|
+
const upgraded = adoptServerStatuses(adoptServerDeletions(adoptServerAttachments(current.messages, fresh), fresh), fresh);
|
|
1510
|
+
if (missing.length === 0) return upgraded === current.messages ? {} : {
|
|
1511
|
+
messages: upgraded
|
|
1512
|
+
};
|
|
1374
1513
|
const local = current.messages.filter(isLocalMessage);
|
|
1375
1514
|
const hasServerMessages = local.length < current.messages.length;
|
|
1376
1515
|
// No overlap means the gap is longer than a page: restart from the
|
|
@@ -1383,7 +1522,7 @@ class WidgetStore {
|
|
|
1383
1522
|
};
|
|
1384
1523
|
}
|
|
1385
1524
|
return {
|
|
1386
|
-
messages: sortByCreatedAt([...
|
|
1525
|
+
messages: sortByCreatedAt([...upgraded, ...missing])
|
|
1387
1526
|
};
|
|
1388
1527
|
});
|
|
1389
1528
|
// The server just moved the read cursor — mirror it locally.
|
|
@@ -1401,9 +1540,26 @@ class WidgetStore {
|
|
|
1401
1540
|
hydrateMessages(page) {
|
|
1402
1541
|
return page.map(message => ({
|
|
1403
1542
|
...message,
|
|
1404
|
-
attachments: this.hydrateAttachments(message.attachments, message.id)
|
|
1543
|
+
attachments: this.hydrateAttachments(message.attachments, message.id),
|
|
1544
|
+
metadata: this.hydrateMetadata(message.metadata)
|
|
1405
1545
|
})).reverse();
|
|
1406
1546
|
}
|
|
1547
|
+
|
|
1548
|
+
/** The "joined" line's avatar can be a server-relative media path. */
|
|
1549
|
+
hydrateMetadata(metadata) {
|
|
1550
|
+
if (!metadata || typeof metadata.agentAvatar !== 'string') return metadata ?? null;
|
|
1551
|
+
return {
|
|
1552
|
+
...metadata,
|
|
1553
|
+
agentAvatar: this.client.resolveUrl(metadata.agentAvatar)
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
/**
|
|
1558
|
+
* Rejects when the message wasn't sent, so the composer can keep
|
|
1559
|
+
* attachments for a retry. A text-only bubble is marked failed; one with
|
|
1560
|
+
* attachments is removed instead — the composer's chips are the retry, and
|
|
1561
|
+
* keeping both would leave a duplicate after a successful resend.
|
|
1562
|
+
*/
|
|
1407
1563
|
async sendMessage(body, attachments = [], agentUiAction) {
|
|
1408
1564
|
const trimmed = body.trim();
|
|
1409
1565
|
if (!trimmed && attachments.length === 0) return;
|
|
@@ -1420,7 +1576,16 @@ class WidgetStore {
|
|
|
1420
1576
|
senderName: visitorProfile.name || 'You',
|
|
1421
1577
|
body: trimmed ? `<p>${trimmed}</p>` : '',
|
|
1422
1578
|
bodyPlain: trimmed,
|
|
1423
|
-
|
|
1579
|
+
// The local preview, never a data URL: the bubble shows up at once and
|
|
1580
|
+
// state never holds megabytes of base64. Without a local file the
|
|
1581
|
+
// bubble shows the name until the server copy replaces it.
|
|
1582
|
+
attachments: attachments.length > 0 ? attachments.map((attachment, index) => ({
|
|
1583
|
+
id: `${tempId}-${index}`,
|
|
1584
|
+
name: attachment.name,
|
|
1585
|
+
url: attachment.previewUrl && !/^data:/i.test(attachment.previewUrl) ? attachment.previewUrl : '',
|
|
1586
|
+
contentType: attachment.contentType,
|
|
1587
|
+
size: attachment.size
|
|
1588
|
+
})) : null,
|
|
1424
1589
|
metadata: null,
|
|
1425
1590
|
status: 'pending',
|
|
1426
1591
|
createdAt: new Date().toISOString()
|
|
@@ -1434,7 +1599,7 @@ class WidgetStore {
|
|
|
1434
1599
|
attachmentCount: attachments.length
|
|
1435
1600
|
});
|
|
1436
1601
|
const markFailed = () => this.setState(current => ({
|
|
1437
|
-
messages: current.messages.map(message => message.id === tempId ? {
|
|
1602
|
+
messages: attachments.length > 0 ? current.messages.filter(message => message.id !== tempId) : current.messages.map(message => message.id === tempId ? {
|
|
1438
1603
|
...message,
|
|
1439
1604
|
status: 'failed'
|
|
1440
1605
|
} : message)
|
|
@@ -1459,7 +1624,7 @@ class WidgetStore {
|
|
|
1459
1624
|
// don't open a thread nobody is looking at.
|
|
1460
1625
|
if (!conversationId && this.threadGeneration !== generation) {
|
|
1461
1626
|
markFailed();
|
|
1462
|
-
|
|
1627
|
+
throw new Error('The conversation this message was queued for was never created');
|
|
1463
1628
|
}
|
|
1464
1629
|
}
|
|
1465
1630
|
let createdId = null;
|
|
@@ -1468,7 +1633,17 @@ class WidgetStore {
|
|
|
1468
1633
|
conversationId: conversationId || undefined,
|
|
1469
1634
|
forceNewConversation: !conversationId,
|
|
1470
1635
|
body: trimmed,
|
|
1471
|
-
attachments: attachments.length > 0 ? attachments
|
|
1636
|
+
attachments: attachments.length > 0 ? attachments.map(({
|
|
1637
|
+
name,
|
|
1638
|
+
url,
|
|
1639
|
+
contentType,
|
|
1640
|
+
size
|
|
1641
|
+
}) => ({
|
|
1642
|
+
name,
|
|
1643
|
+
url,
|
|
1644
|
+
contentType,
|
|
1645
|
+
size
|
|
1646
|
+
})) : undefined,
|
|
1472
1647
|
visitorName: visitorProfile.name,
|
|
1473
1648
|
visitorEmail: visitorProfile.email,
|
|
1474
1649
|
agentUiAction
|
|
@@ -1482,7 +1657,7 @@ class WidgetStore {
|
|
|
1482
1657
|
const messages = delivered ? current.messages.filter(message => message.id !== tempId) : current.messages.map(message => message.id === tempId ? {
|
|
1483
1658
|
...message,
|
|
1484
1659
|
id: result.messageId,
|
|
1485
|
-
status: 'sent'
|
|
1660
|
+
status: this.earlyStatuses.get(result.messageId) ?? 'sent'
|
|
1486
1661
|
} : message);
|
|
1487
1662
|
if (!createdId) return {
|
|
1488
1663
|
messages
|
|
@@ -1617,6 +1792,69 @@ function isLocalMessage(message) {
|
|
|
1617
1792
|
return message.id.startsWith('temp-');
|
|
1618
1793
|
}
|
|
1619
1794
|
|
|
1795
|
+
/**
|
|
1796
|
+
* A sent visitor message keeps its local previews (a `file://` URI, or no URL
|
|
1797
|
+
* at all) until the server copy shows up — the stream echo or a history page.
|
|
1798
|
+
* Its attachments, the only ones that can be opened, then replace them.
|
|
1799
|
+
*/
|
|
1800
|
+
/** A "Seen" missed while the stream was down shows up on the next re-read. */
|
|
1801
|
+
function adoptServerStatuses(messages, server) {
|
|
1802
|
+
const seen = new Set(server.filter(message => message.status === 'read').map(message => message.id));
|
|
1803
|
+
if (seen.size === 0) return messages;
|
|
1804
|
+
let changed = false;
|
|
1805
|
+
const next = messages.map(message => {
|
|
1806
|
+
if (!seen.has(message.id) || message.status === 'read') return message;
|
|
1807
|
+
changed = true;
|
|
1808
|
+
return {
|
|
1809
|
+
...message,
|
|
1810
|
+
status: 'read'
|
|
1811
|
+
};
|
|
1812
|
+
});
|
|
1813
|
+
return changed ? next : messages;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
/** Keeps who/when, drops everything the visitor could read. */
|
|
1817
|
+
function asDeletedPlaceholder(message, deletedAt) {
|
|
1818
|
+
return {
|
|
1819
|
+
...message,
|
|
1820
|
+
body: null,
|
|
1821
|
+
bodyPlain: null,
|
|
1822
|
+
attachments: null,
|
|
1823
|
+
metadata: null,
|
|
1824
|
+
deletedAt
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
/** A deletion missed while the stream was down shows up on the next re-read. */
|
|
1829
|
+
function adoptServerDeletions(messages, server) {
|
|
1830
|
+
const deleted = new Map(server.filter(message => message.deletedAt).map(message => [message.id, message.deletedAt]));
|
|
1831
|
+
if (deleted.size === 0) return messages;
|
|
1832
|
+
let changed = false;
|
|
1833
|
+
const next = messages.map(message => {
|
|
1834
|
+
const deletedAt = deleted.get(message.id);
|
|
1835
|
+
if (!deletedAt || message.deletedAt) return message;
|
|
1836
|
+
changed = true;
|
|
1837
|
+
return asDeletedPlaceholder(message, deletedAt);
|
|
1838
|
+
});
|
|
1839
|
+
return changed ? next : messages;
|
|
1840
|
+
}
|
|
1841
|
+
function adoptServerAttachments(messages, server) {
|
|
1842
|
+
const serverCopies = new Map(server.filter(message => message.attachments?.length).map(message => [message.id, message]));
|
|
1843
|
+
if (serverCopies.size === 0) return messages;
|
|
1844
|
+
let changed = false;
|
|
1845
|
+
const next = messages.map(message => {
|
|
1846
|
+
const copy = serverCopies.get(message.id);
|
|
1847
|
+
const hasLocal = message.attachments?.some(attachment => !/^https?:\/\//i.test(attachment.url));
|
|
1848
|
+
if (!copy || !hasLocal) return message;
|
|
1849
|
+
changed = true;
|
|
1850
|
+
return {
|
|
1851
|
+
...message,
|
|
1852
|
+
attachments: copy.attachments
|
|
1853
|
+
};
|
|
1854
|
+
});
|
|
1855
|
+
return changed ? next : messages;
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1620
1858
|
/** Stable, so same-instant rows keep their order; bad dates sort as oldest. */
|
|
1621
1859
|
function sortByCreatedAt(messages) {
|
|
1622
1860
|
const time = message => Date.parse(message.createdAt) || 0;
|