@experiaapp/webchat-react-native 2.0.1

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 (101) hide show
  1. package/README.md +254 -0
  2. package/app.plugin.js +6 -0
  3. package/lib/adapters/audio.d.ts +74 -0
  4. package/lib/adapters/audio.js +39 -0
  5. package/lib/adapters/audioRoute.d.ts +57 -0
  6. package/lib/adapters/audioRoute.js +77 -0
  7. package/lib/adapters/expoDefaults.d.ts +77 -0
  8. package/lib/adapters/expoDefaults.js +539 -0
  9. package/lib/adapters/picker.d.ts +67 -0
  10. package/lib/adapters/picker.js +37 -0
  11. package/lib/adapters/webrtc.d.ts +131 -0
  12. package/lib/adapters/webrtc.js +70 -0
  13. package/lib/core/VideoCallClient.d.ts +106 -0
  14. package/lib/core/VideoCallClient.js +302 -0
  15. package/lib/core/WebchatClient.d.ts +34 -0
  16. package/lib/core/WebchatClient.js +132 -0
  17. package/lib/core/configClient.d.ts +42 -0
  18. package/lib/core/configClient.js +302 -0
  19. package/lib/core/greet.d.ts +11 -0
  20. package/lib/core/greet.js +17 -0
  21. package/lib/core/ice.d.ts +31 -0
  22. package/lib/core/ice.js +48 -0
  23. package/lib/core/linkify.d.ts +11 -0
  24. package/lib/core/linkify.js +25 -0
  25. package/lib/core/logger.d.ts +17 -0
  26. package/lib/core/logger.js +53 -0
  27. package/lib/core/media.d.ts +52 -0
  28. package/lib/core/media.js +115 -0
  29. package/lib/core/mediaType.d.ts +21 -0
  30. package/lib/core/mediaType.js +66 -0
  31. package/lib/core/messagesReducer.d.ts +36 -0
  32. package/lib/core/messagesReducer.js +58 -0
  33. package/lib/core/persistence.d.ts +45 -0
  34. package/lib/core/persistence.js +63 -0
  35. package/lib/core/socketFactory.d.ts +16 -0
  36. package/lib/core/socketFactory.js +82 -0
  37. package/lib/core/types.d.ts +320 -0
  38. package/lib/core/types.js +30 -0
  39. package/lib/core/unread.d.ts +2 -0
  40. package/lib/core/unread.js +5 -0
  41. package/lib/i18n/ar.json +1 -0
  42. package/lib/i18n/en.json +1 -0
  43. package/lib/i18n/index.d.ts +7 -0
  44. package/lib/i18n/index.js +43 -0
  45. package/lib/index.d.ts +59 -0
  46. package/lib/index.js +142 -0
  47. package/lib/plugin/withWebchat.d.ts +53 -0
  48. package/lib/plugin/withWebchat.js +164 -0
  49. package/lib/state/WebchatProvider.d.ts +132 -0
  50. package/lib/state/WebchatProvider.js +906 -0
  51. package/lib/state/useWebchat.d.ts +1 -0
  52. package/lib/state/useWebchat.js +12 -0
  53. package/lib/theme/dir.d.ts +14 -0
  54. package/lib/theme/dir.js +20 -0
  55. package/lib/theme/themeFactory.d.ts +219 -0
  56. package/lib/theme/themeFactory.js +182 -0
  57. package/lib/ui/AttachButton.d.ts +35 -0
  58. package/lib/ui/AttachButton.js +26 -0
  59. package/lib/ui/AudioRecorder.d.ts +25 -0
  60. package/lib/ui/AudioRecorder.js +228 -0
  61. package/lib/ui/Bubble.d.ts +1 -0
  62. package/lib/ui/Bubble.js +265 -0
  63. package/lib/ui/CallControls.d.ts +27 -0
  64. package/lib/ui/CallControls.js +92 -0
  65. package/lib/ui/CallPlaceholder.d.ts +16 -0
  66. package/lib/ui/CallPlaceholder.js +73 -0
  67. package/lib/ui/Composer.d.ts +5 -0
  68. package/lib/ui/Composer.js +272 -0
  69. package/lib/ui/FileTile.d.ts +9 -0
  70. package/lib/ui/FileTile.js +31 -0
  71. package/lib/ui/Header.d.ts +52 -0
  72. package/lib/ui/Header.js +236 -0
  73. package/lib/ui/Icon.d.ts +21 -0
  74. package/lib/ui/Icon.js +110 -0
  75. package/lib/ui/ImageBubble.d.ts +11 -0
  76. package/lib/ui/ImageBubble.js +16 -0
  77. package/lib/ui/MediaUploadMenu.d.ts +23 -0
  78. package/lib/ui/MediaUploadMenu.js +68 -0
  79. package/lib/ui/MessageList.d.ts +1 -0
  80. package/lib/ui/MessageList.js +46 -0
  81. package/lib/ui/PoweredBy.d.ts +8 -0
  82. package/lib/ui/PoweredBy.js +14 -0
  83. package/lib/ui/PrechatForm.d.ts +1 -0
  84. package/lib/ui/PrechatForm.js +230 -0
  85. package/lib/ui/QuickReplies.d.ts +1 -0
  86. package/lib/ui/QuickReplies.js +24 -0
  87. package/lib/ui/TypingIndicator.d.ts +9 -0
  88. package/lib/ui/TypingIndicator.js +88 -0
  89. package/lib/ui/VideoBubble.d.ts +10 -0
  90. package/lib/ui/VideoBubble.js +130 -0
  91. package/lib/ui/VideoCall.d.ts +34 -0
  92. package/lib/ui/VideoCall.js +191 -0
  93. package/lib/ui/VideoTile.d.ts +25 -0
  94. package/lib/ui/VideoTile.js +13 -0
  95. package/lib/ui/VoiceMessage.d.ts +19 -0
  96. package/lib/ui/VoiceMessage.js +127 -0
  97. package/lib/ui/WebChat.d.ts +10 -0
  98. package/lib/ui/WebChat.js +386 -0
  99. package/lib/ui/openLink.d.ts +1 -0
  100. package/lib/ui/openLink.js +16 -0
  101. package/package.json +94 -0
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Expo config plugin — injects the media/mic/camera permissions the RN WebChat SDK needs.
3
+ *
4
+ * Consumers add this to their `app.json` / `app.config.js`:
5
+ *
6
+ * {
7
+ * "expo": {
8
+ * "plugins": [
9
+ * "@experiaapp/webchat-react-native",
10
+ * // ...or with options:
11
+ * ["@experiaapp/webchat-react-native", {
12
+ * "photoLibraryPermission": "Allow $(PRODUCT_NAME) to attach photos in chat.",
13
+ * "microphonePermission": "Allow $(PRODUCT_NAME) to record voice notes.",
14
+ * "cameraPermission": "Allow $(PRODUCT_NAME) to take photos / join video calls.",
15
+ * "backgroundCall": true // add FOREGROUND_SERVICE[_MICROPHONE] for bg video calls
16
+ * }]
17
+ * ]
18
+ * }
19
+ * }
20
+ *
21
+ * Permissions injected:
22
+ * iOS — NSPhotoLibraryUsageDescription, NSMicrophoneUsageDescription,
23
+ * NSCameraUsageDescription
24
+ * Android — CAMERA, RECORD_AUDIO, READ_MEDIA_IMAGES, READ_MEDIA_VIDEO,
25
+ * MODIFY_AUDIO_SETTINGS, READ_EXTERNAL_STORAGE (maxSdkVersion 32),
26
+ * and — only when `backgroundCall` is set — FOREGROUND_SERVICE +
27
+ * FOREGROUND_SERVICE_MICROPHONE.
28
+ *
29
+ * `@expo/config-plugins` is NOT a hard dependency of this SDK; it is provided by the Expo
30
+ * toolchain in the consumer's project. We resolve it at runtime via `require` so this file
31
+ * type-checks (and the package installs) even where Expo is absent. All Expo types are
32
+ * intentionally `any` to keep the SDK dependency-light.
33
+ */
34
+ export interface WithWebchatOptions {
35
+ /** iOS NSPhotoLibraryUsageDescription copy. */
36
+ photoLibraryPermission?: string;
37
+ /** iOS NSMicrophoneUsageDescription copy. */
38
+ microphonePermission?: string;
39
+ /** iOS NSCameraUsageDescription copy. */
40
+ cameraPermission?: string;
41
+ /**
42
+ * Add Android FOREGROUND_SERVICE + FOREGROUND_SERVICE_MICROPHONE so a video call can keep
43
+ * running while the app is backgrounded. Off by default — only opt in if you implement the
44
+ * background-call foreground service (Android 14+ requires the typed `_MICROPHONE` flavor).
45
+ */
46
+ backgroundCall?: boolean;
47
+ }
48
+ /**
49
+ * The config plugin. Typed loosely (`any`) because `@expo/config-plugins`' `ExpoConfig`
50
+ * type is not a dependency here. Returns the config unchanged when Expo is unavailable.
51
+ */
52
+ declare const withWebchat: (config: any, options?: WithWebchatOptions) => any;
53
+ export default withWebchat;
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ /**
3
+ * Expo config plugin — injects the media/mic/camera permissions the RN WebChat SDK needs.
4
+ *
5
+ * Consumers add this to their `app.json` / `app.config.js`:
6
+ *
7
+ * {
8
+ * "expo": {
9
+ * "plugins": [
10
+ * "@experiaapp/webchat-react-native",
11
+ * // ...or with options:
12
+ * ["@experiaapp/webchat-react-native", {
13
+ * "photoLibraryPermission": "Allow $(PRODUCT_NAME) to attach photos in chat.",
14
+ * "microphonePermission": "Allow $(PRODUCT_NAME) to record voice notes.",
15
+ * "cameraPermission": "Allow $(PRODUCT_NAME) to take photos / join video calls.",
16
+ * "backgroundCall": true // add FOREGROUND_SERVICE[_MICROPHONE] for bg video calls
17
+ * }]
18
+ * ]
19
+ * }
20
+ * }
21
+ *
22
+ * Permissions injected:
23
+ * iOS — NSPhotoLibraryUsageDescription, NSMicrophoneUsageDescription,
24
+ * NSCameraUsageDescription
25
+ * Android — CAMERA, RECORD_AUDIO, READ_MEDIA_IMAGES, READ_MEDIA_VIDEO,
26
+ * MODIFY_AUDIO_SETTINGS, READ_EXTERNAL_STORAGE (maxSdkVersion 32),
27
+ * and — only when `backgroundCall` is set — FOREGROUND_SERVICE +
28
+ * FOREGROUND_SERVICE_MICROPHONE.
29
+ *
30
+ * `@expo/config-plugins` is NOT a hard dependency of this SDK; it is provided by the Expo
31
+ * toolchain in the consumer's project. We resolve it at runtime via `require` so this file
32
+ * type-checks (and the package installs) even where Expo is absent. All Expo types are
33
+ * intentionally `any` to keep the SDK dependency-light.
34
+ */
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const DEFAULTS = {
37
+ photoLibraryPermission: "Allow $(PRODUCT_NAME) to access your photos so you can attach them in chat.",
38
+ microphonePermission: "Allow $(PRODUCT_NAME) to access your microphone so you can record voice notes and join video calls in chat.",
39
+ cameraPermission: "Allow $(PRODUCT_NAME) to access your camera so you can take photos and join video calls in chat.",
40
+ backgroundCall: false,
41
+ };
42
+ /** Plain Android permissions (no extra manifest attributes). */
43
+ const ANDROID_PERMISSIONS = [
44
+ "android.permission.CAMERA",
45
+ "android.permission.RECORD_AUDIO",
46
+ "android.permission.READ_MEDIA_IMAGES",
47
+ "android.permission.READ_MEDIA_VIDEO",
48
+ "android.permission.MODIFY_AUDIO_SETTINGS",
49
+ ];
50
+ /** Foreground-service permissions, only added when `backgroundCall` is enabled. */
51
+ const ANDROID_BACKGROUND_CALL_PERMISSIONS = [
52
+ "android.permission.FOREGROUND_SERVICE",
53
+ "android.permission.FOREGROUND_SERVICE_MICROPHONE",
54
+ ];
55
+ /**
56
+ * Permissions that carry extra manifest attributes (so the Expo `addPermission` helper —
57
+ * which only writes a bare `android:name` — can't express them). Merged by hand.
58
+ */
59
+ const ANDROID_PERMISSIONS_WITH_ATTRS = [
60
+ // Scoped storage: legacy read only matters up to Android 12L (API 32). API 33+ uses
61
+ // READ_MEDIA_IMAGES/VIDEO above, so cap this one to avoid a Play Console review flag.
62
+ {
63
+ name: "android.permission.READ_EXTERNAL_STORAGE",
64
+ attrs: { "android:maxSdkVersion": "32" },
65
+ },
66
+ ];
67
+ /**
68
+ * Lazily load `@expo/config-plugins`. Returns `null` when it cannot be resolved, so the
69
+ * plugin degrades to a no-op instead of crashing a project that doesn't use Expo prebuild.
70
+ */
71
+ function loadConfigPlugins() {
72
+ try {
73
+ // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
74
+ return require("@expo/config-plugins");
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ }
80
+ /** iOS: merge the three usage-description strings into Info.plist. */
81
+ function applyIos(config, opts, plugins) {
82
+ const { withInfoPlist } = plugins;
83
+ return withInfoPlist(config, (cfg) => {
84
+ const plist = cfg.modResults;
85
+ if (!plist.NSPhotoLibraryUsageDescription) {
86
+ plist.NSPhotoLibraryUsageDescription = opts.photoLibraryPermission;
87
+ }
88
+ if (!plist.NSMicrophoneUsageDescription) {
89
+ plist.NSMicrophoneUsageDescription = opts.microphonePermission;
90
+ }
91
+ if (!plist.NSCameraUsageDescription) {
92
+ plist.NSCameraUsageDescription = opts.cameraPermission;
93
+ }
94
+ return cfg;
95
+ });
96
+ }
97
+ /** Ensure the manifest has a `uses-permission` array and return it. */
98
+ function usesPermissionList(manifest) {
99
+ const root = manifest.manifest;
100
+ if (!Array.isArray(root["uses-permission"])) {
101
+ root["uses-permission"] = [];
102
+ }
103
+ return root["uses-permission"];
104
+ }
105
+ function hasPermission(list, name) {
106
+ return list.some((entry) => (entry === null || entry === void 0 ? void 0 : entry.$) && entry.$["android:name"] === name);
107
+ }
108
+ /** Android: add the media/mic/camera permissions to AndroidManifest.xml. */
109
+ function applyAndroid(config, opts, plugins) {
110
+ const { withAndroidManifest, AndroidConfig } = plugins;
111
+ return withAndroidManifest(config, (cfg) => {
112
+ const plain = [
113
+ ...ANDROID_PERMISSIONS,
114
+ ...(opts.backgroundCall ? ANDROID_BACKGROUND_CALL_PERMISSIONS : []),
115
+ ];
116
+ // Bare-name permissions: prefer Expo's helper when present.
117
+ const addPermission = AndroidConfig &&
118
+ AndroidConfig.Permissions &&
119
+ AndroidConfig.Permissions.addPermission;
120
+ if (typeof addPermission === "function") {
121
+ for (const perm of plain) {
122
+ addPermission(cfg.modResults, perm);
123
+ }
124
+ }
125
+ else {
126
+ const list = usesPermissionList(cfg.modResults);
127
+ for (const perm of plain) {
128
+ if (!hasPermission(list, perm)) {
129
+ list.push({ $: { "android:name": perm } });
130
+ }
131
+ }
132
+ }
133
+ // Attribute-carrying permissions (e.g. maxSdkVersion) always need a manual merge —
134
+ // the helper can only write a bare android:name.
135
+ const list = usesPermissionList(cfg.modResults);
136
+ for (const { name, attrs } of ANDROID_PERMISSIONS_WITH_ATTRS) {
137
+ const existing = list.find((entry) => (entry === null || entry === void 0 ? void 0 : entry.$) && entry.$["android:name"] === name);
138
+ if (existing) {
139
+ existing.$ = { ...existing.$, ...attrs };
140
+ }
141
+ else {
142
+ list.push({ $: { "android:name": name, ...attrs } });
143
+ }
144
+ }
145
+ return cfg;
146
+ });
147
+ }
148
+ /**
149
+ * The config plugin. Typed loosely (`any`) because `@expo/config-plugins`' `ExpoConfig`
150
+ * type is not a dependency here. Returns the config unchanged when Expo is unavailable.
151
+ */
152
+ const withWebchat = (config, options) => {
153
+ const opts = { ...DEFAULTS, ...(options || {}) };
154
+ const plugins = loadConfigPlugins();
155
+ if (!plugins) {
156
+ // No Expo prebuild context — nothing to inject. Bare projects edit native files directly.
157
+ return config;
158
+ }
159
+ let next = config;
160
+ next = applyIos(next, opts, plugins);
161
+ next = applyAndroid(next, opts, plugins);
162
+ return next;
163
+ };
164
+ exports.default = withWebchat;
@@ -0,0 +1,132 @@
1
+ import React from "react";
2
+ import type { MinimalSocket } from "../core/WebchatClient";
3
+ import type { Message, Config, MessageReaction } from "../core/types";
4
+ import type { WebChatTheme } from "../theme/themeFactory";
5
+ import type { Dir } from "../theme/dir";
6
+ import type { Asset } from "../adapters/picker";
7
+ import type { AudioAdapter } from "../adapters/audio";
8
+ import { type VideoCallClient } from "../core/VideoCallClient";
9
+ /**
10
+ * Phase 3 video seam (Group E). The audio-route surface the provider brackets a
11
+ * call with — `AudioRoute` (`adapters/audioRoute`) satisfies it. Kept structural
12
+ * so a spy `{ start, stop }` works in tests and the provider never imports a
13
+ * native module unless a real call is actually placed.
14
+ */
15
+ export interface AudioRouteLike {
16
+ start(opts?: {
17
+ media?: "video" | "audio";
18
+ }): void;
19
+ stop(): void;
20
+ setSpeaker?(on: boolean): void;
21
+ }
22
+ /**
23
+ * #5 — object form for {@link Ctx.send}. A bare string is shorthand for
24
+ * `{ text }`. `payload` is what reaches the backend as the message body (display
25
+ * `text` shown in the bubble); `metadata` is forwarded untouched on the
26
+ * OutgoingPayload (S9).
27
+ */
28
+ export type SendInput = string | {
29
+ text?: string;
30
+ payload?: string;
31
+ metadata?: Record<string, unknown>;
32
+ };
33
+ export interface Ctx {
34
+ messages: Message[];
35
+ status: string;
36
+ isStorageReady: boolean;
37
+ unread: number;
38
+ /**
39
+ * Item 23 — "awaiting a bot reply" state that gates the typing indicator. True
40
+ * when the most recent message in the thread was sent by the user and no bot
41
+ * (`response`) message has arrived since (the SDK-faithful analog of the web
42
+ * trigger, which flips `displayTypingIndication` while the agent is composing).
43
+ * The MessageList renders <TypingIndicator> when this is true AND
44
+ * `resolvedConfig.displayTypingIndication` is on.
45
+ */
46
+ awaitingReply: boolean;
47
+ /**
48
+ * Item 34 — has a connection EVER been established this mount? Latches true the
49
+ * first time the status reaches "connected"/"session-confirmed" and never resets.
50
+ * Consumers (the Surface/Launcher, when `config.hideWhenNotConnected` is set) use
51
+ * it to distinguish the INITIAL pre-connect (never connected -> keep visible so the
52
+ * user can open to connect) from a connect-then-disconnect drop (hide while down).
53
+ */
54
+ wasConnected: boolean;
55
+ /**
56
+ * Web parity (ChatContainer `!sessionId` gate): true when the CURRENT session was
57
+ * RESUMED from a stored id (a returning user), false for a brand-new session. The
58
+ * prechat form is shown only for a NEW session — a resume continues the existing
59
+ * conversation without re-asking. Latched at hydrate (restored.sessionId present),
60
+ * cleared by resetSession (which forces a brand-new session).
61
+ */
62
+ resumedSession: boolean;
63
+ /**
64
+ * Injected playback adapter for received voice notes (audit #23). When present,
65
+ * the live MessageList/Bubble render an inline <VoiceMessage> player for audio
66
+ * attachments instead of a download FileTile. Undefined for chat-only consumers
67
+ * that never wire an audio surface.
68
+ */
69
+ audioAdapter?: AudioAdapter;
70
+ /**
71
+ * Route the active call's audio output (speakerphone <-> earpiece, B14/audit #25).
72
+ * Delegates to the provider's REAL AudioRoute (audioRouteRef, started in startCall);
73
+ * a no-op when no call is active. The live VideoCall surface wires its speaker
74
+ * toggle to this so the toggle reaches the provider's audio session, not a stub.
75
+ */
76
+ setSpeaker: (on: boolean) => void;
77
+ /** Merged config (prop > server > default) — the single source of truth. */
78
+ resolvedConfig: Config;
79
+ /** Active theme = buildTheme(variant, publicStyle). */
80
+ theme: WebChatTheme;
81
+ /** Reading direction derived from language ('ar' -> 'rtl', else 'ltr'). */
82
+ dir: Dir;
83
+ /** Resolved language (prop > server > localize default). */
84
+ language: string;
85
+ send: (input: SendInput) => void;
86
+ /**
87
+ * Phase 2 — send picked/recorded media. Creates an optimistic user message that
88
+ * transitions queued → encoding → sending, emits `user_uttered` with the
89
+ * base64 `media[]` + a stable `messageKey`, and reconciles to `sent` (URL
90
+ * patched, base64 stripped) on the server echo. Rejected/failed assets surface
91
+ * a typed {@link WebChatError} via the `onError` prop.
92
+ */
93
+ attach: (assets: Asset[]) => Promise<void>;
94
+ clearHistory: () => void;
95
+ resetSession: () => void;
96
+ /**
97
+ * Section 4 — apply a like/dislike feedback reaction to a RECEIVED message (web
98
+ * parity). `messageKey` is the SERVER id (not the SDK's client `key`). Mutually
99
+ * exclusive + toggle-clear: clicking the ACTIVE reaction clears it. Reads the
100
+ * message's current `userReaction`, computes the next state + the backend verb
101
+ * (`like`/`unlike`/`dislike`/`undislike`), dispatches `REACT` optimistically, and
102
+ * emits `socket.emit("activity", { messageKey, type })`. The server reflection on
103
+ * `"activityAck"` reconciles the stored reaction.
104
+ */
105
+ react: (messageKey: string, reaction: MessageReaction) => void;
106
+ /** True while a 1:1 video call is active (Surface renders <VideoCall> instead of the thread). */
107
+ videoCallStarted: boolean;
108
+ /**
109
+ * Place a 1:1 video call over the EXISTING chat socket (no second connection).
110
+ * Builds a {@link VideoCallClient} bound to the live socket + the current
111
+ * `session_id` (web parity, C8), starts the audio route (speakerphone default,
112
+ * B14) and the answerer-only signaling. No-op if a call is already up or the
113
+ * socket isn't ready yet.
114
+ */
115
+ startCall: () => void;
116
+ /** Tear the active call down: leave_room, release camera/mic, restore audio. Idempotent. */
117
+ endCall: () => void;
118
+ /**
119
+ * Internal seam for the WebChat Surface: the live socket + session accessor +
120
+ * the already-built {@link VideoCallClient}, so `<VideoCall>` renders as a view
121
+ * over the provider's single client (it does NOT build a second one). Null
122
+ * until {@link startCall} runs. Consumers should use `videoCallStarted`/
123
+ * `startCall`/`endCall`; this is wiring, not public surface.
124
+ */
125
+ _video?: {
126
+ socket: MinimalSocket | null;
127
+ getSessionId: () => string;
128
+ client: VideoCallClient | null;
129
+ };
130
+ }
131
+ export declare const WebchatContext: React.Context<Ctx | null>;
132
+ export declare function WebchatProvider(props: any): import("react/jsx-runtime").JSX.Element;