@sagepilot-ai/react-native-sdk 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,1441 @@
1
+ // src/core/errors/SagepilotChatError.ts
2
+ var SagepilotChatError = class extends Error {
3
+ constructor(code, message, options = {}) {
4
+ super(message);
5
+ this.name = "SagepilotChatError";
6
+ this.code = code;
7
+ this.status = options.status;
8
+ this.requestId = options.requestId;
9
+ this.details = options.details;
10
+ if (options.cause) {
11
+ this.cause = options.cause;
12
+ }
13
+ }
14
+ };
15
+
16
+ // src/core/config/constants.ts
17
+ var SDK_NAME = "@sagepilot-ai/react-native-sdk";
18
+ var SDK_VERSION = "0.1.0";
19
+ var DEFAULT_HOST = "https://app.sagepilot.ai";
20
+ var DEFAULT_WIDGET_HOST = "https://app.sagepilot.ai";
21
+ var CUSTOMER_API_PREFIX = "/customer-api/v1";
22
+ var DEFAULT_UNREAD_POLL_INTERVAL_MS = 3e4;
23
+ var DEFAULT_HOSTED_CLAIMS_STORAGE_KEY_PREFIX = "sg_chat_claims";
24
+
25
+ // src/core/auth/sessionManager.ts
26
+ var SagepilotSessionManager = class {
27
+ constructor(tokenManager) {
28
+ this.tokenManager = tokenManager;
29
+ this.currentSession = null;
30
+ }
31
+ async hydrate() {
32
+ this.currentSession = await this.tokenManager.getSession();
33
+ return this.currentSession;
34
+ }
35
+ getSession() {
36
+ return this.currentSession;
37
+ }
38
+ requireSession() {
39
+ if (!this.currentSession?.session_id || !this.currentSession.session_token) {
40
+ throw new SagepilotChatError(
41
+ "not_initialized",
42
+ `${SDK_NAME} does not have an active customer session. Call configure() first.`
43
+ );
44
+ }
45
+ return this.currentSession;
46
+ }
47
+ async setSession(session) {
48
+ this.currentSession = {
49
+ session_id: session.session_id,
50
+ session_token: session.session_token,
51
+ customer_id: session.customer_id ?? null,
52
+ conversation_id: session.conversation_id ?? null,
53
+ authenticated: session.authenticated ?? false,
54
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
55
+ };
56
+ await this.tokenManager.setSession(this.currentSession);
57
+ return this.currentSession;
58
+ }
59
+ async updateSession(updates) {
60
+ const session = this.requireSession();
61
+ return this.setSession({
62
+ ...session,
63
+ ...updates
64
+ });
65
+ }
66
+ async clearSession() {
67
+ this.currentSession = null;
68
+ await this.tokenManager.clearSession();
69
+ }
70
+ getAuthorizationHeader(sessionToken = this.requireSession().session_token) {
71
+ return this.tokenManager.createAuthorizationHeader(sessionToken);
72
+ }
73
+ };
74
+
75
+ // src/core/storage/tokenStorage.ts
76
+ function createMemoryTokenStorage() {
77
+ const records = /* @__PURE__ */ new Map();
78
+ return {
79
+ async getItem(key) {
80
+ return records.get(key) ?? null;
81
+ },
82
+ async setItem(key, value) {
83
+ records.set(key, value);
84
+ },
85
+ async removeItem(key) {
86
+ records.delete(key);
87
+ }
88
+ };
89
+ }
90
+ function createKeychainTokenStorage(keychain, options = {}) {
91
+ return {
92
+ async getItem(key) {
93
+ const result = await keychain.getGenericPassword({
94
+ ...options,
95
+ service: key
96
+ });
97
+ return result ? result.password : null;
98
+ },
99
+ async setItem(key, value) {
100
+ await keychain.setGenericPassword("sagepilot", value, {
101
+ ...options,
102
+ service: key
103
+ });
104
+ },
105
+ async removeItem(key) {
106
+ await keychain.resetGenericPassword({
107
+ ...options,
108
+ service: key
109
+ });
110
+ }
111
+ };
112
+ }
113
+ function serializeSessionTokenRecord(record) {
114
+ return JSON.stringify(record);
115
+ }
116
+ function parseSessionTokenRecord(value) {
117
+ if (!value) return null;
118
+ try {
119
+ const record = JSON.parse(value);
120
+ if (!record.session_id || !record.session_token) return null;
121
+ return {
122
+ session_id: record.session_id,
123
+ session_token: record.session_token,
124
+ customer_id: record.customer_id ?? null,
125
+ conversation_id: record.conversation_id ?? null,
126
+ authenticated: record.authenticated ?? false,
127
+ updated_at: record.updated_at
128
+ };
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+
134
+ // src/core/auth/tokenManager.ts
135
+ function getSessionStorageKey(channelId) {
136
+ return `sagepilot_rn_session_${channelId}`;
137
+ }
138
+ var SagepilotTokenManager = class {
139
+ constructor(channelId, storage) {
140
+ this.channelId = channelId;
141
+ this.storage = storage;
142
+ }
143
+ async getSession() {
144
+ return parseSessionTokenRecord(await this.storage.getItem(getSessionStorageKey(this.channelId)));
145
+ }
146
+ async setSession(session) {
147
+ await this.storage.setItem(
148
+ getSessionStorageKey(this.channelId),
149
+ serializeSessionTokenRecord({
150
+ session_id: session.session_id,
151
+ session_token: session.session_token,
152
+ customer_id: session.customer_id ?? null,
153
+ conversation_id: session.conversation_id ?? null,
154
+ authenticated: session.authenticated ?? false,
155
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
156
+ })
157
+ );
158
+ }
159
+ async clearSession() {
160
+ await this.storage.removeItem(getSessionStorageKey(this.channelId));
161
+ }
162
+ createAuthorizationHeader(sessionToken) {
163
+ return {
164
+ Authorization: `Bearer ${sessionToken}`
165
+ };
166
+ }
167
+ };
168
+
169
+ // src/core/config/resolveConfig.ts
170
+ function normalizeHost(host = DEFAULT_HOST) {
171
+ return host.replace(/\/+$/, "");
172
+ }
173
+ function readRuntimeEnv(name) {
174
+ return globalThis.process?.env?.[name]?.trim();
175
+ }
176
+ function resolveSagepilotHost(configuredHost) {
177
+ const envHost = readRuntimeEnv("EXPO_PUBLIC_SAGEPILOT_HOST") || readRuntimeEnv("SAGEPILOT_HOST") || readRuntimeEnv("EXPO_PUBLIC_SAGEPILOT_CUSTOMER_API_BASE_URL") || readRuntimeEnv("SAGEPILOT_CUSTOMER_API_BASE_URL");
178
+ return normalizeHost(configuredHost || envHost);
179
+ }
180
+ function resolveCustomerApiHost(configuredApiHost, fallbackHost) {
181
+ const envHost = readRuntimeEnv("EXPO_PUBLIC_SAGEPILOT_CUSTOMER_API_BASE_URL") || readRuntimeEnv("SAGEPILOT_CUSTOMER_API_BASE_URL");
182
+ return normalizeHost(configuredApiHost || envHost || fallbackHost);
183
+ }
184
+ function parseKey(key) {
185
+ const [workspaceId, channelId] = key.split(":");
186
+ if (!workspaceId || !channelId) {
187
+ throw new SagepilotChatError(
188
+ "invalid_config",
189
+ `${SDK_NAME} Invalid key format. Expected "workspace_id:channel_id".`
190
+ );
191
+ }
192
+ return { workspaceId, channelId };
193
+ }
194
+ function resolveFetch(fetchImpl) {
195
+ const resolvedFetch = fetchImpl ?? globalThis.fetch;
196
+ if (typeof resolvedFetch !== "function") {
197
+ throw new SagepilotChatError(
198
+ "invalid_config",
199
+ `${SDK_NAME} requires a fetch implementation in this runtime.`
200
+ );
201
+ }
202
+ return resolvedFetch.bind(globalThis);
203
+ }
204
+
205
+ // src/core/http/retryPolicy.ts
206
+ var DEFAULT_RETRY_POLICY = {
207
+ retries: 2,
208
+ baseDelayMs: 250,
209
+ maxDelayMs: 2e3,
210
+ retryOnStatuses: [408, 425, 429, 500, 502, 503, 504]
211
+ };
212
+ function resolveRetryPolicy(policy) {
213
+ return {
214
+ retries: policy?.retries ?? DEFAULT_RETRY_POLICY.retries,
215
+ baseDelayMs: policy?.baseDelayMs ?? DEFAULT_RETRY_POLICY.baseDelayMs,
216
+ maxDelayMs: policy?.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs,
217
+ retryOnStatuses: policy?.retryOnStatuses ?? DEFAULT_RETRY_POLICY.retryOnStatuses
218
+ };
219
+ }
220
+ function shouldRetryStatus(status, policy) {
221
+ return policy.retryOnStatuses.includes(status);
222
+ }
223
+ function getRetryDelayMs(attempt, policy) {
224
+ const exponentialDelay = policy.baseDelayMs * 2 ** Math.max(attempt - 1, 0);
225
+ return Math.min(exponentialDelay, policy.maxDelayMs);
226
+ }
227
+ function wait(ms) {
228
+ return new Promise((resolve) => setTimeout(resolve, ms));
229
+ }
230
+
231
+ // src/core/http/client.ts
232
+ var RETRYABLE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
233
+ async function readResponseJson(response) {
234
+ if (response.status === 204 || response.status === 205) {
235
+ return void 0;
236
+ }
237
+ try {
238
+ const text = await response.text();
239
+ if (!text) return void 0;
240
+ return JSON.parse(text);
241
+ } catch (error) {
242
+ throw new SagepilotChatError(
243
+ "invalid_response",
244
+ `${SDK_NAME} API returned invalid JSON.`,
245
+ { status: response.status, cause: error }
246
+ );
247
+ }
248
+ }
249
+ function normalizeErrorCode(code) {
250
+ return code || "invalid_response";
251
+ }
252
+ function toHeaders(headers) {
253
+ return new Headers(headers);
254
+ }
255
+ function mergeHeaders(baseHeaders, requestHeaders) {
256
+ const headers = toHeaders(baseHeaders);
257
+ const extraHeaders = toHeaders(requestHeaders);
258
+ extraHeaders.forEach((value, key) => {
259
+ headers.set(key, value);
260
+ });
261
+ return headers;
262
+ }
263
+ function withJsonHeaders(baseHeaders, requestHeaders) {
264
+ const headers = mergeHeaders(baseHeaders, requestHeaders);
265
+ if (!headers.has("Content-Type")) {
266
+ headers.set("Content-Type", "application/json");
267
+ }
268
+ return headers;
269
+ }
270
+ var SagepilotHttpClient = class {
271
+ constructor(options) {
272
+ this.fetchImpl = options.fetch;
273
+ this.baseHeaders = options.headers;
274
+ this.retryPolicy = resolveRetryPolicy(options.retryPolicy);
275
+ }
276
+ async request(url, init) {
277
+ const policy = resolveRetryPolicy(init.retryPolicy ?? this.retryPolicy);
278
+ const method = (init.method ?? "GET").toUpperCase();
279
+ const canRetry = RETRYABLE_METHODS.has(method);
280
+ let lastNetworkError;
281
+ for (let attempt = 0; attempt <= policy.retries; attempt += 1) {
282
+ let response;
283
+ try {
284
+ response = await this.fetchImpl(url, {
285
+ ...init,
286
+ headers: mergeHeaders(this.baseHeaders, init.headers)
287
+ });
288
+ } catch (error) {
289
+ lastNetworkError = error;
290
+ if (canRetry && attempt < policy.retries) {
291
+ await wait(getRetryDelayMs(attempt + 1, policy));
292
+ continue;
293
+ }
294
+ throw new SagepilotChatError("network_error", init.errorMessage, { cause: error });
295
+ }
296
+ if (canRetry && !response.ok && attempt < policy.retries && shouldRetryStatus(response.status, policy)) {
297
+ await wait(getRetryDelayMs(attempt + 1, policy));
298
+ continue;
299
+ }
300
+ const body = await readResponseJson(response);
301
+ if (!response.ok) {
302
+ const errorBody = body;
303
+ const apiError = errorBody.error;
304
+ const message = typeof apiError === "string" ? apiError : apiError?.message || init.errorMessage;
305
+ throw new SagepilotChatError(
306
+ typeof apiError === "string" ? "invalid_response" : normalizeErrorCode(apiError?.code),
307
+ message,
308
+ {
309
+ status: response.status,
310
+ requestId: typeof apiError === "string" ? void 0 : apiError?.request_id,
311
+ details: typeof apiError === "string" ? void 0 : apiError?.details
312
+ }
313
+ );
314
+ }
315
+ return body;
316
+ }
317
+ throw new SagepilotChatError("network_error", init.errorMessage, { cause: lastNetworkError });
318
+ }
319
+ };
320
+
321
+ // src/core/native/deviceInfo.ts
322
+ async function resolveDeviceInfo(adapter) {
323
+ if (!adapter) return {};
324
+ return adapter.getDeviceInfo();
325
+ }
326
+
327
+ // src/resources/channels.ts
328
+ function bootstrapChannel(http, host, channelId) {
329
+ return http.request(
330
+ `${host}${CUSTOMER_API_PREFIX}/channels/${encodeURIComponent(channelId)}/bootstrap`,
331
+ {
332
+ method: "GET",
333
+ errorMessage: `${SDK_NAME} Failed to load channel configuration.`
334
+ }
335
+ );
336
+ }
337
+
338
+ // src/resources/sessions.ts
339
+ function createSession(http, host, channelId, input) {
340
+ return http.request(
341
+ `${host}${CUSTOMER_API_PREFIX}/sessions`,
342
+ {
343
+ method: "POST",
344
+ headers: withJsonHeaders(void 0),
345
+ body: JSON.stringify({
346
+ channel_id: channelId,
347
+ anonymous_id: input.anonymousId,
348
+ metadata: {
349
+ platform: "react_native",
350
+ sdk: SDK_NAME,
351
+ sdk_version: SDK_VERSION,
352
+ ...input.metadata
353
+ }
354
+ }),
355
+ errorMessage: `${SDK_NAME} Failed to create customer session.`
356
+ }
357
+ );
358
+ }
359
+ function getSession(http, host, sessionId, authorizationHeader) {
360
+ return http.request(
361
+ `${host}${CUSTOMER_API_PREFIX}/sessions/${encodeURIComponent(sessionId)}`,
362
+ {
363
+ method: "GET",
364
+ headers: authorizationHeader,
365
+ errorMessage: `${SDK_NAME} Failed to load customer session.`
366
+ }
367
+ );
368
+ }
369
+ function identifySession(http, host, channelId, sessionId, authorizationHeader, identity) {
370
+ return http.request(
371
+ `${host}${CUSTOMER_API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/identify`,
372
+ {
373
+ method: "POST",
374
+ headers: withJsonHeaders(authorizationHeader),
375
+ body: JSON.stringify({
376
+ channel_id: channelId,
377
+ identity
378
+ }),
379
+ errorMessage: `${SDK_NAME} Failed to identify customer.`
380
+ }
381
+ );
382
+ }
383
+ function logoutSession(http, host, sessionId, authorizationHeader) {
384
+ return http.request(
385
+ `${host}${CUSTOMER_API_PREFIX}/sessions/${encodeURIComponent(sessionId)}/logout`,
386
+ {
387
+ method: "POST",
388
+ headers: authorizationHeader,
389
+ errorMessage: `${SDK_NAME} Failed to logout customer session.`
390
+ }
391
+ );
392
+ }
393
+
394
+ // src/resources/unread.ts
395
+ function fetchUnreadCount(http, host, channelId, sessionId, authorizationHeader) {
396
+ const url = new URL(`${host}${CUSTOMER_API_PREFIX}/unread-count`);
397
+ url.searchParams.set("channel_id", channelId);
398
+ url.searchParams.set("session_id", sessionId);
399
+ return http.request(
400
+ url.toString(),
401
+ {
402
+ method: "GET",
403
+ headers: authorizationHeader,
404
+ errorMessage: `${SDK_NAME} Failed to fetch unread count.`
405
+ }
406
+ );
407
+ }
408
+
409
+ // src/public/SdkClient.ts
410
+ var DEFAULT_MOBILE_LAUNCHER_CONFIG = {
411
+ label: "Chat",
412
+ buttonColor: "#173c2d",
413
+ pressedButtonColor: "#225340",
414
+ disabledButtonColor: "#d8cdbb",
415
+ borderColor: "#2a5e49",
416
+ disabledBorderColor: "#cbbda9",
417
+ iconColor: "#fff8ef",
418
+ iconInsideColor: "#173c2d",
419
+ labelColor: "#d4e9dc",
420
+ disabledContentColor: "#8d8172",
421
+ unreadBadgeColor: "#e76f51",
422
+ unreadBadgeTextColor: "#ffffff",
423
+ unreadBadgeBorderColor: "#efe4d4"
424
+ };
425
+ function resolveMobileLauncherConfig(theme) {
426
+ const launcher = theme?.launcher ?? {};
427
+ const accentColor = theme?.accentColor;
428
+ const primaryColor = launcher.buttonColor ?? accentColor ?? DEFAULT_MOBILE_LAUNCHER_CONFIG.buttonColor;
429
+ return {
430
+ label: launcher.label ?? DEFAULT_MOBILE_LAUNCHER_CONFIG.label,
431
+ buttonColor: primaryColor,
432
+ pressedButtonColor: launcher.pressedButtonColor ?? primaryColor,
433
+ disabledButtonColor: launcher.disabledButtonColor ?? DEFAULT_MOBILE_LAUNCHER_CONFIG.disabledButtonColor,
434
+ borderColor: launcher.borderColor ?? primaryColor,
435
+ disabledBorderColor: launcher.disabledBorderColor ?? DEFAULT_MOBILE_LAUNCHER_CONFIG.disabledBorderColor,
436
+ iconColor: launcher.iconColor ?? DEFAULT_MOBILE_LAUNCHER_CONFIG.iconColor,
437
+ iconInsideColor: launcher.iconInsideColor ?? primaryColor,
438
+ labelColor: launcher.labelColor ?? DEFAULT_MOBILE_LAUNCHER_CONFIG.labelColor,
439
+ disabledContentColor: launcher.disabledContentColor ?? DEFAULT_MOBILE_LAUNCHER_CONFIG.disabledContentColor,
440
+ unreadBadgeColor: launcher.unreadBadgeColor ?? DEFAULT_MOBILE_LAUNCHER_CONFIG.unreadBadgeColor,
441
+ unreadBadgeTextColor: launcher.unreadBadgeTextColor ?? DEFAULT_MOBILE_LAUNCHER_CONFIG.unreadBadgeTextColor,
442
+ unreadBadgeBorderColor: launcher.unreadBadgeBorderColor ?? DEFAULT_MOBILE_LAUNCHER_CONFIG.unreadBadgeBorderColor
443
+ };
444
+ }
445
+ function resolveMobileThemeState(theme) {
446
+ return {
447
+ accentColor: theme?.accentColor,
448
+ logoUrl: theme?.logoUrl,
449
+ preferredColorScheme: theme?.preferredColorScheme,
450
+ launcher: resolveMobileLauncherConfig(theme)
451
+ };
452
+ }
453
+ function normalizeIdentity(identity) {
454
+ const userId = identity?.userId ?? identity?.user_id;
455
+ if (!userId) {
456
+ throw new SagepilotChatError("invalid_config", `${SDK_NAME} identify() requires userId.`);
457
+ }
458
+ return {
459
+ user_id: userId,
460
+ email: identity.email,
461
+ name: identity.name,
462
+ phone: identity.phone,
463
+ custom_properties: identity.customProperties ?? identity.custom_properties,
464
+ user_hash: identity.userHash ?? identity.user_hash
465
+ };
466
+ }
467
+ function toPublicSessionState(session) {
468
+ return {
469
+ session_id: session.session_id,
470
+ customer_id: session.customer_id ?? null,
471
+ conversation_id: session.conversation_id ?? null,
472
+ authenticated: session.authenticated ?? false,
473
+ updated_at: "updated_at" in session ? session.updated_at : void 0
474
+ };
475
+ }
476
+ var SagepilotReactNativeChat = class {
477
+ constructor() {
478
+ this.sdkName = SDK_NAME;
479
+ this.runtimeOptions = null;
480
+ this.http = null;
481
+ this.sessionManager = null;
482
+ this.apiHost = "";
483
+ this.widgetHost = "";
484
+ this.workspaceId = "";
485
+ this.channelId = "";
486
+ this.channel = null;
487
+ this.session = null;
488
+ this.customer = null;
489
+ this.legacyWidgetJwt = null;
490
+ this.lastIdentifyResult = null;
491
+ this.identified = false;
492
+ this.identifyPending = false;
493
+ this.isConfigured = false;
494
+ this.presented = false;
495
+ this.hostedChatView = { screen: "home" };
496
+ this.unreadCount = 0;
497
+ this.unreadPollTimer = null;
498
+ this.stateCallbacks = /* @__PURE__ */ new Set();
499
+ this.identifyCallbacks = /* @__PURE__ */ new Set();
500
+ this.unreadCallbacks = /* @__PURE__ */ new Set();
501
+ this.readyCallbacks = /* @__PURE__ */ new Set();
502
+ this.presentCallbacks = /* @__PURE__ */ new Set();
503
+ this.dismissCallbacks = /* @__PURE__ */ new Set();
504
+ this.errorCallbacks = /* @__PURE__ */ new Set();
505
+ }
506
+ async configure(config) {
507
+ if (this.isConfigured) {
508
+ throw new SagepilotChatError(
509
+ "already_initialized",
510
+ `${SDK_NAME} is already configured. Call destroy() before configuring again.`
511
+ );
512
+ }
513
+ if (!config?.key || typeof config.key !== "string") {
514
+ throw new SagepilotChatError(
515
+ "invalid_config",
516
+ `${SDK_NAME} requires a key in the format "workspace_id:channel_id".`
517
+ );
518
+ }
519
+ const { workspaceId, channelId } = parseKey(config.key);
520
+ this.runtimeOptions = {
521
+ behavior: config.behavior,
522
+ presentation: config.presentation,
523
+ theme: config.theme,
524
+ hostedClaimsStorageKeyPrefix: config.hostedClaimsStorageKeyPrefix || DEFAULT_HOSTED_CLAIMS_STORAGE_KEY_PREFIX
525
+ };
526
+ const fetchImpl = resolveFetch(config.fetch);
527
+ const publicHost = resolveSagepilotHost(config.host);
528
+ this.widgetHost = normalizeHost(config.widgetHost || DEFAULT_WIDGET_HOST);
529
+ this.apiHost = resolveCustomerApiHost(config.apiHost, publicHost);
530
+ this.workspaceId = workspaceId;
531
+ this.channelId = channelId;
532
+ this.http = new SagepilotHttpClient({
533
+ fetch: fetchImpl,
534
+ headers: config.headers
535
+ });
536
+ this.sessionManager = new SagepilotSessionManager(
537
+ new SagepilotTokenManager(this.channelId, config.tokenStorage ?? createMemoryTokenStorage())
538
+ );
539
+ try {
540
+ const channel = await bootstrapChannel(this.requireHttp(), this.apiHost, this.channelId);
541
+ if (channel.workspace_id !== this.workspaceId) {
542
+ throw new SagepilotChatError(
543
+ "forbidden",
544
+ `${SDK_NAME} The workspace id in the key does not match this channel.`,
545
+ { details: { channel_id: this.channelId } }
546
+ );
547
+ }
548
+ const session = await this.resolveCustomerSession(config);
549
+ this.channel = channel;
550
+ this.session = session;
551
+ this.isConfigured = true;
552
+ this.emitReady();
553
+ this.emitState();
554
+ if (config.behavior?.enableUnreadPolling ?? true) {
555
+ this.startUnreadPolling(config.behavior?.unreadPollIntervalMs);
556
+ }
557
+ return { channel, session: toPublicSessionState(session) };
558
+ } catch (error) {
559
+ this.resetRuntimeState();
560
+ this.emitError(error);
561
+ throw error;
562
+ }
563
+ }
564
+ async getSession() {
565
+ const session = this.requireSession();
566
+ const liveSession = await getSession(
567
+ this.requireHttp(),
568
+ this.apiHost,
569
+ session.session_id,
570
+ this.requireSessionManager().getAuthorizationHeader(session.session_token)
571
+ );
572
+ return toPublicSessionState({
573
+ ...session,
574
+ customer_id: liveSession.customer_id,
575
+ conversation_id: liveSession.conversation_id,
576
+ authenticated: liveSession.authenticated ?? session.authenticated ?? false
577
+ });
578
+ }
579
+ async identify(identity) {
580
+ const session = this.requireSession();
581
+ const apiIdentity = normalizeIdentity(identity);
582
+ this.identifyPending = true;
583
+ this.emitState();
584
+ try {
585
+ const result = await identifySession(
586
+ this.requireHttp(),
587
+ this.apiHost,
588
+ this.channelId,
589
+ session.session_id,
590
+ this.requireSessionManager().getAuthorizationHeader(session.session_token),
591
+ apiIdentity
592
+ );
593
+ const updatedSession = await this.requireSessionManager().setSession({
594
+ ...session,
595
+ session_token: result.session_token,
596
+ customer_id: result.customer_id,
597
+ conversation_id: result.conversation_id,
598
+ authenticated: true
599
+ });
600
+ this.session = updatedSession;
601
+ this.customer = result.customer;
602
+ this.legacyWidgetJwt = result.legacy_jwt ?? null;
603
+ this.identified = true;
604
+ this.identifyPending = false;
605
+ const { session_token: _sessionToken, legacy_jwt: _legacyWidgetJwt, ...safeResult } = result;
606
+ const publicResult = {
607
+ ...safeResult,
608
+ success: true
609
+ };
610
+ this.lastIdentifyResult = publicResult;
611
+ this.identifyCallbacks.forEach((callback) => callback(publicResult));
612
+ this.emitState();
613
+ return publicResult;
614
+ } catch (error) {
615
+ this.identifyPending = false;
616
+ this.emitState();
617
+ this.emitError(error);
618
+ throw error;
619
+ }
620
+ }
621
+ async logout() {
622
+ const session = this.requireSession();
623
+ const result = await logoutSession(
624
+ this.requireHttp(),
625
+ this.apiHost,
626
+ session.session_id,
627
+ this.requireSessionManager().getAuthorizationHeader(session.session_token)
628
+ );
629
+ this.identified = false;
630
+ this.customer = null;
631
+ this.legacyWidgetJwt = null;
632
+ this.lastIdentifyResult = null;
633
+ this.session = await this.requireSessionManager().setSession({
634
+ ...session,
635
+ customer_id: null,
636
+ conversation_id: null,
637
+ authenticated: false
638
+ });
639
+ this.setUnreadCount(0);
640
+ this.emitState();
641
+ return result;
642
+ }
643
+ getIdentityState() {
644
+ return {
645
+ identified: this.identified,
646
+ customer: this.customer,
647
+ pending: this.identifyPending
648
+ };
649
+ }
650
+ onIdentify(callback) {
651
+ if (typeof callback !== "function") return () => void 0;
652
+ this.identifyCallbacks.add(callback);
653
+ if (this.identified && this.lastIdentifyResult) {
654
+ callback(this.lastIdentifyResult);
655
+ }
656
+ return () => {
657
+ this.identifyCallbacks.delete(callback);
658
+ };
659
+ }
660
+ async getUnreadCount() {
661
+ if (!this.isConfigured || !this.session) return this.unreadCount;
662
+ return this.fetchUnreadCount();
663
+ }
664
+ onUnreadChange(callback) {
665
+ if (typeof callback !== "function") return () => void 0;
666
+ this.unreadCallbacks.add(callback);
667
+ callback(this.getUnreadState());
668
+ return () => {
669
+ this.unreadCallbacks.delete(callback);
670
+ };
671
+ }
672
+ startUnreadPolling(intervalMs = this.runtimeOptions?.behavior?.unreadPollIntervalMs ?? DEFAULT_UNREAD_POLL_INTERVAL_MS) {
673
+ this.stopUnreadPolling();
674
+ void this.fetchUnreadCount().catch((error) => this.emitError(error));
675
+ this.unreadPollTimer = setInterval(() => {
676
+ void this.fetchUnreadCount().catch((error) => this.emitError(error));
677
+ }, intervalMs);
678
+ return () => this.stopUnreadPolling();
679
+ }
680
+ stopUnreadPolling() {
681
+ if (this.unreadPollTimer) {
682
+ clearInterval(this.unreadPollTimer);
683
+ this.unreadPollTimer = null;
684
+ }
685
+ }
686
+ present() {
687
+ this.requireConfigured();
688
+ this.hostedChatView = { screen: "home" };
689
+ if (this.presented) {
690
+ this.emitState();
691
+ return true;
692
+ }
693
+ this.presented = true;
694
+ this.setUnreadCount(0);
695
+ this.presentCallbacks.forEach((callback) => callback(this.getLifecycleState()));
696
+ this.emitState();
697
+ return true;
698
+ }
699
+ presentMessages() {
700
+ this.requireConfigured();
701
+ this.hostedChatView = { screen: "messages" };
702
+ return this.showHostedChat();
703
+ }
704
+ presentMessageComposer(message) {
705
+ this.requireConfigured();
706
+ this.hostedChatView = { screen: "composer", message };
707
+ return this.showHostedChat();
708
+ }
709
+ dismiss() {
710
+ this.requireConfigured();
711
+ if (!this.presented) return true;
712
+ this.presented = false;
713
+ this.emitUnreadChange();
714
+ this.dismissCallbacks.forEach((callback) => callback(this.getLifecycleState()));
715
+ this.emitState();
716
+ return true;
717
+ }
718
+ hide() {
719
+ return this.dismiss();
720
+ }
721
+ toggle() {
722
+ if (this.presented) {
723
+ this.dismiss();
724
+ return false;
725
+ }
726
+ this.present();
727
+ return true;
728
+ }
729
+ isPresented() {
730
+ return this.presented;
731
+ }
732
+ onReady(callback) {
733
+ if (typeof callback !== "function") return () => void 0;
734
+ this.readyCallbacks.add(callback);
735
+ if (this.isConfigured) callback(this.getLifecycleState());
736
+ return () => {
737
+ this.readyCallbacks.delete(callback);
738
+ };
739
+ }
740
+ onPresent(callback) {
741
+ if (typeof callback !== "function") return () => void 0;
742
+ this.presentCallbacks.add(callback);
743
+ if (this.presented) callback(this.getLifecycleState());
744
+ return () => {
745
+ this.presentCallbacks.delete(callback);
746
+ };
747
+ }
748
+ onDismiss(callback) {
749
+ if (typeof callback !== "function") return () => void 0;
750
+ this.dismissCallbacks.add(callback);
751
+ return () => {
752
+ this.dismissCallbacks.delete(callback);
753
+ };
754
+ }
755
+ onError(callback) {
756
+ if (typeof callback !== "function") return () => void 0;
757
+ this.errorCallbacks.add(callback);
758
+ return () => {
759
+ this.errorCallbacks.delete(callback);
760
+ };
761
+ }
762
+ onStateChange(callback) {
763
+ if (typeof callback !== "function") return () => void 0;
764
+ this.stateCallbacks.add(callback);
765
+ callback(this.getState());
766
+ return () => {
767
+ this.stateCallbacks.delete(callback);
768
+ };
769
+ }
770
+ getState() {
771
+ return {
772
+ configured: this.isConfigured,
773
+ isOpen: this.isPresented(),
774
+ unreadCount: this.unreadCount,
775
+ identity: this.getIdentityState(),
776
+ theme: resolveMobileThemeState(this.runtimeOptions?.theme)
777
+ };
778
+ }
779
+ getSessionState() {
780
+ return this.session ? toPublicSessionState(this.session) : null;
781
+ }
782
+ getChannel() {
783
+ return this.channel;
784
+ }
785
+ getConfig() {
786
+ return this.runtimeOptions;
787
+ }
788
+ getConversationUrl() {
789
+ this.requireConfigured();
790
+ const session = this.requireSession();
791
+ const path = this.hostedChatView.screen === "home" ? `/chat-widget/${encodeURIComponent(this.channelId)}` : `/chat-widget/${encodeURIComponent(this.channelId)}/conversations`;
792
+ const url = new URL(`${this.widgetHost}${path}`);
793
+ url.searchParams.set("sdk", "react-native");
794
+ if (this.runtimeOptions?.presentation?.mobile ?? true) {
795
+ url.searchParams.set("mobile", "1");
796
+ }
797
+ if (this.runtimeOptions?.presentation?.showCloseButton ?? false) {
798
+ url.searchParams.set("hideCloseButton", "true");
799
+ }
800
+ url.searchParams.set("session_id", session.session_id);
801
+ url.searchParams.set("wid", this.workspaceId);
802
+ if (this.hostedChatView.screen === "composer") {
803
+ url.searchParams.set("new", "true");
804
+ if (this.hostedChatView.message) {
805
+ url.searchParams.set("topic", this.hostedChatView.message);
806
+ }
807
+ }
808
+ return url.toString();
809
+ }
810
+ getPreloadUrl() {
811
+ this.requireConfigured();
812
+ const previousView = this.hostedChatView;
813
+ this.hostedChatView = { screen: "home" };
814
+ const preloadUrl = this.getConversationUrl();
815
+ this.hostedChatView = previousView;
816
+ return preloadUrl;
817
+ }
818
+ getHostedAuthScript() {
819
+ if (!this.legacyWidgetJwt || !this.workspaceId) return "";
820
+ const storageKey = `${this.runtimeOptions?.hostedClaimsStorageKeyPrefix || DEFAULT_HOSTED_CLAIMS_STORAGE_KEY_PREFIX}_${this.workspaceId}`;
821
+ return [
822
+ "(function(){",
823
+ "try {",
824
+ `window.localStorage.setItem(${JSON.stringify(storageKey)}, ${JSON.stringify(this.legacyWidgetJwt)});`,
825
+ "} catch (_) {}",
826
+ "})();"
827
+ ].join("\n");
828
+ }
829
+ handleHostedBridgeMessage(message) {
830
+ if (!message) return false;
831
+ if (message.type === "close_widget") {
832
+ this.dismiss();
833
+ return true;
834
+ }
835
+ if (message.type === "unread_count") {
836
+ const nextCount = message.count ?? message.unread_count;
837
+ if (typeof nextCount === "number") {
838
+ this.setUnreadCount(nextCount);
839
+ } else {
840
+ void this.getUnreadCount().catch((error) => this.emitError(error));
841
+ }
842
+ return true;
843
+ }
844
+ if (message.type === "sagepilot:error") {
845
+ this.emitError(message);
846
+ return true;
847
+ }
848
+ return true;
849
+ }
850
+ destroy() {
851
+ this.stopUnreadPolling();
852
+ this.resetRuntimeState();
853
+ this.emitState();
854
+ this.identifyCallbacks.clear();
855
+ this.unreadCallbacks.clear();
856
+ this.readyCallbacks.clear();
857
+ this.presentCallbacks.clear();
858
+ this.dismissCallbacks.clear();
859
+ this.errorCallbacks.clear();
860
+ }
861
+ setUnreadCount(count) {
862
+ if (count === this.unreadCount) return;
863
+ this.unreadCount = count;
864
+ this.emitUnreadChange();
865
+ }
866
+ emitUnreadChange() {
867
+ const state = this.getUnreadState();
868
+ this.unreadCallbacks.forEach((callback) => callback(state));
869
+ }
870
+ emitReady() {
871
+ const state = this.getLifecycleState();
872
+ this.readyCallbacks.forEach((callback) => callback(state));
873
+ }
874
+ emitError(error) {
875
+ this.errorCallbacks.forEach((callback) => callback(error));
876
+ }
877
+ getUnreadState() {
878
+ return {
879
+ count: this.unreadCount,
880
+ isOpen: this.presented
881
+ };
882
+ }
883
+ getLifecycleState() {
884
+ return {
885
+ isOpen: this.presented,
886
+ unreadCount: this.unreadCount
887
+ };
888
+ }
889
+ emitState() {
890
+ const state = this.getState();
891
+ this.stateCallbacks.forEach((callback) => callback(state));
892
+ }
893
+ requireConfigured() {
894
+ if (!this.isConfigured || !this.session) {
895
+ throw new SagepilotChatError(
896
+ "not_initialized",
897
+ `${SDK_NAME} is not configured. Call configure() first.`
898
+ );
899
+ }
900
+ }
901
+ requireSession() {
902
+ this.requireConfigured();
903
+ return this.session;
904
+ }
905
+ requireHttp() {
906
+ if (!this.http) {
907
+ throw new SagepilotChatError(
908
+ "not_initialized",
909
+ `${SDK_NAME} is not configured. Call configure() first.`
910
+ );
911
+ }
912
+ return this.http;
913
+ }
914
+ requireSessionManager() {
915
+ if (!this.sessionManager) {
916
+ throw new SagepilotChatError(
917
+ "not_initialized",
918
+ `${SDK_NAME} is not configured. Call configure() first.`
919
+ );
920
+ }
921
+ return this.sessionManager;
922
+ }
923
+ async resolveCustomerSession(config) {
924
+ const sessionManager = this.requireSessionManager();
925
+ const storedSession = await sessionManager.hydrate();
926
+ if (storedSession?.session_id && storedSession.session_token) {
927
+ try {
928
+ const liveSession = await getSession(
929
+ this.requireHttp(),
930
+ this.apiHost,
931
+ storedSession.session_id,
932
+ sessionManager.getAuthorizationHeader(storedSession.session_token)
933
+ );
934
+ const session2 = await sessionManager.setSession({
935
+ ...storedSession,
936
+ customer_id: liveSession.customer_id,
937
+ conversation_id: liveSession.conversation_id,
938
+ authenticated: liveSession.authenticated ?? storedSession.authenticated ?? false
939
+ });
940
+ return {
941
+ ...liveSession,
942
+ session_token: session2.session_token
943
+ };
944
+ } catch {
945
+ await sessionManager.clearSession();
946
+ }
947
+ }
948
+ const deviceInfo = await resolveDeviceInfo(config.deviceInfo);
949
+ const session = await createSession(
950
+ this.requireHttp(),
951
+ this.apiHost,
952
+ this.channelId,
953
+ {
954
+ anonymousId: config.anonymousId,
955
+ metadata: {
956
+ ...config.metadata,
957
+ device: deviceInfo,
958
+ presentation: config.presentation,
959
+ theme: config.theme,
960
+ push_notifications_enabled: config.behavior?.enablePushNotifications
961
+ }
962
+ }
963
+ );
964
+ await sessionManager.setSession(session);
965
+ return session;
966
+ }
967
+ async fetchUnreadCount() {
968
+ const session = this.requireSession();
969
+ const response = await fetchUnreadCount(
970
+ this.requireHttp(),
971
+ this.apiHost,
972
+ this.channelId,
973
+ session.session_id,
974
+ this.requireSessionManager().getAuthorizationHeader(session.session_token)
975
+ );
976
+ this.setUnreadCount(response.unread_count || 0);
977
+ return this.unreadCount;
978
+ }
979
+ resetRuntimeState() {
980
+ this.runtimeOptions = null;
981
+ this.http = null;
982
+ this.sessionManager = null;
983
+ this.apiHost = "";
984
+ this.widgetHost = "";
985
+ this.workspaceId = "";
986
+ this.channelId = "";
987
+ this.channel = null;
988
+ this.session = null;
989
+ this.customer = null;
990
+ this.legacyWidgetJwt = null;
991
+ this.lastIdentifyResult = null;
992
+ this.identified = false;
993
+ this.identifyPending = false;
994
+ this.isConfigured = false;
995
+ this.presented = false;
996
+ this.hostedChatView = { screen: "home" };
997
+ this.unreadCount = 0;
998
+ }
999
+ showHostedChat() {
1000
+ if (!this.presented) {
1001
+ this.presented = true;
1002
+ this.setUnreadCount(0);
1003
+ this.presentCallbacks.forEach((callback) => callback(this.getLifecycleState()));
1004
+ }
1005
+ this.emitState();
1006
+ return true;
1007
+ }
1008
+ };
1009
+ var internalSagepilotChat = new SagepilotReactNativeChat();
1010
+ var SagepilotChat = {
1011
+ configure: (config) => internalSagepilotChat.configure(config),
1012
+ getSession: () => internalSagepilotChat.getSession(),
1013
+ identify: (identity) => internalSagepilotChat.identify(identity),
1014
+ logout: () => internalSagepilotChat.logout(),
1015
+ getIdentityState: () => internalSagepilotChat.getIdentityState(),
1016
+ onIdentify: (callback) => internalSagepilotChat.onIdentify(callback),
1017
+ getUnreadCount: () => internalSagepilotChat.getUnreadCount(),
1018
+ onUnreadChange: (callback) => internalSagepilotChat.onUnreadChange(callback),
1019
+ startUnreadPolling: (intervalMs) => internalSagepilotChat.startUnreadPolling(intervalMs),
1020
+ stopUnreadPolling: () => internalSagepilotChat.stopUnreadPolling(),
1021
+ present: () => internalSagepilotChat.present(),
1022
+ presentMessages: () => internalSagepilotChat.presentMessages(),
1023
+ presentMessageComposer: (message) => internalSagepilotChat.presentMessageComposer(message),
1024
+ dismiss: () => internalSagepilotChat.dismiss(),
1025
+ hide: () => internalSagepilotChat.hide(),
1026
+ toggle: () => internalSagepilotChat.toggle(),
1027
+ isPresented: () => internalSagepilotChat.isPresented(),
1028
+ onReady: (callback) => internalSagepilotChat.onReady(callback),
1029
+ onPresent: (callback) => internalSagepilotChat.onPresent(callback),
1030
+ onDismiss: (callback) => internalSagepilotChat.onDismiss(callback),
1031
+ onError: (callback) => internalSagepilotChat.onError(callback),
1032
+ onStateChange: (callback) => internalSagepilotChat.onStateChange(callback),
1033
+ getState: () => internalSagepilotChat.getState(),
1034
+ getSessionState: () => internalSagepilotChat.getSessionState(),
1035
+ getChannel: () => internalSagepilotChat.getChannel(),
1036
+ destroy: () => internalSagepilotChat.destroy()
1037
+ };
1038
+
1039
+ // src/core/storage/cache.ts
1040
+ function createAsyncStorageCacheStorage(asyncStorage) {
1041
+ return {
1042
+ getItem: (key) => asyncStorage.getItem(key),
1043
+ setItem: (key, value) => asyncStorage.setItem(key, value),
1044
+ removeItem: (key) => asyncStorage.removeItem(key)
1045
+ };
1046
+ }
1047
+
1048
+ // src/ui/SagepilotChatProvider.ts
1049
+ import { createElement, useEffect, useState } from "react";
1050
+ import {
1051
+ ActivityIndicator,
1052
+ Modal,
1053
+ Platform,
1054
+ Pressable,
1055
+ StyleSheet,
1056
+ Text,
1057
+ View
1058
+ } from "react-native";
1059
+ import { WebView } from "react-native-webview";
1060
+
1061
+ // src/core/webview/mobileBridge.ts
1062
+ function isHostedBridgeMessage(value) {
1063
+ if (!value || typeof value !== "object") return false;
1064
+ const message = value;
1065
+ return typeof message.type === "string";
1066
+ }
1067
+ function parseHostedBridgeMessage(rawData) {
1068
+ if (!rawData) return null;
1069
+ if (isHostedBridgeMessage(rawData)) {
1070
+ return rawData;
1071
+ }
1072
+ if (typeof rawData !== "string") {
1073
+ return null;
1074
+ }
1075
+ try {
1076
+ const message = JSON.parse(rawData);
1077
+ return isHostedBridgeMessage(message) ? message : null;
1078
+ } catch {
1079
+ return null;
1080
+ }
1081
+ }
1082
+ var mobileWebViewBridgeScript = `
1083
+ (function () {
1084
+ if (window.__sagepilotRnBridgeInstalled) return true;
1085
+ window.__sagepilotRnBridgeInstalled = true;
1086
+
1087
+ var ensureViewport = function () {
1088
+ var viewport = document.querySelector('meta[name="viewport"]');
1089
+ if (!viewport) {
1090
+ viewport = document.createElement("meta");
1091
+ viewport.setAttribute("name", "viewport");
1092
+ document.head.appendChild(viewport);
1093
+ }
1094
+ viewport.setAttribute("content", "width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover");
1095
+ };
1096
+
1097
+ var injectMobileStyles = function () {
1098
+ if (document.getElementById("sagepilot-rn-mobile-style")) return;
1099
+ var style = document.createElement("style");
1100
+ style.id = "sagepilot-rn-mobile-style";
1101
+ style.textContent = [
1102
+ "html, body { overscroll-behavior: none; -webkit-text-size-adjust: 100%; }",
1103
+ "body { -webkit-tap-highlight-color: transparent; touch-action: manipulation; }",
1104
+ "input, textarea, [contenteditable='true'] { -webkit-user-select: text; user-select: text; }",
1105
+ "*:not(input):not(textarea):not([contenteditable='true']) { -webkit-user-select: none; user-select: none; }"
1106
+ ].join("\\n");
1107
+ document.head.appendChild(style);
1108
+ };
1109
+
1110
+ var sendToReactNative = function (message) {
1111
+ try {
1112
+ if (window.ReactNativeWebView && typeof window.ReactNativeWebView.postMessage === "function") {
1113
+ window.ReactNativeWebView.postMessage(typeof message === "string" ? message : JSON.stringify(message));
1114
+ }
1115
+ } catch (error) {}
1116
+ };
1117
+
1118
+ var originalPostMessage = window.postMessage;
1119
+ window.postMessage = function (message, targetOrigin, transfer) {
1120
+ sendToReactNative(message);
1121
+ return originalPostMessage.apply(window, arguments);
1122
+ };
1123
+
1124
+ window.SagepilotMobileBridge = {
1125
+ postMessage: sendToReactNative,
1126
+ ready: function () { sendToReactNative({ type: "sagepilot:ready" }); }
1127
+ };
1128
+
1129
+ document.addEventListener("click", function (event) {
1130
+ try {
1131
+ var target = event.target && event.target.closest
1132
+ ? event.target
1133
+ : event.target && event.target.parentElement;
1134
+ if (target && target.closest && target.closest("[data-sagepilot-close-widget='true']")) {
1135
+ sendToReactNative({ type: "close_widget" });
1136
+ }
1137
+ } catch (error) {}
1138
+ }, true);
1139
+
1140
+ ensureViewport();
1141
+ injectMobileStyles();
1142
+ sendToReactNative({ type: "sagepilot:ready" });
1143
+ return true;
1144
+ })();
1145
+ `;
1146
+
1147
+ // src/ui/SagepilotChatProvider.ts
1148
+ function readPresentationState() {
1149
+ return {
1150
+ isPresented: internalSagepilotChat.isPresented(),
1151
+ configured: internalSagepilotChat.getState().configured,
1152
+ conversationUrl: internalSagepilotChat.getState().configured ? internalSagepilotChat.getConversationUrl() : null,
1153
+ preloadUrl: internalSagepilotChat.getState().configured ? internalSagepilotChat.getPreloadUrl() : null,
1154
+ shouldPreload: internalSagepilotChat.getConfig()?.behavior?.preloadWebView ?? true,
1155
+ presentation: internalSagepilotChat.getConfig()?.presentation
1156
+ };
1157
+ }
1158
+ function getInjectedWebViewScript() {
1159
+ return [
1160
+ internalSagepilotChat.getHostedAuthScript(),
1161
+ mobileWebViewBridgeScript
1162
+ ].filter(Boolean).join("\n");
1163
+ }
1164
+ function readUrlOrigin(url) {
1165
+ if (!url) return null;
1166
+ try {
1167
+ return new URL(url).origin;
1168
+ } catch {
1169
+ return null;
1170
+ }
1171
+ }
1172
+ var hostedChatWebViewProps = {
1173
+ allowFileAccess: true,
1174
+ allowFileAccessFromFileURLs: true,
1175
+ domStorageEnabled: true,
1176
+ javaScriptEnabled: true,
1177
+ setSupportMultipleWindows: false,
1178
+ sharedCookiesEnabled: true,
1179
+ thirdPartyCookiesEnabled: true
1180
+ };
1181
+ function SagepilotChatProvider({
1182
+ children,
1183
+ closeLabel = "Close",
1184
+ loadingLabel = "Loading chat"
1185
+ }) {
1186
+ const [state, setState] = useState(readPresentationState);
1187
+ useEffect(() => {
1188
+ return internalSagepilotChat.onStateChange(() => {
1189
+ setState(readPresentationState());
1190
+ });
1191
+ }, []);
1192
+ const showCloseButton = state.presentation?.showCloseButton ?? false;
1193
+ const presentationStyle = state.presentation?.style ?? "sheet";
1194
+ const animationType = presentationStyle === "fullScreen" || presentationStyle === "push" ? "slide" : "fade";
1195
+ const handleWebViewMessage = (event) => {
1196
+ internalSagepilotChat.handleHostedBridgeMessage(parseHostedBridgeMessage(event.nativeEvent?.data));
1197
+ };
1198
+ useEffect(() => {
1199
+ if (Platform.OS !== "web" || typeof window === "undefined") return;
1200
+ const trustedWidgetOrigin = readUrlOrigin(state.conversationUrl);
1201
+ if (!trustedWidgetOrigin) return;
1202
+ const handleWindowMessage = (event) => {
1203
+ if (event.origin !== trustedWidgetOrigin) return;
1204
+ internalSagepilotChat.handleHostedBridgeMessage(parseHostedBridgeMessage(event.data));
1205
+ };
1206
+ window.addEventListener("message", handleWindowMessage);
1207
+ return () => window.removeEventListener("message", handleWindowMessage);
1208
+ }, [state.conversationUrl]);
1209
+ if (Platform.OS === "web") {
1210
+ return createElement(
1211
+ View,
1212
+ { style: styles.root },
1213
+ children,
1214
+ state.configured && !state.isPresented && state.shouldPreload && state.preloadUrl ? createElement("iframe", {
1215
+ src: state.preloadUrl,
1216
+ style: styles.webPreloadFrame,
1217
+ title: "Sagepilot chat preload"
1218
+ }) : null,
1219
+ state.configured && state.isPresented && state.conversationUrl ? createElement(
1220
+ View,
1221
+ { style: styles.webOverlay },
1222
+ createElement(
1223
+ View,
1224
+ { style: styles.webPanel },
1225
+ showCloseButton ? createElement(
1226
+ Pressable,
1227
+ {
1228
+ accessibilityRole: "button",
1229
+ accessibilityLabel: closeLabel,
1230
+ onPress: () => internalSagepilotChat.dismiss(),
1231
+ style: styles.webCloseButton
1232
+ },
1233
+ createElement(Text, { style: styles.closeText }, closeLabel)
1234
+ ) : null,
1235
+ createElement("iframe", {
1236
+ src: state.conversationUrl,
1237
+ style: styles.webFrame,
1238
+ title: "Sagepilot chat"
1239
+ })
1240
+ )
1241
+ ) : null
1242
+ );
1243
+ }
1244
+ return createElement(
1245
+ View,
1246
+ { style: styles.root },
1247
+ children,
1248
+ state.configured && !state.isPresented && state.shouldPreload && state.preloadUrl ? createElement(WebView, {
1249
+ ...hostedChatWebViewProps,
1250
+ source: { uri: state.preloadUrl },
1251
+ style: styles.preloadWebview,
1252
+ injectedJavaScriptBeforeContentLoaded: getInjectedWebViewScript(),
1253
+ onMessage: handleWebViewMessage
1254
+ }) : null,
1255
+ createElement(
1256
+ Modal,
1257
+ {
1258
+ visible: state.configured && state.isPresented,
1259
+ animationType,
1260
+ presentationStyle: presentationStyle === "fullScreen" ? "fullScreen" : "pageSheet",
1261
+ onRequestClose: () => internalSagepilotChat.dismiss()
1262
+ },
1263
+ createElement(
1264
+ View,
1265
+ { style: styles.container },
1266
+ showCloseButton ? createElement(
1267
+ View,
1268
+ { style: styles.header },
1269
+ createElement(
1270
+ Pressable,
1271
+ {
1272
+ accessibilityRole: "button",
1273
+ accessibilityLabel: closeLabel,
1274
+ onPress: () => internalSagepilotChat.dismiss(),
1275
+ style: styles.closeButton
1276
+ },
1277
+ createElement(Text, { style: styles.closeText }, closeLabel)
1278
+ )
1279
+ ) : null,
1280
+ state.conversationUrl ? createElement(WebView, {
1281
+ ...hostedChatWebViewProps,
1282
+ source: { uri: state.conversationUrl },
1283
+ style: styles.webview,
1284
+ startInLoadingState: true,
1285
+ injectedJavaScriptBeforeContentLoaded: getInjectedWebViewScript(),
1286
+ onMessage: handleWebViewMessage,
1287
+ renderLoading: () => createElement(
1288
+ View,
1289
+ { style: styles.loading },
1290
+ createElement(ActivityIndicator, null),
1291
+ createElement(Text, { style: styles.loadingText }, loadingLabel)
1292
+ )
1293
+ }) : null
1294
+ )
1295
+ )
1296
+ );
1297
+ }
1298
+ var styles = StyleSheet.create({
1299
+ root: {
1300
+ flex: 1
1301
+ },
1302
+ container: {
1303
+ flex: 1,
1304
+ backgroundColor: "#ffffff"
1305
+ },
1306
+ header: {
1307
+ minHeight: 48,
1308
+ alignItems: "flex-end",
1309
+ justifyContent: "center",
1310
+ paddingHorizontal: 12,
1311
+ borderBottomWidth: 1,
1312
+ borderBottomColor: "#e5e7eb"
1313
+ },
1314
+ closeButton: {
1315
+ minHeight: 36,
1316
+ justifyContent: "center",
1317
+ paddingHorizontal: 8
1318
+ },
1319
+ closeText: {
1320
+ color: "#111827",
1321
+ fontSize: 16,
1322
+ fontWeight: "600"
1323
+ },
1324
+ webview: {
1325
+ flex: 1
1326
+ },
1327
+ preloadWebview: {
1328
+ position: "absolute",
1329
+ width: 1,
1330
+ height: 1,
1331
+ opacity: 0
1332
+ },
1333
+ webOverlay: {
1334
+ position: "absolute",
1335
+ top: 0,
1336
+ right: 0,
1337
+ bottom: 0,
1338
+ left: 0,
1339
+ zIndex: 9999,
1340
+ backgroundColor: "rgba(17, 24, 39, 0.36)"
1341
+ },
1342
+ webPanel: {
1343
+ position: "absolute",
1344
+ right: 24,
1345
+ bottom: 24,
1346
+ width: 420,
1347
+ maxWidth: "calc(100vw - 32px)",
1348
+ height: 680,
1349
+ maxHeight: "calc(100vh - 32px)",
1350
+ overflow: "hidden",
1351
+ borderRadius: 16,
1352
+ backgroundColor: "#ffffff",
1353
+ boxShadow: "0 24px 80px rgba(17, 24, 39, 0.28)"
1354
+ },
1355
+ webCloseButton: {
1356
+ minHeight: 44,
1357
+ alignItems: "flex-end",
1358
+ justifyContent: "center",
1359
+ paddingHorizontal: 14,
1360
+ borderBottomWidth: 1,
1361
+ borderBottomColor: "#e5e7eb"
1362
+ },
1363
+ webFrame: {
1364
+ width: "100%",
1365
+ height: "calc(100% - 45px)",
1366
+ borderWidth: 0,
1367
+ borderStyle: "none"
1368
+ },
1369
+ webPreloadFrame: {
1370
+ position: "absolute",
1371
+ width: 1,
1372
+ height: 1,
1373
+ opacity: 0,
1374
+ pointerEvents: "none",
1375
+ borderWidth: 0,
1376
+ borderStyle: "none"
1377
+ },
1378
+ loading: {
1379
+ ...StyleSheet.absoluteFillObject,
1380
+ alignItems: "center",
1381
+ justifyContent: "center",
1382
+ backgroundColor: "#ffffff"
1383
+ },
1384
+ loadingText: {
1385
+ marginTop: 12,
1386
+ color: "#4b5563",
1387
+ fontSize: 14
1388
+ }
1389
+ });
1390
+
1391
+ // src/hooks/useSagepilotChat.ts
1392
+ import { useCallback, useEffect as useEffect2, useState as useState2 } from "react";
1393
+ function readState() {
1394
+ const state = SagepilotChat.getState();
1395
+ return {
1396
+ configured: state.configured,
1397
+ isPresented: state.isOpen,
1398
+ unreadCount: state.unreadCount,
1399
+ identity: state.identity,
1400
+ theme: state.theme
1401
+ };
1402
+ }
1403
+ function useSagepilotChat() {
1404
+ const [state, setState] = useState2(readState);
1405
+ useEffect2(() => {
1406
+ return SagepilotChat.onStateChange(() => {
1407
+ setState(readState());
1408
+ });
1409
+ }, []);
1410
+ const present = useCallback(() => SagepilotChat.present(), []);
1411
+ const presentMessages = useCallback(() => SagepilotChat.presentMessages(), []);
1412
+ const presentMessageComposer = useCallback((message) => SagepilotChat.presentMessageComposer(message), []);
1413
+ const dismiss = useCallback(() => SagepilotChat.dismiss(), []);
1414
+ const hide = useCallback(() => SagepilotChat.hide(), []);
1415
+ const toggle = useCallback(() => SagepilotChat.toggle(), []);
1416
+ const identify = useCallback((identity) => {
1417
+ return SagepilotChat.identify(identity);
1418
+ }, []);
1419
+ const logout = useCallback(() => SagepilotChat.logout(), []);
1420
+ const getUnreadCount = useCallback(() => SagepilotChat.getUnreadCount(), []);
1421
+ return {
1422
+ ...state,
1423
+ present,
1424
+ presentMessages,
1425
+ presentMessageComposer,
1426
+ dismiss,
1427
+ hide,
1428
+ toggle,
1429
+ identify,
1430
+ logout,
1431
+ getUnreadCount
1432
+ };
1433
+ }
1434
+ export {
1435
+ SagepilotChat,
1436
+ SagepilotChatError,
1437
+ SagepilotChatProvider,
1438
+ createAsyncStorageCacheStorage,
1439
+ createKeychainTokenStorage,
1440
+ useSagepilotChat
1441
+ };