@ethora/chat-component 26.8.1 → 26.9.0

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 (48) hide show
  1. package/README.md +1 -0
  2. package/dist/{ChatProfileModal-DZVi2WRb.js → ChatProfileModal-DfXEnzJf.js} +6 -3
  3. package/dist/{FilePreviewModal-BelSYN5e.js → FilePreviewModal-Cs1tij9R.js} +35 -35
  4. package/dist/{ManageDataModal-Bs_0SHBL.js → ManageDataModal-BBOui09n.js} +3 -3
  5. package/dist/{MarkdownBody-BYUK4GVM.js → MarkdownBody-DttRXyAK.js} +3 -3
  6. package/dist/{SideDrawer-C_nWZRwH.js → SideDrawer-DLqEOqAi.js} +35 -35
  7. package/dist/UserProfileModal-DEtLQxkJ.js +594 -0
  8. package/dist/{UserSettingsModal-J7uJfki1.js → UserSettingsModal-Ddht5fc0.js} +9 -9
  9. package/dist/{VideoCallOverlay-BXHNEhdX.js → VideoCallOverlay-BJMarThf.js} +2673 -2662
  10. package/dist/VisibilityModal-CckTnGKB.js +138 -0
  11. package/dist/assets/icons.d.ts +8 -0
  12. package/dist/components/MainComponents/AttachmentList.d.ts +5 -0
  13. package/dist/components/MainComponents/SealedAttachmentList.d.ts +14 -0
  14. package/dist/components/RoomComponents/styled/LastMessageSealed.d.ts +14 -0
  15. package/dist/components/styled/AudioMessage.d.ts +38 -0
  16. package/dist/components/styled/MessageImage.d.ts +10 -0
  17. package/dist/e2ee/crypto.d.ts +32 -0
  18. package/dist/e2ee/fileEnvelope.d.ts +48 -0
  19. package/dist/e2ee/index.d.ts +53 -0
  20. package/dist/e2ee/omemo.d.ts +130 -0
  21. package/dist/e2ee/protobuf.d.ts +25 -0
  22. package/dist/e2ee/ratchet.d.ts +54 -0
  23. package/dist/e2ee/stanza.d.ts +24 -0
  24. package/dist/e2ee/store.d.ts +12 -0
  25. package/dist/{handleCopyClick-Bj2V_brV.js → handleCopyClick-CWw4UAlT.js} +2 -2
  26. package/dist/helpers/findMessagesForFile.d.ts +25 -0
  27. package/dist/helpers/sealedAttachments.d.ts +25 -0
  28. package/dist/helpers/xmppIdShape.d.ts +9 -0
  29. package/dist/helpers/xmppUsername.d.ts +1 -1
  30. package/dist/hooks/useChatWrapperInit.d.ts +5 -1
  31. package/dist/{main-DmtEeYji.js → main-BHtKhpBZ.js} +21004 -16820
  32. package/dist/main.d.ts +2 -0
  33. package/dist/main.js +23 -20
  34. package/dist/networking/api-requests/auth.api.d.ts +10 -1
  35. package/dist/networking/api-requests/rooms.api.d.ts +1 -1
  36. package/dist/networking/authRefresh.d.ts +6 -0
  37. package/dist/networking/xmpp/sendMediaMessage.xmpp.d.ts +12 -1
  38. package/dist/networking/xmpp/sendTextMessage.xmpp.d.ts +1 -1
  39. package/dist/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.d.ts +1 -1
  40. package/dist/networking/xmppClient.d.ts +39 -0
  41. package/dist/roomStore/storage.d.ts +2 -1
  42. package/dist/styles/tokens.d.ts +14 -4
  43. package/dist/types/models/config.model.d.ts +33 -0
  44. package/dist/types/models/message.model.d.ts +27 -0
  45. package/dist/types/models/room.model.d.ts +10 -0
  46. package/package.json +4 -1
  47. package/dist/UserProfileModal-CD2IB5SD.js +0 -574
  48. package/dist/VisibilityModal-4QJnWpAL.js +0 -132
package/dist/main.d.ts CHANGED
@@ -32,3 +32,5 @@ export type { Iso639_1Codes, Language, LanguageOptions, } from './types/models/l
32
32
  export type { CustomComponentsContextValue, CustomScrollableAreaProps, DaySeparatorProps, NewMessageLabelProps, DecoratedMessage, ScrollControllerApi, } from './types/models/customComponents.model';
33
33
  export type { SendInputProps } from './components/styled/SendInput';
34
34
  export type { xmppSettingsInterface, XmppClientInterface, MediaUploadData, } from './types/models/xmpp.model';
35
+ export { omemo, isE2eeEnabled, formatFingerprint } from './e2ee';
36
+ export type { DeviceInfo, Trust } from './e2ee';
package/dist/main.js CHANGED
@@ -1,27 +1,30 @@
1
- import { b2 as a, b3 as r, b4 as b, b5 as o, b6 as t, b7 as h, b8 as u, b9 as i, ba as p, bb as n, bc as d, J as l, bd as T, be as f, bf as y, bg as I, bh as R, bi as c, bj as g, bk as m, ah as C, ai as k, bl as A, bm as Q } from "./main-DmtEeYji.js";
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-BHtKhpBZ.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
- h as RoomListTestIds,
9
- u as XmppProvider,
10
- i as applyTypography,
11
- p as clearTypography,
12
- n as handleQRChatId,
13
- d as isRefreshFatalError,
14
- l as logoutService,
15
- T as parseQuickReplies,
16
- f as refreshAuthTokens,
17
- y as refreshAuthTokensQuietly,
18
- I as resendMessage,
19
- R as useInAppNotifications,
20
- c as useIsUserOnline,
21
- g as usePushNotifications,
22
- m as useQRCodeChat,
23
- C as useRoomMute,
24
- k as useRoomPresence,
25
- A as useTypography,
26
- Q as useUnread
8
+ i as RoomListTestIds,
9
+ p as XmppProvider,
10
+ u as applyTypography,
11
+ h as clearTypography,
12
+ n as formatFingerprint,
13
+ l as handleQRChatId,
14
+ d as isE2eeEnabled,
15
+ f as isRefreshFatalError,
16
+ m as logoutService,
17
+ T as omemo,
18
+ g as parseQuickReplies,
19
+ y as refreshAuthTokens,
20
+ I as refreshAuthTokensQuietly,
21
+ R as resendMessage,
22
+ c as useInAppNotifications,
23
+ k as useIsUserOnline,
24
+ C as usePushNotifications,
25
+ A as useQRCodeChat,
26
+ E as useRoomMute,
27
+ Q as useRoomPresence,
28
+ F as useTypography,
29
+ M as useUnread
27
30
  };
@@ -19,5 +19,14 @@ export declare const signInWithGoogle: () => Promise<{
19
19
  idToken: string;
20
20
  credential: import('firebase/auth').OAuthCredential;
21
21
  }>;
22
- export declare function uploadFile(formData: FormData, activeRoomJID: string): Promise<import('axios').AxiosResponse<any, any, {}>>;
22
+ export interface UploadFileOptions {
23
+ /**
24
+ * The payload is ciphertext the caller sealed (e2ee rooms). Tells the
25
+ * backend to store it verbatim: no thumbnail, no duration/page probe, and
26
+ * `mimetype` recorded as declared rather than inferred. Set on the form
27
+ * BEFORE the secure/legacy split so both routes carry it.
28
+ */
29
+ clientEncrypted?: boolean;
30
+ }
31
+ export declare function uploadFile(formData: FormData, activeRoomJID: string, options?: UploadFileOptions): Promise<import('axios').AxiosResponse<any, any, {}>>;
23
32
  export declare function ensureUserFromMy(user: User | null | undefined): Promise<User | null>;
@@ -15,7 +15,7 @@ export declare function unmuteRoom(chatName: string): Promise<{
15
15
  muted: boolean;
16
16
  } | undefined>;
17
17
  export declare function postRoom(data: PostRoom): Promise<any>;
18
- export declare function postPrivateRoom(username: string, title?: string): Promise<ApiRoom>;
18
+ export declare function postPrivateRoom(username: string, e2ee?: boolean): Promise<ApiRoom>;
19
19
  export declare function postReportRoom(data: PostReportRoom): Promise<any>;
20
20
  export declare function postAddRoomMember(data: PostAddRoomMember): Promise<RoomMember[]>;
21
21
  export declare function deleteRoomMember(data: DeleteRoomMember): Promise<any>;
@@ -103,6 +103,12 @@ type CredentialLike = Partial<Pick<User, 'xmppUsername' | 'refreshToken' | 'toke
103
103
  export declare function markCurrentSessionDead(): void;
104
104
  export declare function isSessionDead(candidate: CredentialLike): boolean;
105
105
  export declare function clearDeadSessionLatch(): void;
106
+ /**
107
+ * Single-flight wrapper: several requests can fail with the same dead
108
+ * refresh token at once (a burst of 401s right after the token dies), and
109
+ * without this each would call the host's `loginFunction` independently.
110
+ */
111
+ export declare function attemptHostRecovery(): Promise<boolean>;
106
112
  /** Test seam: drops any shared in-flight promise between cases. */
107
113
  export declare function __resetAuthRefreshStateForTests(): void;
108
114
  export {};
@@ -1,2 +1,13 @@
1
1
  import { Client } from '@xmpp/client';
2
- export declare function sendMediaMessage(client: Client, roomJID: string, data: any, id: string): void;
2
+ /**
3
+ * What <body> carries in an e2ee room: the key material for each sealed
4
+ * attachment, in the same order as `attachments` / the flat fields on <data>.
5
+ * The real mimetype and filename are not here - they are inside each sealed
6
+ * payload (see e2ee/fileEnvelope.ts), so they survive exactly as long as the
7
+ * bytes do and cannot be stripped off in transit.
8
+ */
9
+ export interface E2eeMediaBody {
10
+ v: 1;
11
+ keys: string[];
12
+ }
13
+ export declare function sendMediaMessage(client: Client, roomJID: string, data: any, id: string): Promise<void>;
@@ -1,3 +1,3 @@
1
1
  import { Client } from '@xmpp/client';
2
2
  import { IMentionSpan } from '../../types/types';
3
- export declare const sendTextMessage: (client: Client, roomJID: string, firstName: string, lastName: string, photo: string, walletAddress: string, userMessage: string, notDisplayedValue?: string, isReply?: boolean, showInChannel?: boolean, mainMessage?: string, devServer?: string, customId?: string, mentions?: IMentionSpan[]) => boolean;
3
+ export declare const sendTextMessage: (client: Client, roomJID: string, firstName: string, lastName: string, photo: string, walletAddress: string, userMessage: string, notDisplayedValue?: string, isReply?: boolean, showInChannel?: boolean, mainMessage?: string, devServer?: string, customId?: string, mentions?: IMentionSpan[]) => Promise<boolean>;
@@ -13,4 +13,4 @@ export declare const sendTextMessageWithTranslateTag: (client: Client, stanzaMes
13
13
  mainMessage?: string;
14
14
  devServer?: string;
15
15
  mentions?: IMentionSpan[];
16
- }, source: Iso639_1Codes, customId?: string) => boolean;
16
+ }, source: Iso639_1Codes, customId?: string) => Promise<boolean>;
@@ -109,6 +109,45 @@ export declare class XmppClient implements XmppClientInterface {
109
109
  */
110
110
  private authRecoveryAttempts;
111
111
  private maxAuthRecoveryAttempts;
112
+ /**
113
+ * True for the whole span of a bounded, self-healing credential
114
+ * refresh - from the moment `recoverFromAuthFailure` commits to trying
115
+ * again through to it either reconnecting online or giving up. While
116
+ * this is true, `status` cycles through 'auth_failed' -> 'offline' ->
117
+ * 'connecting' purely as a side effect of the refresh-and-reconnect
118
+ * dance, not because anything is actually wrong with the network - see
119
+ * `isRecoveringAuth` and its use in useChatWrapperInit's
120
+ * `isStatusConnectionLost`.
121
+ */
122
+ private authRecoveryActive;
123
+ /**
124
+ * Whether a bounded, provider-backed credential recovery is currently
125
+ * in flight (see `authRecoveryActive` above). Consumers use this to
126
+ * avoid painting a "Connection lost" banner for a recoverable, expected
127
+ * token refresh - only a status change that happens OUTSIDE this window
128
+ * (a real socket/network drop, or a recovery that has already given up)
129
+ * should read as a genuine connection loss.
130
+ */
131
+ get isRecoveringAuth(): boolean;
132
+ /**
133
+ * Bug C, part 2: a client that has never once reached 'online' is
134
+ * mid-FIRST-connect, not "lost" - the WS handshake + SASL bind for a
135
+ * brand new session routinely spends a second or two sitting in
136
+ * 'connecting'/'offline' before its first 'online', and
137
+ * isStatusConnectionLost used to paint that identically to a real
138
+ * dropped connection, flashing "Connection lost. Retrying..." on every
139
+ * cold start (measured live: ~2s, samples 4-13 of a 200ms poll) even
140
+ * though nothing had ever been connected in the first place.
141
+ *
142
+ * Set once, on the first 'online' event, and deliberately never reset -
143
+ * a later real drop (offline/error) or auth-triggered reconnect must
144
+ * still read as connection-lost once this session has actually been
145
+ * online before. `authFailureDetected`/`authRecoveryAttempts` reset on
146
+ * every reconnect; this one is a one-way latch for the life of the
147
+ * client instance.
148
+ */
149
+ private everOnline;
150
+ get hasEverBeenOnline(): boolean;
112
151
  /**
113
152
  * Inject (or replace) the callback used to fetch fresh XMPP
114
153
  * credentials after a SASL `not-authorized`. The provider refreshes
@@ -1,4 +1,5 @@
1
- import { default as createWebStorage } from 'redux-persist/lib/storage/createWebStorage';
1
+ import { default as createWebStorageImport } from 'redux-persist/lib/storage/createWebStorage';
2
+ declare const createWebStorage: typeof createWebStorageImport;
2
3
  export declare const isQuotaExceededError: (error: unknown) => boolean;
3
4
  export declare const withQuotaHandling: (engine: ReturnType<typeof createWebStorage>) => ReturnType<typeof createWebStorage>;
4
5
  declare const storage: {
@@ -1,15 +1,19 @@
1
1
  import { CSSProperties } from 'react';
2
- import { IConfig, TypographyConfig } from '../types/types';
2
+ import { ChatColorScheme, IConfig, TypographyConfig } from '../types/types';
3
3
  /** Mix `hex` toward white by `amount` (0-1). Used for soft tints. */
4
4
  export declare const tintColor: (hex: string, amount: number) => string;
5
5
  /** Mix `hex` toward black by `amount` (0-1). Used for hover/pressed shades. */
6
6
  export declare const shadeColor: (hex: string, amount: number) => string;
7
+ /** Mix `a` toward `b` by `amount` (0-1). Used for dark-scheme tints. */
8
+ export declare const mixColor: (a: string, b: string, amount: number) => string;
9
+ /** The scheme actually rendered: `'system'` resolved against the OS. */
10
+ export type ResolvedColorScheme = 'light' | 'dark';
7
11
  export type ThemeTokenMap = Record<string, string>;
8
12
  /**
9
13
  * Build the full `--ethora-*` CSS variable map from the host's config.
10
14
  * Pure function - safe to call outside the browser (SSR, tests).
11
15
  */
12
- export declare function buildThemeTokens(colors?: IConfig['colors'], typography?: TypographyConfig): ThemeTokenMap;
16
+ export declare function buildThemeTokens(colors?: IConfig['colors'], typography?: TypographyConfig, scheme?: ResolvedColorScheme): ThemeTokenMap;
13
17
  /**
14
18
  * The class the chat's root element carries; useful for scoping global CSS.
15
19
  * Re-exported from `styles/classNames`, which owns every class name this
@@ -21,7 +25,13 @@ export { CHAT_ROOT_CLASS } from './classNames';
21
25
  * the chat root element's inline `style` so the variables are scoped to the
22
26
  * chat subtree wherever the root can carry them.
23
27
  */
24
- export declare function useThemeTokenStyle(colors?: IConfig['colors'], typography?: TypographyConfig): CSSProperties;
28
+ export declare function useThemeTokenStyle(colors?: IConfig['colors'], typography?: TypographyConfig, scheme?: ResolvedColorScheme): CSSProperties;
25
29
  export declare function ThemeTokens({ config, }: {
26
- config?: Pick<IConfig, 'colors' | 'typography'>;
30
+ config?: Pick<IConfig, 'colors' | 'typography' | 'colorScheme'>;
27
31
  }): null;
32
+ /**
33
+ * Resolve `config.colorScheme` to the scheme to render. `'system'` tracks the
34
+ * OS setting live; anything else (including unset) is taken as given, with
35
+ * `'light'` as the default so existing integrations are unchanged.
36
+ */
37
+ export declare function useResolvedColorScheme(scheme?: ChatColorScheme): ResolvedColorScheme;
@@ -133,8 +133,23 @@ export interface ChatErrorInfo {
133
133
  export type ChatErrorFallbackRenderer = (context: ChatErrorInfo & {
134
134
  reset: () => void;
135
135
  }) => React.ReactNode;
136
+ export type ChatColorScheme = 'light' | 'dark' | 'system';
136
137
  export interface IConfig {
137
138
  appId?: string;
139
+ /**
140
+ * End-to-end encryption (OMEMO 2, XEP-0384).
141
+ *
142
+ * Off by default, and off means nothing changes: no keys are generated, no
143
+ * PEP node is touched, and stanzas are built exactly as before. With it on,
144
+ * the SDK publishes this device's keys after connecting and encrypts
145
+ * messages in rooms the backend marked `e2ee` - other rooms stay plain.
146
+ *
147
+ * Requires a backend that reports `e2ee` per chat and an XMPP server that
148
+ * lets members read each other's OMEMO PEP nodes (`access_model: open`).
149
+ */
150
+ e2ee?: {
151
+ enabled: boolean;
152
+ };
138
153
  disableHeader?: boolean;
139
154
  disableMedia?: boolean;
140
155
  /** Composer attachment limits. Applies to the built-in SendInput. */
@@ -162,6 +177,24 @@ export interface IConfig {
162
177
  /** Documents above this are not auto-rendered. Default 25. */
163
178
  maxFileSizeMb?: number;
164
179
  };
180
+ /**
181
+ * Light or dark UI. `'system'` follows the viewer's OS / browser setting
182
+ * (`prefers-color-scheme`) and updates live when it changes.
183
+ *
184
+ * Defaults to `'light'`, which is exactly the chat as it looked before
185
+ * this option existed. Surfaces, text, borders and bubbles switch to a
186
+ * dark palette; `colors.icons`, `colors.iconsBg`, the bubble/input
187
+ * colours and `backgroundChat` are used as given in both schemes.
188
+ * `colors.primary` is the exception: in dark it is lightened slightly so
189
+ * primary-coloured text and icons stay legible on the dark surface while
190
+ * buttons keep white text on them (see `--ethora-color-primary-text` for
191
+ * the text-only variant). Pass an already-dark-suitable `primary` if the
192
+ * exact brand value matters more. The
193
+ * scheme is scoped to the chat's own elements - the host page is not
194
+ * touched, so a host with its own dark mode should pass its resolved
195
+ * choice here.
196
+ */
197
+ colorScheme?: ChatColorScheme;
165
198
  colors?: {
166
199
  primary: string;
167
200
  secondary: string;
@@ -17,8 +17,35 @@ export interface IMessage {
17
17
  locationPreview?: string;
18
18
  mimetype?: string;
19
19
  location?: string;
20
+ attachmentId?: string;
20
21
  pending?: boolean;
21
22
  failed?: boolean;
23
+ /**
24
+ * Sent in clear in a room that is supposed to be end-to-end encrypted.
25
+ *
26
+ * Derived locally, never trusted from the sender: in an `e2ee` room any
27
+ * message that did not arrive inside an OMEMO payload is unprotected,
28
+ * whatever it claims about itself. The bubble marks these so nobody
29
+ * mistakes the room's badge for a guarantee about this message.
30
+ */
31
+ unencrypted?: boolean;
32
+ /**
33
+ * Attachments on this message are sealed; `clientEncrypted` is set on the
34
+ * stanza and these open them, one key per attachment, in attachment order.
35
+ *
36
+ * Lifted by getDataFromXml out of the OMEMO-decrypted <body>, which is the
37
+ * only part of a media stanza that is encrypted - so an undecryptable
38
+ * message has the ciphertext URLs and no keys, and stays unopenable.
39
+ */
40
+ e2eeKeys?: string[];
41
+ /**
42
+ * Set by the sender when this message's attachments are sealed. Stamped on
43
+ * <data>, which rides in the clear, so it survives a body that could not be
44
+ * decrypted - which is exactly when a renderer most needs to know the bytes
45
+ * are ciphertext rather than a corrupt file. A string, because it comes off
46
+ * an XML attribute.
47
+ */
48
+ clientEncrypted?: string;
22
49
  timestamp?: number;
23
50
  showInChannel?: string;
24
51
  activeMessage?: boolean;
@@ -27,6 +27,14 @@ export interface IRoom {
27
27
  roomBg: string;
28
28
  members?: RoomMember[];
29
29
  type?: 'public' | 'group' | 'private';
30
+ /**
31
+ * End-to-end encrypted room (OMEMO 2), as the backend reports it on
32
+ * `GET /v1/chats/my`. Set once when the room is created and never changed:
33
+ * flipping it would either leak plaintext into a room the other side reads
34
+ * as encrypted, or drop encryption the other side relies on. The SDK only
35
+ * runs its OMEMO path for rooms carrying this flag.
36
+ */
37
+ e2ee?: boolean;
30
38
  creteadAt?: string;
31
39
  appId?: string;
32
40
  createdAt?: string;
@@ -95,6 +103,8 @@ export interface ApiRoomLastMessage {
95
103
  export interface ApiRoom {
96
104
  name: string;
97
105
  type: 'public' | 'group' | 'private';
106
+ /** See IRoom.e2ee. Passed straight through by createRoomFromApi. */
107
+ e2ee?: boolean;
98
108
  title?: string;
99
109
  description?: string;
100
110
  picture?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ethora/chat-component",
3
- "version": "26.8.1",
3
+ "version": "26.9.0",
4
4
  "type": "module",
5
5
  "main": "dist/main.js",
6
6
  "types": "dist/main.d.ts",
@@ -49,6 +49,9 @@
49
49
  "@emoji-mart/react": "^1.1.1",
50
50
  "@livekit/components-react": "^2.9.20",
51
51
  "@livekit/components-styles": "^1.2.0",
52
+ "@noble/ciphers": "^2.4.0",
53
+ "@noble/curves": "^2.4.0",
54
+ "@noble/hashes": "^2.4.0",
52
55
  "@reduxjs/toolkit": "^2.8.2",
53
56
  "@types/styled-components": "^5.1.34",
54
57
  "@xmpp/client": "^0.13.6",