@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/LICENSE.md +21 -0
- package/README.md +422 -0
- package/android/.gitkeep +1 -0
- package/dist/index.cjs +1465 -0
- package/dist/index.d.cts +310 -0
- package/dist/index.d.ts +310 -0
- package/dist/index.js +1441 -0
- package/ios/.gitkeep +1 -0
- package/package.json +58 -0
- package/react-native.config.js +8 -0
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
|
+
};
|