@robylon/react-native-sdk 2.2.1-staging.1 → 2.2.3-staging.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/README.md +206 -0
- package/lib/commonjs/Chatbotsdk.js +6 -3
- package/lib/commonjs/Chatbotsdk.js.map +1 -1
- package/lib/commonjs/openChatbot.js +2 -1
- package/lib/commonjs/openChatbot.js.map +1 -1
- package/lib/commonjs/utils/session.js +1 -1
- package/lib/commonjs/utils/session.js.map +1 -1
- package/lib/commonjs/utils/webViewNavigation.js +2 -0
- package/lib/commonjs/utils/webViewNavigation.js.map +1 -0
- package/lib/commonjs/versions/version.staging.js +1 -1
- package/lib/module/Chatbotsdk.js +6 -3
- package/lib/module/Chatbotsdk.js.map +1 -1
- package/lib/module/utils/session.js +1 -1
- package/lib/module/utils/session.js.map +1 -1
- package/lib/module/utils/webViewNavigation.js +2 -0
- package/lib/module/utils/webViewNavigation.js.map +1 -0
- package/lib/module/versions/version.staging.js +1 -1
- package/lib/typescript/Chatbotsdk.d.ts +1 -0
- package/lib/typescript/Chatbotsdk.d.ts.map +1 -1
- package/lib/typescript/utils/session.d.ts +11 -0
- package/lib/typescript/utils/session.d.ts.map +1 -1
- package/lib/typescript/utils/webViewNavigation.d.ts +34 -0
- package/lib/typescript/utils/webViewNavigation.d.ts.map +1 -0
- package/lib/typescript/versions/version.staging.d.ts +1 -1
- package/package.json +1 -1
- package/robylon-react-native-sdk-2.2.1-staging.1.tgz +0 -0
- package/src/Chatbotsdk.tsx +121 -13
- package/src/utils/session.ts +12 -0
- package/src/utils/webViewNavigation.ts +132 -0
- package/src/versions/version.staging.ts +1 -1
- package/robylon-react-native-sdk-2.2.1-staging.0.tgz +0 -0
package/src/Chatbotsdk.tsx
CHANGED
|
@@ -49,9 +49,14 @@ import {
|
|
|
49
49
|
} from "./utils/webViewStorage";
|
|
50
50
|
import { animateWebViewOpen, animateWebViewClose } from "./utils/animations";
|
|
51
51
|
import { handleDownloadRequest, DownloadRequest } from "./utils/fileDownload";
|
|
52
|
+
import {
|
|
53
|
+
handleWebViewNavigationRequest,
|
|
54
|
+
CHATBOT_ORIGIN_WHITELIST,
|
|
55
|
+
} from "./utils/webViewNavigation";
|
|
52
56
|
import {
|
|
53
57
|
SESSION_UPDATED_MESSAGE_TYPE,
|
|
54
58
|
RESUME_SESSION_KEY,
|
|
59
|
+
SWITCH_SESSION_ACTION,
|
|
55
60
|
extractSessionId,
|
|
56
61
|
} from "./utils/session";
|
|
57
62
|
|
|
@@ -113,6 +118,10 @@ export interface ChatbotRef {
|
|
|
113
118
|
toggle: () => void;
|
|
114
119
|
isReady: () => boolean;
|
|
115
120
|
getSessionId: () => string | undefined;
|
|
121
|
+
openToSession: (
|
|
122
|
+
session_id: string,
|
|
123
|
+
session_context?: Record<string, any>,
|
|
124
|
+
) => void;
|
|
116
125
|
}
|
|
117
126
|
|
|
118
127
|
interface InternalChatbotProps extends ChatbotProps {
|
|
@@ -254,6 +263,29 @@ const Chatbot = forwardRef<ChatbotRef, InternalChatbotProps>(
|
|
|
254
263
|
discreteRef.current = discrete;
|
|
255
264
|
loadingScreenRef.current = loading_screen;
|
|
256
265
|
|
|
266
|
+
// openToSession's target, kept apart from the prop mirror above rather
|
|
267
|
+
// than written into it. That line reassigns on every render, so an
|
|
268
|
+
// imperative value stored there would be erased by the very state update
|
|
269
|
+
// that opens the WebView. This one outranks the prop while it is set.
|
|
270
|
+
const imperativeSessionRef = useRef<string | undefined>(undefined);
|
|
271
|
+
const resolveResumeTarget = () =>
|
|
272
|
+
imperativeSessionRef.current ?? resumeSessionRef.current;
|
|
273
|
+
// Same treatment for the context openToSession was called with. Writing it
|
|
274
|
+
// into sessionContextRef would be erased on the next render for the same
|
|
275
|
+
// reason, so a push-initiated switch would report the mounting screen's
|
|
276
|
+
// context instead of the one describing why the switch happened.
|
|
277
|
+
const imperativeContextRef = useRef<Record<string, any> | undefined>(
|
|
278
|
+
undefined,
|
|
279
|
+
);
|
|
280
|
+
// A session openToSession was asked to show before the SDK could act on it.
|
|
281
|
+
const pendingOpenRef = useRef<string | undefined>(undefined);
|
|
282
|
+
// The init effect runs above where these are declared and must not list
|
|
283
|
+
// them as dependencies, so it reaches them through refs kept in step below.
|
|
284
|
+
const openWebViewRef = useRef<(() => void) | undefined>(undefined);
|
|
285
|
+
const postSessionSwitchRef = useRef<((id: string) => void) | undefined>(
|
|
286
|
+
undefined,
|
|
287
|
+
);
|
|
288
|
+
|
|
257
289
|
// Same reasoning as above: the branding hint is read inside handleMessage,
|
|
258
290
|
// which does not list chatbotConfig among its dependencies.
|
|
259
291
|
const chatbotConfigRef = useRef<ChatbotConfig | null>(null);
|
|
@@ -315,6 +347,29 @@ const Chatbot = forwardRef<ChatbotRef, InternalChatbotProps>(
|
|
|
315
347
|
},
|
|
316
348
|
isReady: () => isInitializedRef.current,
|
|
317
349
|
getSessionId: () => sessionIdRef.current,
|
|
350
|
+
openToSession: (
|
|
351
|
+
sessionId: string,
|
|
352
|
+
sessionContext?: Record<string, any>,
|
|
353
|
+
) => {
|
|
354
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) return;
|
|
355
|
+
// Armed before anything is injected. Opening the WebView for the
|
|
356
|
+
// first time boots the chat surface, and the registerUserId that
|
|
357
|
+
// follows has to carry the same target this switch asks for.
|
|
358
|
+
imperativeSessionRef.current = sessionId;
|
|
359
|
+
imperativeContextRef.current = sessionContext;
|
|
360
|
+
// Held rather than dropped when the chat surface has not finished
|
|
361
|
+
// booting. A notification tapped from a killed app arrives long
|
|
362
|
+
// before this component is ready, which is the most common way one
|
|
363
|
+
// is opened at all — returning silently there would land the user in
|
|
364
|
+
// whatever conversation the app happened to restore. Applied by the
|
|
365
|
+
// effect that watches isInitialized.
|
|
366
|
+
if (!isInitializedRef.current) {
|
|
367
|
+
pendingOpenRef.current = sessionId;
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
openWebView();
|
|
371
|
+
postSessionSwitch(sessionId);
|
|
372
|
+
},
|
|
318
373
|
}),
|
|
319
374
|
[isInitialized, isWebViewVisible],
|
|
320
375
|
);
|
|
@@ -324,11 +379,24 @@ const Chatbot = forwardRef<ChatbotRef, InternalChatbotProps>(
|
|
|
324
379
|
// throwing client handler cannot break message handling.
|
|
325
380
|
const emitSession = useCallback((data: any) => {
|
|
326
381
|
const sessionId = extractSessionId(data);
|
|
327
|
-
if (!sessionId
|
|
382
|
+
if (!sessionId) return;
|
|
383
|
+
// Captured before the clears below so the context that arrived with
|
|
384
|
+
// openToSession still reaches the handler for the session it described.
|
|
385
|
+
const context = imperativeContextRef.current ?? sessionContextRef.current;
|
|
386
|
+
// The imperative target is an intent about one initialization, matching
|
|
387
|
+
// the web app's own one-shot handling of it. Once a session id comes
|
|
388
|
+
// back — whether the backend honoured the target or quietly fell back to
|
|
389
|
+
// a fresh session — it is spent, so a later reopen cannot revive the
|
|
390
|
+
// conversation the user has since left. Cleared ahead of the dedupe
|
|
391
|
+
// below, which returns early when the switch resolved to the session
|
|
392
|
+
// already on screen.
|
|
393
|
+
imperativeSessionRef.current = undefined;
|
|
394
|
+
imperativeContextRef.current = undefined;
|
|
395
|
+
if (sessionId === sessionIdRef.current) return;
|
|
328
396
|
sessionIdRef.current = sessionId;
|
|
329
397
|
|
|
330
398
|
try {
|
|
331
|
-
onSessionRef.current?.(sessionId,
|
|
399
|
+
onSessionRef.current?.(sessionId, context ?? {});
|
|
332
400
|
} catch (error) {
|
|
333
401
|
errorTracker.trackError(
|
|
334
402
|
error instanceof Error
|
|
@@ -356,6 +424,29 @@ const Chatbot = forwardRef<ChatbotRef, InternalChatbotProps>(
|
|
|
356
424
|
}
|
|
357
425
|
};
|
|
358
426
|
|
|
427
|
+
// Asks the chat surface to abandon whatever conversation it is showing and
|
|
428
|
+
// re-initialize on this one. Sent alongside the resume target rather than
|
|
429
|
+
// instead of it: a WebView that has not booted yet reaches the chat surface
|
|
430
|
+
// through APP_READY -> registerUserId and resumes from there, while one
|
|
431
|
+
// that is already live needs this message, because by then its resume
|
|
432
|
+
// target is only read when a session is being created.
|
|
433
|
+
const postSessionSwitch = useCallback((sessionId: string) => {
|
|
434
|
+
if (!webViewRef.current) return;
|
|
435
|
+
const messageData = {
|
|
436
|
+
name: SWITCH_SESSION_ACTION,
|
|
437
|
+
action: SWITCH_SESSION_ACTION,
|
|
438
|
+
data: { [RESUME_SESSION_KEY]: sessionId },
|
|
439
|
+
};
|
|
440
|
+
// Not logged, even redacted: there is nothing in this payload but the
|
|
441
|
+
// session id.
|
|
442
|
+
webViewRef.current.injectJavaScript(`
|
|
443
|
+
window.postMessage(${JSON.stringify(messageData)}, '*');
|
|
444
|
+
true;
|
|
445
|
+
`);
|
|
446
|
+
}, []);
|
|
447
|
+
|
|
448
|
+
postSessionSwitchRef.current = postSessionSwitch;
|
|
449
|
+
|
|
359
450
|
const executeStorageOp = useCallback(
|
|
360
451
|
(operation: () => void) => {
|
|
361
452
|
if (isStorageReady) {
|
|
@@ -503,7 +594,7 @@ const Chatbot = forwardRef<ChatbotRef, InternalChatbotProps>(
|
|
|
503
594
|
data: {
|
|
504
595
|
userId: effectiveUserId,
|
|
505
596
|
...initLoadHint,
|
|
506
|
-
[RESUME_SESSION_KEY]:
|
|
597
|
+
[RESUME_SESSION_KEY]: resolveResumeTarget() || undefined,
|
|
507
598
|
// Sent only when opted in. false is the default and is
|
|
508
599
|
// conveyed by the key's absence, so integrations that do not
|
|
509
600
|
// use it keep sending an unchanged payload.
|
|
@@ -527,7 +618,7 @@ const Chatbot = forwardRef<ChatbotRef, InternalChatbotProps>(
|
|
|
527
618
|
...messageData,
|
|
528
619
|
data: {
|
|
529
620
|
...messageData.data,
|
|
530
|
-
[RESUME_SESSION_KEY]:
|
|
621
|
+
[RESUME_SESSION_KEY]: resolveResumeTarget()
|
|
531
622
|
? "[redacted]"
|
|
532
623
|
: undefined,
|
|
533
624
|
},
|
|
@@ -679,7 +770,7 @@ const Chatbot = forwardRef<ChatbotRef, InternalChatbotProps>(
|
|
|
679
770
|
// does not re-trigger the config fetch. Consistent with session_id
|
|
680
771
|
// being an initialization-time input. JSON.stringify drops the key
|
|
681
772
|
// when there is nothing to resume.
|
|
682
|
-
resumable_session_id:
|
|
773
|
+
resumable_session_id: resolveResumeTarget() || undefined,
|
|
683
774
|
// See the registerUserId payload — omitted when false.
|
|
684
775
|
discrete: discreteRef.current ? true : undefined,
|
|
685
776
|
org_id: api_key,
|
|
@@ -819,6 +910,8 @@ const Chatbot = forwardRef<ChatbotRef, InternalChatbotProps>(
|
|
|
819
910
|
}
|
|
820
911
|
}, [enableAnimation, animatedValues, externalOnOpen, emitEvent]);
|
|
821
912
|
|
|
913
|
+
openWebViewRef.current = openWebView;
|
|
914
|
+
|
|
822
915
|
// Handle closing the chatbot
|
|
823
916
|
const closeWebView = useCallback(() => {
|
|
824
917
|
if (webViewRef.current) {
|
|
@@ -862,6 +955,16 @@ const Chatbot = forwardRef<ChatbotRef, InternalChatbotProps>(
|
|
|
862
955
|
isInitializedRef.current = true;
|
|
863
956
|
setIsInitialized(true);
|
|
864
957
|
onReady?.();
|
|
958
|
+
|
|
959
|
+
// Apply a session the client asked for before the SDK could act on it.
|
|
960
|
+
// After onReady, so a client that also opens the chatbot from there
|
|
961
|
+
// has already run and this lands on top rather than being overwritten.
|
|
962
|
+
const pending = pendingOpenRef.current;
|
|
963
|
+
if (pending) {
|
|
964
|
+
pendingOpenRef.current = undefined;
|
|
965
|
+
openWebViewRef.current?.();
|
|
966
|
+
postSessionSwitchRef.current?.(pending);
|
|
967
|
+
}
|
|
865
968
|
}
|
|
866
969
|
}, [loading, chatbotConfig, effectiveUserId, isInitialized, onReady]);
|
|
867
970
|
|
|
@@ -1037,14 +1140,19 @@ const Chatbot = forwardRef<ChatbotRef, InternalChatbotProps>(
|
|
|
1037
1140
|
cacheEnabled={true}
|
|
1038
1141
|
scrollEnabled={true}
|
|
1039
1142
|
bounces={false}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1143
|
+
// The library's default whitelist is http/https, and it
|
|
1144
|
+
// answers anything outside it itself rather than calling
|
|
1145
|
+
// the handler below — which is why `tel:` used to die in
|
|
1146
|
+
// its canOpenURL check. This adds the handoff schemes to
|
|
1147
|
+
// that default instead of replacing it, so every other
|
|
1148
|
+
// scheme keeps the library's handling.
|
|
1149
|
+
originWhitelist={CHATBOT_ORIGIN_WHITELIST}
|
|
1150
|
+
onShouldStartLoadWithRequest={(request) =>
|
|
1151
|
+
handleWebViewNavigationRequest(request, [
|
|
1152
|
+
BASE_CHATBOT_URL,
|
|
1153
|
+
chatbotConfig?.chat_iframe_url,
|
|
1154
|
+
])
|
|
1155
|
+
}
|
|
1048
1156
|
startInLoadingState={isFirstLoadRef.current}
|
|
1049
1157
|
onLoadEnd={() => {
|
|
1050
1158
|
if (__DEV__) {
|
package/src/utils/session.ts
CHANGED
|
@@ -13,6 +13,18 @@ export const SESSION_UPDATED_MESSAGE_TYPE = "SESSION_UPDATED";
|
|
|
13
13
|
/** Key carrying the resume target inside the `registerUserId` payload. */
|
|
14
14
|
export const RESUME_SESSION_KEY = "sessionId";
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Action the web app treats as an authoritative switch to another conversation.
|
|
18
|
+
*
|
|
19
|
+
* The resume target on `registerUserId` is only consulted while a session is
|
|
20
|
+
* being created, so re-sending it to a chat surface that has already booted
|
|
21
|
+
* arms a value nothing goes on to consume. A notification tap is exactly that
|
|
22
|
+
* case — the WebView is warm and usually showing a different conversation — so
|
|
23
|
+
* the switch travels as its own intent, which the web app answers by tearing
|
|
24
|
+
* down the conversation on screen and re-initializing on the target.
|
|
25
|
+
*/
|
|
26
|
+
export const SWITCH_SESSION_ACTION = "switch_session";
|
|
27
|
+
|
|
16
28
|
/**
|
|
17
29
|
* Pulls the session id out of a `SESSION_UPDATED` payload.
|
|
18
30
|
*
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File: src/utils/webViewNavigation.ts
|
|
3
|
+
* Decides which navigations stay inside the chatbot WebView and hands the rest
|
|
4
|
+
* to the OS.
|
|
5
|
+
*
|
|
6
|
+
* Returning `false` from `onShouldStartLoadWithRequest` only cancels the
|
|
7
|
+
* navigation: neither platform then falls back to the OS, so without this an
|
|
8
|
+
* external link (`https://…`) or an app handoff (`tel:`, `mailto:`) silently
|
|
9
|
+
* does nothing.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Linking } from "react-native";
|
|
13
|
+
import { logger } from "./logger";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Schemes the WebView resolves against its own document. Handing these to
|
|
17
|
+
* `Linking` would only produce an "unsupported URL" error.
|
|
18
|
+
*/
|
|
19
|
+
const INTERNAL_SCHEMES = ["about:", "javascript:", "data:", "blob:"];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Schemes that hand the URL to another app rather than rendering a document.
|
|
23
|
+
*
|
|
24
|
+
* The chat surface requests these from a detached iframe on purpose: the
|
|
25
|
+
* WebView cannot render them, so navigating the page itself would leave the
|
|
26
|
+
* user looking at an ERR_UNKNOWN_URL_SCHEME error instead of the conversation.
|
|
27
|
+
* That makes them the one kind of sub-frame navigation worth acting on.
|
|
28
|
+
*/
|
|
29
|
+
const APP_HANDOFF_SCHEMES = [
|
|
30
|
+
"tel:",
|
|
31
|
+
"sms:",
|
|
32
|
+
"mailto:",
|
|
33
|
+
"facetime:",
|
|
34
|
+
"whatsapp:",
|
|
35
|
+
"geo:",
|
|
36
|
+
"maps:",
|
|
37
|
+
"intent:",
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* What to pass as the WebView's `originWhitelist`.
|
|
42
|
+
*
|
|
43
|
+
* The library's default is http/https only, and it answers anything outside
|
|
44
|
+
* that itself — with a `canOpenURL` check that Android 11 makes fail for an
|
|
45
|
+
* installed dialer. So the handoff schemes are added to the default rather
|
|
46
|
+
* than the default being replaced: every other scheme keeps the library's
|
|
47
|
+
* handling, and this list cannot drift from the one above.
|
|
48
|
+
*/
|
|
49
|
+
export const CHATBOT_ORIGIN_WHITELIST: string[] = [
|
|
50
|
+
"http://*",
|
|
51
|
+
"https://*",
|
|
52
|
+
...APP_HANDOFF_SCHEMES.map((scheme) => `${scheme}*`),
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const isAppHandoffScheme = (url?: string): boolean => {
|
|
56
|
+
const lowerUrl = trimUrl(url)?.toLowerCase();
|
|
57
|
+
return !!APP_HANDOFF_SCHEMES?.some((scheme) => lowerUrl?.startsWith(scheme));
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/** `hasTargetFrame` is sent by iOS but missing from the library's TS types. */
|
|
61
|
+
export interface WebViewNavigationRequestLike {
|
|
62
|
+
url?: string;
|
|
63
|
+
isTopFrame?: boolean;
|
|
64
|
+
hasTargetFrame?: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const trimUrl = (url?: string): string => url?.trim() || "";
|
|
68
|
+
|
|
69
|
+
const isInternalScheme = (url?: string): boolean => {
|
|
70
|
+
const lowerUrl = trimUrl(url)?.toLowerCase();
|
|
71
|
+
return !!INTERNAL_SCHEMES?.some((scheme) => lowerUrl?.startsWith(scheme));
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const matchesAnyPrefix = (
|
|
75
|
+
url: string,
|
|
76
|
+
prefixes?: (string | undefined)[],
|
|
77
|
+
): boolean =>
|
|
78
|
+
!!prefixes?.some((prefix) => !!prefix?.trim() && url?.startsWith(prefix));
|
|
79
|
+
|
|
80
|
+
export const shouldLoadInChatbotWebView = (
|
|
81
|
+
request?: WebViewNavigationRequestLike,
|
|
82
|
+
chatbotUrlPrefixes?: (string | undefined)[],
|
|
83
|
+
): boolean => matchesAnyPrefix(trimUrl(request?.url), chatbotUrlPrefixes);
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* A sub-frame load is a resource the page pulled in — an embedded player, a
|
|
87
|
+
* tracking pixel — not a destination the user asked for, so it must not launch
|
|
88
|
+
* an app. A popup (`window.open`, `target="_blank"`) reports no target frame
|
|
89
|
+
* and is a real destination.
|
|
90
|
+
*
|
|
91
|
+
* Both flags are iOS-only; Android only reports frame-level navigations here.
|
|
92
|
+
*/
|
|
93
|
+
const isUserDestination = (request?: WebViewNavigationRequestLike): boolean =>
|
|
94
|
+
request?.isTopFrame !== false ||
|
|
95
|
+
request?.hasTargetFrame === false ||
|
|
96
|
+
// The chat surface asks for these from a hidden frame deliberately, so the
|
|
97
|
+
// sub-frame rule above would reject exactly the case it was written for. A
|
|
98
|
+
// pixel or embedded player is http(s) and is still rejected.
|
|
99
|
+
isAppHandoffScheme(request?.url);
|
|
100
|
+
|
|
101
|
+
const canLeaveWebView = (request?: WebViewNavigationRequestLike): boolean =>
|
|
102
|
+
!!trimUrl(request?.url) &&
|
|
103
|
+
!isInternalScheme(request?.url) &&
|
|
104
|
+
isUserDestination(request);
|
|
105
|
+
|
|
106
|
+
export const openUrlOutsideWebView = async (url?: string): Promise<boolean> => {
|
|
107
|
+
try {
|
|
108
|
+
await Linking.openURL(trimUrl(url));
|
|
109
|
+
return true;
|
|
110
|
+
} catch (error) {
|
|
111
|
+
logger.warn("Failed to open URL outside the WebView", {
|
|
112
|
+
url,
|
|
113
|
+
error: error instanceof Error ? error?.message : String(error),
|
|
114
|
+
});
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Synchronous by contract: `onShouldStartLoadWithRequest` needs its answer
|
|
121
|
+
* immediately, so the external launch is fired off without being awaited.
|
|
122
|
+
*/
|
|
123
|
+
export const handleWebViewNavigationRequest = (
|
|
124
|
+
request?: WebViewNavigationRequestLike,
|
|
125
|
+
chatbotUrlPrefixes?: (string | undefined)[],
|
|
126
|
+
): boolean => {
|
|
127
|
+
if (shouldLoadInChatbotWebView(request, chatbotUrlPrefixes)) return true;
|
|
128
|
+
if (!canLeaveWebView(request)) return false;
|
|
129
|
+
logger.debug("Opening navigation outside the WebView", { url: request?.url });
|
|
130
|
+
void openUrlOutsideWebView(request?.url);
|
|
131
|
+
return false;
|
|
132
|
+
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// This file is auto-generated. Do not modify it manually.
|
|
2
|
-
export const SDK_VERSION = '2.2.
|
|
2
|
+
export const SDK_VERSION = '2.2.3-staging.0';
|
|
Binary file
|