@ethora/chat-component 26.9.4 → 26.9.6

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.
Files changed (49) hide show
  1. package/README.md +2 -0
  2. package/dist/{ChatProfileModal-Pr4--RdW.js → ChatProfileModal-DKuxJsdM.js} +526 -519
  3. package/dist/{FilePreviewModal-CYHlKzRc.js → FilePreviewModal-BcaSoO3-.js} +89 -89
  4. package/dist/{ManageDataModal-BE1eGlYh.js → ManageDataModal-Dw4dCQll.js} +2 -2
  5. package/dist/{MarkdownBody-G0_J0NL6.js → MarkdownBody-xtKDMR0h.js} +1 -1
  6. package/dist/MessageSearchModal-BVKU6H3v.js +389 -0
  7. package/dist/PublicChatsModal-CscOnVaQ.js +202 -0
  8. package/dist/{SideDrawer-BG2m7Bcm.js → SideDrawer-WneDrrWG.js} +27 -27
  9. package/dist/{UserProfileModal-B2Hp5bAF.js → UserProfileModal-CMHFq1I9.js} +3 -3
  10. package/dist/{UserSettingsModal-D871FDAT.js → UserSettingsModal-BoZXZ0oR.js} +2 -2
  11. package/dist/{VideoCallOverlay-Br0ji0nZ.js → VideoCallOverlay-CQNi7wp5.js} +1 -1
  12. package/dist/{VisibilityModal-DdFBV1_g.js → VisibilityModal-BPsNiMzJ.js} +2 -2
  13. package/dist/assets/icons.d.ts +9 -0
  14. package/dist/components/InputComponents/Search.d.ts +4 -0
  15. package/dist/components/MainComponents/ArchivedMessageCard.d.ts +11 -0
  16. package/dist/components/MainComponents/computeAnchoredScrollTop.d.ts +9 -0
  17. package/dist/components/MainComponents/useJumpToMessage.d.ts +63 -0
  18. package/dist/components/MainComponents/useStickToBottom.d.ts +27 -0
  19. package/dist/components/Modals/MessageSearchModal/MessageHitResults.d.ts +22 -0
  20. package/dist/components/Modals/MessageSearchModal/MessageSearchModal.d.ts +6 -0
  21. package/dist/components/Modals/MessageSearchModal/resolveSender.d.ts +33 -0
  22. package/dist/components/Modals/MessageSearchModal/searchFilters.d.ts +34 -0
  23. package/dist/components/Modals/MessageSearchModal/snippet.d.ts +14 -0
  24. package/dist/components/Modals/MessageSearchModal/useMessageSearch.d.ts +22 -0
  25. package/dist/components/Modals/PublicChatsModal/PublicChatsModal.d.ts +13 -0
  26. package/dist/components/styled/RoomListComponents/index.d.ts +3 -0
  27. package/dist/context/ToastContext.d.ts +7 -0
  28. package/dist/firebase-messaging-sw.js +21 -3
  29. package/dist/{handleCopyClick-DPjxMoJn.js → handleCopyClick-B70vLjJD.js} +1 -1
  30. package/dist/helpers/constants/MODAL_TYPES.d.ts +2 -0
  31. package/dist/helpers/nanoToMs.d.ts +1 -1
  32. package/dist/helpers/openRoomAtMessage.d.ts +17 -0
  33. package/dist/hooks/useMessageSearchShortcut.d.ts +14 -0
  34. package/dist/hooks/useRoomState.d.ts +1 -0
  35. package/dist/{main-BRIZ98KV.js → main-rJpE4hh-.js} +12318 -11108
  36. package/dist/main.css +1 -1
  37. package/dist/main.js +6 -6
  38. package/dist/networking/api-requests/messageSearch.api.d.ts +56 -0
  39. package/dist/networking/api-requests/publicChats.api.d.ts +23 -0
  40. package/dist/networking/api-requests/xmppToken.api.d.ts +1 -0
  41. package/dist/networking/xmppClient.d.ts +2 -0
  42. package/dist/roomStore/chatSettingsSlice.d.ts +1 -1
  43. package/dist/roomStore/roomsSlice.d.ts +38 -1
  44. package/dist/styles/classNames.d.ts +2 -0
  45. package/dist/types/models/config.model.d.ts +11 -0
  46. package/dist/utils/notificationUtils.d.ts +12 -0
  47. package/dist/version.d.ts +1 -1
  48. package/package.json +1 -1
  49. package/public/firebase-messaging-sw.js +21 -3
package/dist/main.css CHANGED
@@ -1 +1 @@
1
- .ethora-chat-root{font-family:var( --ethora-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif );-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.ethora-chat-root code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}.ethora-chat-root,.ethora-chat-root *{scrollbar-width:thin;scrollbar-color:rgba(20,20,20,.22) transparent;scrollbar-color:var( --ethora-scrollbar-thumb, color-mix(in srgb, var(--ethora-icon-color, #0052cd) 35%, transparent) ) var(--ethora-scrollbar-track, transparent)}.ethora-chat-root::-webkit-scrollbar,.ethora-chat-root ::-webkit-scrollbar{width:8px;height:8px}.ethora-chat-root::-webkit-scrollbar-thumb,.ethora-chat-root ::-webkit-scrollbar-thumb{background-color:#14141438;background-color:var( --ethora-scrollbar-thumb, color-mix(in srgb, var(--ethora-icon-color, #0052cd) 35%, transparent) );border-radius:var(--ethora-radius-full, 999px);border:2px solid transparent;background-clip:content-box}.ethora-chat-root::-webkit-scrollbar-thumb:hover,.ethora-chat-root ::-webkit-scrollbar-thumb:hover{background-color:#14141466;background-color:var( --ethora-scrollbar-thumb-hover, color-mix(in srgb, var(--ethora-icon-color, #0052cd) 60%, transparent) );background-clip:content-box}.ethora-chat-root::-webkit-scrollbar-track,.ethora-chat-root ::-webkit-scrollbar-track{background-color:var(--ethora-scrollbar-track, transparent)}.ethora-apple-emoji{font-family:"Apple Color Emoji","Segoe UI Emoji","Noto Color Emoji",sans-serif}.ethora-chat-root .emoji-mart{max-width:320px;max-height:360px;overflow-y:auto;border-radius:12px;box-shadow:0 4px 12px #0000001a}.ethora-message-highlight{animation:ethoraMessageHighlight 2s ease-out;transition:background-color .3s ease}@keyframes ethoraMessageHighlight{0%{background-color:#2196f366;transform:scale(1.02);box-shadow:0 0 0 2px #2196f333}30%{background-color:#2196f34d;box-shadow:0 0 0 2px #2196f326}60%{background-color:#2196f333;box-shadow:0 0 0 1px #2196f31a}to{background-color:transparent;transform:scale(1);box-shadow:none}}@media(max-width:768px){.ethora-message-notification-container{left:50%!important;right:auto!important;transform:translate(-50%)!important;align-items:center!important}.ethora-message-notification-container[data-position-horizontal=left],.ethora-message-notification-container[data-position-horizontal=right]{left:50%!important;right:auto!important;transform:translate(-50%)!important}}
1
+ .ethora-chat-root{font-family:var( --ethora-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif );-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.ethora-chat-root code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}.ethora-chat-root,.ethora-chat-root *{scrollbar-width:thin;scrollbar-color:rgba(20,20,20,.22) transparent;scrollbar-color:var( --ethora-scrollbar-thumb, color-mix(in srgb, var(--ethora-icon-color, #0052cd) 35%, transparent) ) var(--ethora-scrollbar-track, transparent)}.ethora-chat-root::-webkit-scrollbar,.ethora-chat-root ::-webkit-scrollbar{width:8px;height:8px}.ethora-chat-root::-webkit-scrollbar-thumb,.ethora-chat-root ::-webkit-scrollbar-thumb{background-color:#14141438;background-color:var( --ethora-scrollbar-thumb, color-mix(in srgb, var(--ethora-icon-color, #0052cd) 35%, transparent) );border-radius:var(--ethora-radius-full, 999px);border:2px solid transparent;background-clip:content-box}.ethora-chat-root::-webkit-scrollbar-thumb:hover,.ethora-chat-root ::-webkit-scrollbar-thumb:hover{background-color:#14141466;background-color:var( --ethora-scrollbar-thumb-hover, color-mix(in srgb, var(--ethora-icon-color, #0052cd) 60%, transparent) );background-clip:content-box}.ethora-chat-root::-webkit-scrollbar-track,.ethora-chat-root ::-webkit-scrollbar-track{background-color:var(--ethora-scrollbar-track, transparent)}.ethora-apple-emoji{font-family:"Apple Color Emoji","Segoe UI Emoji","Noto Color Emoji",sans-serif}.ethora-chat-root .emoji-mart{max-width:320px;max-height:360px;overflow-y:auto;border-radius:12px;box-shadow:0 4px 12px #0000001a}[data-message-id].ethora-message-highlight{animation:ethoraMessageFlash 1s ease-out;border-radius:var(--ethora-radius-md, 12px)}@keyframes ethoraMessageFlash{0%{background-color:color-mix(in srgb,var(--ethora-color-primary, #0052cd) 35%,transparent)}to{background-color:transparent}}.ethora-message-highlight{animation:ethoraMessageHighlight 1s ease-out;transition:background-color .3s ease}@keyframes ethoraMessageHighlight{0%{background-color:#2196f366;transform:scale(1.02);box-shadow:0 0 0 2px #2196f333}30%{background-color:#2196f34d;box-shadow:0 0 0 2px #2196f326}60%{background-color:#2196f333;box-shadow:0 0 0 1px #2196f31a}to{background-color:transparent;transform:scale(1);box-shadow:none}}@media(max-width:768px){.ethora-message-notification-container{left:50%!important;right:auto!important;transform:translate(-50%)!important;align-items:center!important}.ethora-message-notification-container[data-position-horizontal=left],.ethora-message-notification-container[data-position-horizontal=right]{left:50%!important;right:auto!important;transform:translate(-50%)!important}}
package/dist/main.js CHANGED
@@ -1,13 +1,13 @@
1
- import { b5 as a, b6 as r, b7 as b, b8 as o, b9 as t, ba as i, bb as p, bc as u, bd as h, be as n, bf as l, bg as d, bh as f, L as m, bi as T, bj as g, bk as y, bl as I, bm as R, bn as c, bo as k, bp as C, bq as A, ak as E, al as Q, br as F, bs as M } from "./main-BRIZ98KV.js";
1
+ import { ba as a, bb as r, bc as b, bd as o, be as t, bf as u, bg as i, bh as p, bi as h, bj as n, bk as l, bl as d, bm as f, L as m, bn as T, bo as g, bp as y, bq as I, br as R, bs as c, bt as k, bu as C, bv as A, ak as E, al as Q, bw as v, bx as F } from "./main-rJpE4hh-.js";
2
2
  export {
3
3
  a as AuthTestIds,
4
4
  r as Chat,
5
5
  b as ChatInputTestIds,
6
6
  o as MessageBubbleTestIds,
7
7
  t as RefreshFatalError,
8
- i as RoomListTestIds,
9
- p as XmppProvider,
10
- u as applyTypography,
8
+ u as RoomListTestIds,
9
+ i as XmppProvider,
10
+ p as applyTypography,
11
11
  h as clearTypography,
12
12
  n as formatFingerprint,
13
13
  l as handleQRChatId,
@@ -25,6 +25,6 @@ export {
25
25
  A as useQRCodeChat,
26
26
  E as useRoomMute,
27
27
  Q as useRoomPresence,
28
- F as useTypography,
29
- M as useUnread
28
+ v as useTypography,
29
+ F as useUnread
30
30
  };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * GET /v2/apps/{appId}/messages/search: full-text / substring search over
3
+ * the chat archive. For a user token the server restricts it to chats the
4
+ * caller can see, so no client-side filtering is needed for privacy.
5
+ */
6
+ export interface MessageSearchHit {
7
+ /** The archive row's own id: the only identifier every hit is guaranteed to have. */
8
+ id: string;
9
+ /** Room name, the local part of the room JID. */
10
+ chatId: string;
11
+ chatType: string;
12
+ /** Full room JID. */
13
+ room: string;
14
+ /** Sender's full JID, WITH the xmpp host: `appId_userId@xmpp.host`. */
15
+ from: string;
16
+ fromUserId: string;
17
+ body: string;
18
+ messageId: string;
19
+ stanzaId: string;
20
+ createdAt: string;
21
+ }
22
+ export interface MessageSearchPage {
23
+ items: MessageSearchHit[];
24
+ /** Total matches on the server, not the size of this page. */
25
+ total: number;
26
+ offset: number;
27
+ limit: number;
28
+ /**
29
+ * Where the next page starts. Counted in SERVER rows, not in `items`:
30
+ * deleted messages are dropped from `items`, and paging by the filtered
31
+ * count would re-request rows already seen.
32
+ */
33
+ nextOffset: number;
34
+ }
35
+ export interface SearchMessagesParams {
36
+ q: string;
37
+ /** Room name (local part of the JID). Omit to search every visible chat. */
38
+ chatId?: string;
39
+ /** Only messages from this user (a room member's `_id`). */
40
+ fromUserId?: string;
41
+ /** ISO 8601 bounds; the server rejects anything else. */
42
+ since?: string;
43
+ until?: string;
44
+ limit?: number;
45
+ offset?: number;
46
+ signal?: AbortSignal;
47
+ }
48
+ export declare const MESSAGE_SEARCH_PAGE_SIZE = 20;
49
+ export declare function searchMessages({ q, chatId, fromUserId, since, until, limit, offset, signal, }: SearchMessagesParams): Promise<MessageSearchPage>;
50
+ /**
51
+ * A key that is unique per hit. stanzaId and messageId are NOT: on QA whole
52
+ * pages of older hits come back with both empty, which made every React key
53
+ * `chatId:` (rows were left behind when the list changed) and made a
54
+ * duplicate check by those ids throw real hits away.
55
+ */
56
+ export declare const hitKey: (hit: MessageSearchHit) => string;
@@ -0,0 +1,23 @@
1
+ /** One entry of GET /v1/chats/public: a chat anyone in the app may join. */
2
+ export interface PublicChat {
3
+ /** Room name: the local part of its JID. */
4
+ name: string;
5
+ title: string;
6
+ description: string;
7
+ picture: string;
8
+ e2ee: boolean;
9
+ }
10
+ export interface PublicChatsPage {
11
+ items: PublicChat[];
12
+ total: number;
13
+ offset: number;
14
+ limit: number;
15
+ /** Server rows consumed so far, not counting hidden ones. */
16
+ nextOffset: number;
17
+ }
18
+ export declare const PUBLIC_CHATS_PAGE_SIZE = 50;
19
+ export declare function getPublicChats({ offset, limit, signal, }?: {
20
+ offset?: number;
21
+ limit?: number;
22
+ signal?: AbortSignal;
23
+ }): Promise<PublicChatsPage>;
@@ -0,0 +1 @@
1
+ export declare function fetchFreshXmppPassword(): Promise<string | null>;
@@ -10,6 +10,8 @@ export declare class XmppClient implements XmppClientInterface {
10
10
  client: Client;
11
11
  devServer: string | undefined;
12
12
  host: string;
13
+ private configuredHost;
14
+ private configuredConference;
13
15
  service: string;
14
16
  conference: string;
15
17
  username: string;
@@ -38,7 +38,7 @@ export declare const setUser: import('@reduxjs/toolkit').ActionCreatorWithOption
38
38
  refreshToken: string;
39
39
  fileToken?: string;
40
40
  xmppPassword?: string;
41
- }, "chatSettingStore/refreshTokens">, logout: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"chatSettingStore/logout">, setActiveModal: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<string, "chatSettingStore/setActiveModal">, setDeleteModal: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<DeleteModal, "chatSettingStore/setDeleteModal">, setSelectedUser: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<IUser, "chatSettingStore/setSelectedUser">, updateUser: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
41
+ }, "chatSettingStore/refreshTokens">, setXmppPassword: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<string, "chatSettingStore/setXmppPassword">, logout: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"chatSettingStore/logout">, setActiveModal: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<string, "chatSettingStore/setActiveModal">, setDeleteModal: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<DeleteModal, "chatSettingStore/setDeleteModal">, setSelectedUser: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<IUser, "chatSettingStore/setSelectedUser">, updateUser: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
42
42
  updates: Partial<User>;
43
43
  }, "chatSettingStore/updateUser">, setActiveFile: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<ModalFile, "chatSettingStore/setActiveFile">, setLangSource: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<Iso639_1Codes, "chatSettingStore/setLangSource">, setTranslateSendEnabled: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<boolean, "chatSettingStore/setTranslateSendEnabled">, setTranslateMode: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<"auto" | "manual", "chatSettingStore/setTranslateMode">;
44
44
  declare const _default: import('redux').Reducer<ChatState>;
@@ -16,6 +16,34 @@ import { default as XmppClient } from '../networking/xmppClient';
16
16
  */
17
17
  export declare const MAX_DRAFT_LENGTH = 2000;
18
18
  export declare const MAX_PERSISTED_DRAFTS = 30;
19
+ /** A message found in the archive that the chat cannot show in place. */
20
+ export interface ArchivedMessage {
21
+ roomJID: string;
22
+ sender: string;
23
+ body: string;
24
+ /** ISO timestamp from the archive. */
25
+ createdAt: string;
26
+ }
27
+ /** A request to scroll a room's transcript to one message. */
28
+ export interface PendingJump {
29
+ roomJID: string;
30
+ /** Any id the message may be known by: message id, stanza id, xmpp id. */
31
+ ids: string[];
32
+ /**
33
+ * For a message that has no usable id (search archive rows often carry
34
+ * none): its timestamp and text, matched against the loaded transcript.
35
+ */
36
+ createdAt?: string;
37
+ body?: string;
38
+ /**
39
+ * What to show if the message cannot be reached in the transcript: the hit
40
+ * itself, so a tap on a search result always ends with the message on
41
+ * screen rather than an error.
42
+ */
43
+ preview?: ArchivedMessage;
44
+ /** Epoch ms the request was made, to drop one nobody ever fulfilled. */
45
+ at: number;
46
+ }
19
47
  interface RoomMessagesState {
20
48
  rooms: {
21
49
  [jid: string]: IRoom;
@@ -34,6 +62,9 @@ interface RoomMessagesState {
34
62
  loadingText?: string;
35
63
  roomsLoadedOnce: boolean;
36
64
  roomsLoadError: boolean;
65
+ pendingJump: PendingJump | null;
66
+ archivedMessage: ArchivedMessage | null;
67
+ joiningRoomJID: string | null;
37
68
  drafts: Record<string, string>;
38
69
  }
39
70
  interface PreloadRoomUpdate {
@@ -102,7 +133,13 @@ export declare const addRoom: import('@reduxjs/toolkit').ActionCreatorWithOption
102
133
  chatJID?: string;
103
134
  }, "roomMessages/setRoomNoMessages">, setRoomsLoadResolved: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
104
135
  success: boolean;
105
- }, "roomMessages/setRoomsLoadResolved">, setCurrentRoom: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
136
+ }, "roomMessages/setRoomsLoadResolved">, requestJumpToMessage: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
137
+ roomJID: string;
138
+ ids: string[];
139
+ createdAt?: string;
140
+ body?: string;
141
+ preview?: ArchivedMessage;
142
+ }, "roomMessages/requestJumpToMessage">, clearPendingJump: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"roomMessages/clearPendingJump">, showArchivedMessage: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<ArchivedMessage, "roomMessages/showArchivedMessage">, clearArchivedMessage: import('@reduxjs/toolkit').ActionCreatorWithoutPayload<"roomMessages/clearArchivedMessage">, setJoiningRoom: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<string, "roomMessages/setJoiningRoom">, clearJoiningRoom: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<string, "roomMessages/clearJoiningRoom">, setCurrentRoom: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
106
143
  roomJID: string | null;
107
144
  }, "roomMessages/setCurrentRoom">, setMemberOnline: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
108
145
  roomJID: string;
@@ -29,6 +29,8 @@
29
29
  export declare const CHAT_ROOT_CLASS = "ethora-chat-root";
30
30
  /** Transient highlight flashed on a message when a notification is opened. */
31
31
  export declare const MESSAGE_HIGHLIGHT_CLASS = "ethora-message-highlight";
32
+ /** How long the highlight stays on a message; keep equal to the CSS animation. */
33
+ export declare const HIGHLIGHT_MS = 1000;
32
34
  /** Fixed-position container holding the in-app notification toasts. */
33
35
  export declare const NOTIFICATION_CONTAINER_CLASS = "ethora-message-notification-container";
34
36
  /** Forces the colour-emoji font on nodes that render a bare emoji glyph. */
@@ -494,6 +494,17 @@ export interface IConfig {
494
494
  * a presence state, not a count, so it is unaffected.
495
495
  */
496
496
  disableUserCount?: boolean;
497
+ /**
498
+ * Hide the "Search messages" button in the chat header (and so the search
499
+ * panel). Search is also hidden when `appId` is not set, since the archive
500
+ * it queries is scoped by app.
501
+ */
502
+ disableMessageSearch?: boolean;
503
+ /**
504
+ * Hide the "Discover chats" entry of the room list menu, the directory of
505
+ * the app's public chats (GET /v1/chats/public).
506
+ */
507
+ disablePublicChatsDirectory?: boolean;
497
508
  disableSentLogic?: boolean;
498
509
  initBeforeLoad?: boolean;
499
510
  initBeforeLoadAuth?: {
@@ -9,6 +9,18 @@ import { PushPayloadLike } from './notificationPolicy';
9
9
  export declare function showBrowserNotification(title: string, options: NotificationOptions & {
10
10
  data?: any;
11
11
  }, serviceWorkerScope?: string, onClick?: (event: Event) => void): Promise<void>;
12
+ /**
13
+ * Only http(s) URLs and same-origin paths are usable as a notification icon;
14
+ * anything else is dropped rather than handed to the browser.
15
+ */
16
+ export declare function sanitizeIconUrl(value: unknown): string;
17
+ /**
18
+ * The icon a push asks for: its own icon first (data.icon, notification.icon),
19
+ * then the picture it carries (notification.image, what this used to return
20
+ * unconditionally), then the favicon. The service worker applies the same
21
+ * preference for a backgrounded tab.
22
+ */
23
+ export declare function pickPushIcon(payload: PushPayloadLike): string;
12
24
  /**
13
25
  * Helper to extract common data from a message/payload for browser notifications.
14
26
  */
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const ETHORA_CHAT_COMPONENT_VERSION = "26.9.1";
1
+ export declare const ETHORA_CHAT_COMPONENT_VERSION = "26.9.6";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ethora/chat-component",
3
- "version": "26.9.4",
3
+ "version": "26.9.6",
4
4
  "type": "module",
5
5
  "main": "dist/main.js",
6
6
  "types": "dist/main.d.ts",
@@ -172,8 +172,26 @@ function pickUrl(raw = {}, data = {}, notification = {}) {
172
172
  );
173
173
  }
174
174
 
175
- function pickIcon() {
176
- return CUSTOM_ICON || DEFAULT_ICON;
175
+ // An icon the push itself carries (data.icon, notification.icon or icon on
176
+ // the payload) wins over the one configured for the whole app: a push about a
177
+ // specific chat or sender knows better than the app-wide default. Only http(s)
178
+ // and same-origin paths are accepted; anything else (data:, javascript:, a
179
+ // bare word) is ignored and the configured/default icon is used instead.
180
+ function sanitizeIconUrl(value) {
181
+ const url = typeof value === 'string' ? value.trim() : '';
182
+ if (!url) return '';
183
+ if (url.charAt(0) === '/' && url.charAt(1) !== '/') return url;
184
+ return /^https?:\/\/[^\s]+$/i.test(url) ? url : '';
185
+ }
186
+
187
+ function pickIcon(raw = {}, data = {}, notification = {}) {
188
+ return (
189
+ sanitizeIconUrl(data.icon) ||
190
+ sanitizeIconUrl(notification.icon) ||
191
+ sanitizeIconUrl(raw.icon) ||
192
+ CUSTOM_ICON ||
193
+ DEFAULT_ICON
194
+ );
177
195
  }
178
196
 
179
197
  function pickBadge() {
@@ -332,7 +350,7 @@ async function handlePush(raw = {}, source = 'push') {
332
350
  const title = pickTitle(raw, data, notification);
333
351
  const body = pickBody(raw, data, notification);
334
352
  const url = pickUrl(raw, data, notification);
335
- const icon = pickIcon();
353
+ const icon = pickIcon(raw, data, notification);
336
354
  const badge = pickBadge();
337
355
  const image = pickImage(raw, data, notification);
338
356