@ethora/chat-component 26.6.1 → 26.6.2

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 (29) hide show
  1. package/dist/{VideoCallOverlay-6PVJgibC.js → VideoCallOverlay-DnKJQXX4.js} +2 -2
  2. package/dist/{VideoCallOverlay-6PVJgibC.js.map → VideoCallOverlay-DnKJQXX4.js.map} +1 -1
  3. package/dist/components/MainComponents/ChatHeader.d.ts +2 -1
  4. package/dist/components/MainComponents/LanguageSelectorModal.d.ts +0 -10
  5. package/dist/components/MainComponents/Switch.d.ts +8 -0
  6. package/dist/components/MessageBubble/MessageTranslate.d.ts +8 -4
  7. package/dist/components/MessageBubble/TranslatedMessageBody.d.ts +8 -1
  8. package/dist/firebase-messaging-sw.js +16 -4
  9. package/dist/helpers/callLogMessage.d.ts +14 -0
  10. package/dist/helpers/createUserNameFromSetUser.d.ts +36 -1
  11. package/dist/helpers/toBaseLanguage.d.ts +8 -0
  12. package/dist/hooks/useChatSettingState.d.ts +2 -0
  13. package/dist/hooks/useChatWrapperInit.d.ts +1 -0
  14. package/dist/hooks/useMessageTranslation.d.ts +13 -10
  15. package/dist/hooks/useSendMessage.d.ts +1 -0
  16. package/dist/i18n/useT.d.ts +20 -0
  17. package/dist/main-BHPCoRk-.js +74357 -0
  18. package/dist/main-BHPCoRk-.js.map +1 -0
  19. package/dist/main.js +1 -1
  20. package/dist/roomStore/chatSettingsSlice.d.ts +25 -1
  21. package/dist/roomStore/index.d.ts +3 -0
  22. package/dist/types/models/config.model.d.ts +45 -25
  23. package/dist/types/models/message.model.d.ts +1 -0
  24. package/dist/utils/translateModePolicy.d.ts +19 -0
  25. package/package.json +1 -1
  26. package/public/firebase-messaging-sw.js +16 -4
  27. package/dist/main-L70Zd5ai.js +0 -54215
  28. package/dist/main-L70Zd5ai.js.map +0 -1
  29. package/dist/networking/api-requests/translate.api.d.ts +0 -9
package/dist/main.js CHANGED
@@ -1,4 +1,4 @@
1
- import { m as e, R as o, C as t, M as p, n as r, X as u, o as n, p as d, q as h, t as i, v as y, w as I, x as T, y as g, z as l, D as C, E as c, F as m } from "./main-L70Zd5ai.js";
1
+ import { m as e, R as o, C as t, M as p, n as r, X as u, o as n, p as d, q as h, t as i, v as y, w as I, x as T, y as g, z as l, D as C, E as c, F as m } from "./main-BHPCoRk-.js";
2
2
  export {
3
3
  e as AuthTestIds,
4
4
  o as Chat,
@@ -8,12 +8,36 @@ interface ChatState {
8
8
  selectedUser?: IUser;
9
9
  activeFile?: ModalFile;
10
10
  langSource?: Iso639_1Codes;
11
+ /**
12
+ * Whether THIS reader's own outgoing messages get tagged with
13
+ * `<translate source="xx"/>` (see sendTextMessageWithTranslateTag) so
14
+ * translations can be generated for them. Defaults to true so existing
15
+ * hosts that already set `config.translates.enabled` see no change
16
+ * until a reader explicitly opts out via the language-selector toggle.
17
+ *
18
+ * Turning this off does NOT untag anything already sent - a message
19
+ * ships with whatever tag was active at send time, permanently. Turning
20
+ * it back on only affects messages sent from that point forward. Same
21
+ * asymmetry the other direction: turning it on doesn't retroactively
22
+ * tag old messages either - see the disclaimer surfaced next to the
23
+ * toggle in LanguageSelectorModal.
24
+ */
25
+ translateSendEnabled?: boolean;
26
+ /**
27
+ * Reader's own auto/manual pick from the language-selector modal's
28
+ * switcher. Undefined means "never touched it" - falls back to
29
+ * `config.translates.mode` (see resolveTranslateMode). Ignored entirely
30
+ * when the host sets `config.translates.forceType: true`, which is also
31
+ * why the switcher itself doesn't render in that case - nothing should
32
+ * ever write here that a host has pinned.
33
+ */
34
+ translateMode?: 'auto' | 'manual';
11
35
  }
12
36
  export declare const setUser: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<User, "chatSettingStore/setUser">, setConfig: import('@reduxjs/toolkit').ActionCreatorWithNonInferrablePayload<"chatSettingStore/setConfig">, refreshTokens: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<{
13
37
  token: string;
14
38
  refreshToken: string;
15
39
  }, "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<{
16
40
  updates: Partial<User>;
17
- }, "chatSettingStore/updateUser">, setActiveFile: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<ModalFile, "chatSettingStore/setActiveFile">, setLangSource: import('@reduxjs/toolkit').ActionCreatorWithOptionalPayload<Iso639_1Codes, "chatSettingStore/setLangSource">;
41
+ }, "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">;
18
42
  declare const _default: import('redux').Reducer<ChatState>;
19
43
  export default _default;
@@ -9,6 +9,9 @@ export declare const ENCRYPTION_INFLATION_FACTOR = 1.4;
9
9
  export declare const getRoomActivityTimestamp: (room: IRoom) => number;
10
10
  export declare const compactMessageForPersist: (message: IMessage) => IMessage;
11
11
  export declare const optimizePersistedRooms: (rooms: Record<string, IRoom>, charBudget?: number) => Record<string, IRoom>;
12
+ export declare const compactUsersSetForPersist: (usersSet: Record<string, any>) => Record<string, any>;
13
+ /** Puts back what compactUsersSetForPersist folded into the key. */
14
+ export declare const restoreUsersSetForRehydrate: (usersSet: Record<string, any>) => Record<string, any>;
12
15
  export declare const limitMessagesTransform: import('redux-persist').Transform<any, any, Record<string, any>, Record<string, any>>;
13
16
  export declare const scrubSensitiveChatStateTransform: import('redux-persist').Transform<any, any, Record<string, any>, Record<string, any>>;
14
17
  export declare const sanitizeRoomsStateTransform: import('redux-persist').Transform<any, any, Record<string, any>, Record<string, any>>;
@@ -223,43 +223,63 @@ export interface IConfig {
223
223
  enabled: boolean;
224
224
  translations?: Iso639_1Codes;
225
225
  /**
226
- * 'auto' shows the translation inline automatically (legacy behaviour).
227
- * 'on-demand' shows a "Translate" link the reader clicks (LinkedIn-style),
228
- * then renders the result inline with a "Show original" toggle. Default 'auto'.
229
- */
230
- mode?: 'auto' | 'on-demand';
231
- /**
232
- * Locales a message is pre-translated into BEFORE it is sent (e.g.
233
- * ["en-CA", "fr-CA", "es-US"]). The sender calls the translation service
234
- * (see `endpoint`) and embeds the result in the stanza as
235
- * `<translations value='...'/>`, so every recipient receives the message
236
- * already translated and renders it with the existing parser - no
237
- * ejabberd module needed.
226
+ * 'auto' shows the translation inline automatically. 'manual' shows a
227
+ * "Translate" link the reader clicks (LinkedIn-style), then renders the
228
+ * result inline with a "Show original" toggle. Default 'auto'.
238
229
  *
239
- * Keep this to the locales actually present in the room: every extra target
240
- * is one more translation shipped per message. Leave empty to disable
241
- * pre-translation (the on-demand `onTranslate` path still works).
230
+ * Neither mode calls any translation service - both only ever display
231
+ * `message.translations`, whatever arrived attached to the stanza (see
232
+ * `readerLocale` below for how a message gets translated in the first
233
+ * place). This is purely a display choice: show it immediately, or let
234
+ * the reader ask for it.
242
235
  */
243
- targets?: string[];
236
+ mode?: 'auto' | 'manual';
244
237
  /**
245
- * Translation service URL for pre-translation (GET ?source=&text=,
246
- * responding `{translates:[{translatedText, language, languageName}]}`).
247
- * Defaults to Ethora's hosted service; point it at your own deployment
248
- * for self-hosted setups.
238
+ * Pins `mode` as fixed host policy. When true, the language-selector
239
+ * modal's auto/manual switcher does not render at all, and the reader
240
+ * has no way to override `mode` - it's always whatever the host set.
241
+ * Defaults to false: the reader can flip between auto and manual
242
+ * themselves via the switcher, which then wins over `mode` (the host's
243
+ * declared default, used until the reader touches it).
249
244
  */
250
- endpoint?: string;
245
+ forceType?: boolean;
251
246
  /**
252
247
  * Reader's full locale (BCP-47, e.g. "fr-CA"). Falls back to
253
248
  * `config.i18n.locale`. The region is passed through to `onTranslate` so
254
249
  * the service can distinguish fr-CA vs fr-FR; the Translate button
255
250
  * visibility comparison ignores the region (en-US vs en-CA => no button).
251
+ *
252
+ * This is also how a host drives the reader's language from OUTSIDE the
253
+ * chat component: set it (e.g. from your own app's language switcher)
254
+ * and pass the updated config down as a normal prop. Whenever it
255
+ * changes, the component syncs it into the same internal state the
256
+ * in-chat language picker writes to - so it drives both what a reader
257
+ * sees translated INTO and the source language declared on their own
258
+ * outgoing messages. Leave unset to let the reader manage it themselves
259
+ * via the picker (see `showLanguageSelector`).
256
260
  */
257
261
  readerLocale?: string;
258
262
  /**
259
- * Host-provided translation function. When set, the Translate action calls
260
- * this - wire it to your own endpoint (or Google / OpenAI). When omitted,
261
- * the component falls back to server-provided translations parsed onto the
262
- * message (`message.translations`).
263
+ * Shows/hides the globe-icon language picker in the chat header.
264
+ * Defaults to true whenever `enabled` is true. Set to false when the
265
+ * host manages the reader's language itself (via `readerLocale`) and
266
+ * an in-chat picker would just be a second, redundant control.
267
+ */
268
+ showLanguageSelector?: boolean;
269
+ /**
270
+ * Shows/hides the list of selectable languages INSIDE the picker
271
+ * (English/Español/...). Defaults to true. Set to false to keep only
272
+ * the enable/disable-translates toggle - for hosts that drive the
273
+ * reader's language externally via `readerLocale` and don't want a
274
+ * second, redundant language list, but still want the reader to control
275
+ * whether their own outgoing messages get tagged for translation.
276
+ */
277
+ showLanguageList?: boolean;
278
+ /**
279
+ * Host-provided translation function. When set, the manual Translate
280
+ * action calls this - wire it to your own service - instead of reading
281
+ * `message.translations`. Optional; omit to use only what already
282
+ * arrived over XMPP.
263
283
  */
264
284
  onTranslate?: (text: string, ctx: {
265
285
  sourceLocale?: string;
@@ -22,6 +22,7 @@ export interface IMessage {
22
22
  activeMessage?: boolean;
23
23
  isReply?: boolean | string;
24
24
  isDeleted?: boolean;
25
+ isEdited?: boolean;
25
26
  mainMessage?: string;
26
27
  reply?: IReply[];
27
28
  reaction?: Record<string, ReactionMessage>;
@@ -0,0 +1,19 @@
1
+ import { IConfig } from '../types/types';
2
+ export type TranslateMode = 'auto' | 'manual';
3
+ /**
4
+ * Whether the reader is allowed to pick auto vs manual themselves via the
5
+ * language-selector modal's switcher. `forceType: true` means the host has
6
+ * pinned `translates.mode` as policy - the switcher must not even render,
7
+ * let alone let a reader flip it.
8
+ */
9
+ export declare const canReaderChooseTranslateMode: (translates?: IConfig["translates"]) => boolean;
10
+ /**
11
+ * The mode actually in effect for this reader right now.
12
+ *
13
+ * `forceType` wins unconditionally - even a leftover `readerMode` from
14
+ * before the host turned forcing on must not leak through. Otherwise the
15
+ * reader's own pick (if they've touched the switcher) wins over the host's
16
+ * declared default, and the host's `mode` (defaulting to 'auto') is the
17
+ * fallback for a reader who never has.
18
+ */
19
+ export declare const resolveTranslateMode: (translates?: IConfig["translates"], readerMode?: TranslateMode) => TranslateMode;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ethora/chat-component",
3
- "version": "26.6.1",
3
+ "version": "26.6.2",
4
4
  "type": "module",
5
5
  "main": "dist/main.js",
6
6
  "types": "dist/main.d.ts",
@@ -384,9 +384,10 @@ async function handlePush(raw = {}, source = 'push') {
384
384
  });
385
385
 
386
386
  const visible = isVisibleClients(windowClients);
387
+ const hasAnyClient = windowClients.length > 0;
387
388
 
388
389
  if (DEBUG) log('clients:', dumpClients(windowClients));
389
- log('visibleClientExists:', visible);
390
+ log('visibleClientExists:', visible, 'anyClientExists:', hasAnyClient);
390
391
 
391
392
  await broadcastDebugMessage({
392
393
  source,
@@ -445,9 +446,20 @@ async function handlePush(raw = {}, source = 'push') {
445
446
  },
446
447
  };
447
448
 
448
- if (!visible) {
449
+ // Calls: a client that's merely backgrounded (open, just not the
450
+ // visible/focused tab) is NOT the "nobody will hear this ring" case -
451
+ // VideoCallOverlay already rings its own OS notification for exactly
452
+ // that state (see its comment: this SW push is meant to be the
453
+ // fully-offline fallback). `visible` collapses "backgrounded but open"
454
+ // and "fully closed" into the same false, which duplicated that
455
+ // client-side ring every time the tab was merely hidden. Suppress on
456
+ // ANY open client for calls; regular messages keep the visible-only
457
+ // check they already had.
458
+ const suppress = isCall ? hasAnyClient : visible;
459
+
460
+ if (!suppress) {
449
461
  log('SHOW NOTIFICATION', {
450
- reason: 'NO_VISIBLE_CLIENT',
462
+ reason: isCall ? 'NO_CLIENT_AT_ALL' : 'NO_VISIBLE_CLIENT',
451
463
  tag: options.tag,
452
464
  isCall,
453
465
  });
@@ -456,7 +468,7 @@ async function handlePush(raw = {}, source = 'push') {
456
468
  }
457
469
 
458
470
  log('SKIP NOTIFICATION', {
459
- reason: 'VISIBLE_CLIENT',
471
+ reason: isCall ? 'CLIENT_OPEN_WILL_RING_ITSELF' : 'VISIBLE_CLIENT',
460
472
  });
461
473
  }
462
474