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