@lorikeetai/react-native-sdk 2.0.0-alpha.1 → 2.0.0-alpha.3
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 +97 -5
- package/dist/index.cjs +228 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +441 -15
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +441 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +229 -46
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { forwardRef, useCallback, useEffect, useRef, useState } from "react";
|
|
1
|
+
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
2
2
|
import { AppState, Linking, StyleSheet, Text, TouchableOpacity, View } from "react-native";
|
|
3
3
|
import { SafeAreaView } from "react-native-safe-area-context";
|
|
4
4
|
import { WebView } from "react-native-webview";
|
|
@@ -115,6 +115,26 @@ var LorikeetClient = class {
|
|
|
115
115
|
if (this.metadata) url.searchParams.append("metadata", JSON.stringify(this.metadata));
|
|
116
116
|
return url.toString();
|
|
117
117
|
});
|
|
118
|
+
_defineProperty(
|
|
119
|
+
this,
|
|
120
|
+
/**
|
|
121
|
+
* The page that clears this key's credentials from the WebView: its cookies,
|
|
122
|
+
* server-side, and the storage the frame keeps. Navigating a WebView here is
|
|
123
|
+
* the only way to reach either, so a host that only drops its own copy of
|
|
124
|
+
* `ticketId` and `session` leaves the next person on the device able to
|
|
125
|
+
* reopen the conversation.
|
|
126
|
+
*
|
|
127
|
+
* The page confirms with a `loggedOut` message, which the SDK reports as
|
|
128
|
+
* `logged_out`. The load event does not: an error page fires that too.
|
|
129
|
+
*/
|
|
130
|
+
"getLogoutUrl",
|
|
131
|
+
() => {
|
|
132
|
+
const url = new URL(this.baseUrl);
|
|
133
|
+
url.pathname = "/logout";
|
|
134
|
+
url.searchParams.append("public_key", this.publicKey);
|
|
135
|
+
return url.toString();
|
|
136
|
+
}
|
|
137
|
+
);
|
|
118
138
|
if (api_base) this.baseUrl = api_base;
|
|
119
139
|
this.publicKey = public_key;
|
|
120
140
|
if (customisations) this.customisations = customisations;
|
|
@@ -181,7 +201,10 @@ const eventFromMessage = (message) => {
|
|
|
181
201
|
switch (record.type) {
|
|
182
202
|
case "ready": return { type: "ready" };
|
|
183
203
|
case "customerMessage": return { type: "customer_message" };
|
|
184
|
-
case "agentMessage": return
|
|
204
|
+
case "agentMessage": return typeof record.markUnread === "boolean" ? {
|
|
205
|
+
type: "agent_message",
|
|
206
|
+
markUnread: record.markUnread
|
|
207
|
+
} : { type: "agent_message" };
|
|
185
208
|
case "closeChat": {
|
|
186
209
|
const reason = asNonEmptyString(record.reason);
|
|
187
210
|
return reason === "ended_by_customer" || reason === "minimised" ? {
|
|
@@ -205,6 +228,7 @@ const eventFromMessage = (message) => {
|
|
|
205
228
|
session
|
|
206
229
|
};
|
|
207
230
|
}
|
|
231
|
+
case "loggedOut": return { type: "logged_out" };
|
|
208
232
|
case "error": {
|
|
209
233
|
const error = asNonEmptyString(record.error);
|
|
210
234
|
return error === null ? null : {
|
|
@@ -255,6 +279,18 @@ const resumedTicketIdFromUri = (uri) => {
|
|
|
255
279
|
*/
|
|
256
280
|
const BACKGROUND_RELOAD_THRESHOLD_MS = 18e4;
|
|
257
281
|
/**
|
|
282
|
+
* How long `/logout` gets to confirm it cleared the frame before the host is
|
|
283
|
+
* told it did not. Matches the web SDK's own budget, and like it, a logout that
|
|
284
|
+
* misses the deadline leaves the widget unavailable rather than letting the next
|
|
285
|
+
* customer start a chat on a device the previous one's credentials still open.
|
|
286
|
+
*
|
|
287
|
+
* `LorikeetWidgetView` applies this to its own `logout()`. Exported because a
|
|
288
|
+
* composed host has to run the timer itself and nothing else will report
|
|
289
|
+
* `logout_failed` for it, and two hosts choosing their own budgets is how one
|
|
290
|
+
* app calls a clear failed that the other never does.
|
|
291
|
+
*/
|
|
292
|
+
const LOGOUT_CONFIRMATION_TIMEOUT_MS = 1e4;
|
|
293
|
+
/**
|
|
258
294
|
* Elapsed time is the only signal available: the bridge reports `ready`,
|
|
259
295
|
* `error` and the persistence messages, but never stream health, so the SDK
|
|
260
296
|
* cannot ask whether the connection actually survived.
|
|
@@ -381,6 +417,7 @@ const stylesheet = StyleSheet.create({
|
|
|
381
417
|
textAlign: "center",
|
|
382
418
|
fontSize: 16
|
|
383
419
|
},
|
|
420
|
+
closedControl: { padding: 16 },
|
|
384
421
|
webView: { flex: 1 },
|
|
385
422
|
closed: {
|
|
386
423
|
flex: 1,
|
|
@@ -459,8 +496,8 @@ const LorikeetWidgetHeader = ({ title, style, icon = /* @__PURE__ */ jsx(Message
|
|
|
459
496
|
]
|
|
460
497
|
});
|
|
461
498
|
};
|
|
462
|
-
function LorikeetWidgetView({ publicKey, baseUrl, authToken, customisations, styles, onEvent, onWebViewMessage, ticketId, session, metadata, webviewProps }) {
|
|
463
|
-
var _customisations$title;
|
|
499
|
+
const LorikeetWidgetView = forwardRef(function LorikeetWidgetView({ publicKey, baseUrl, authToken, customisations, styles, onEvent, onWebViewMessage, ticketId, session, metadata, webviewProps }, ref) {
|
|
500
|
+
var _customisations$hideH, _customisations$title;
|
|
464
501
|
const [lorikeetClient, setLorikeetClient] = useState(null);
|
|
465
502
|
const [loaded, setLoaded] = useState(null);
|
|
466
503
|
const [generation, setGeneration] = useState(0);
|
|
@@ -485,7 +522,12 @@ function LorikeetWidgetView({ publicKey, baseUrl, authToken, customisations, sty
|
|
|
485
522
|
});
|
|
486
523
|
const resumingTicketId = ticketId !== void 0 && ticketId !== refusedTicketId ? ticketId : void 0;
|
|
487
524
|
const [attemptedTicketId, setAttemptedTicketId] = useState(void 0);
|
|
488
|
-
const [closed, setClosed] = useState(
|
|
525
|
+
const [closed, setClosed] = useState(null);
|
|
526
|
+
const [clearing, setClearing] = useState(null);
|
|
527
|
+
const [loggedOutIdentity, setLoggedOutIdentity] = useState(null);
|
|
528
|
+
const onEventRef = useRef(onEvent);
|
|
529
|
+
onEventRef.current = onEvent;
|
|
530
|
+
const hideHeader = (_customisations$hideH = customisations === null || customisations === void 0 ? void 0 : customisations.hideHeader) !== null && _customisations$hideH !== void 0 ? _customisations$hideH : true;
|
|
489
531
|
const load = useCallback((client, forIdentity, intendedTicketId, resume, force = false) => {
|
|
490
532
|
const next = client.getWebviewUrl({
|
|
491
533
|
token: authToken,
|
|
@@ -493,7 +535,7 @@ function LorikeetWidgetView({ publicKey, baseUrl, authToken, customisations, sty
|
|
|
493
535
|
session: resume === null || resume === void 0 ? void 0 : resume.session
|
|
494
536
|
});
|
|
495
537
|
const unchanged = uriRef.current === next;
|
|
496
|
-
if (loadedForRef.current.identity !== forIdentity || loadedForRef.current.intendedTicketId !== intendedTicketId) setClosed(
|
|
538
|
+
if (loadedForRef.current.identity !== forIdentity || loadedForRef.current.intendedTicketId !== intendedTicketId) setClosed(null);
|
|
497
539
|
uriRef.current = next;
|
|
498
540
|
loadedForRef.current = {
|
|
499
541
|
identity: forIdentity,
|
|
@@ -522,10 +564,12 @@ function LorikeetWidgetView({ publicKey, baseUrl, authToken, customisations, sty
|
|
|
522
564
|
metadata,
|
|
523
565
|
customisations: {
|
|
524
566
|
...customisations,
|
|
525
|
-
hideHeader
|
|
567
|
+
hideHeader
|
|
526
568
|
}
|
|
527
569
|
});
|
|
528
570
|
setLorikeetClient(lorikeetClient);
|
|
571
|
+
if (clearing !== null || loggedOutIdentity === identity) return;
|
|
572
|
+
if (loggedOutIdentity !== null) setLoggedOutIdentity(null);
|
|
529
573
|
const resume = resumingTicketId !== void 0 ? {
|
|
530
574
|
session,
|
|
531
575
|
ticketId: resumingTicketId
|
|
@@ -535,13 +579,108 @@ function LorikeetWidgetView({ publicKey, baseUrl, authToken, customisations, sty
|
|
|
535
579
|
publicKey,
|
|
536
580
|
baseUrl,
|
|
537
581
|
authToken,
|
|
582
|
+
clearing,
|
|
538
583
|
customisations,
|
|
584
|
+
hideHeader,
|
|
585
|
+
loggedOutIdentity,
|
|
539
586
|
metadata,
|
|
540
587
|
session,
|
|
541
588
|
resumingTicketId,
|
|
542
589
|
identity,
|
|
543
590
|
load
|
|
544
591
|
]);
|
|
592
|
+
/**
|
|
593
|
+
* Clear this customer's chat from the device. Loads the page's own `/logout`,
|
|
594
|
+
* the only thing that can drop the chat's cookies and the storage the page
|
|
595
|
+
* keeps; a host clearing its stored `ticketId` and `session` leaves both
|
|
596
|
+
* behind.
|
|
597
|
+
*
|
|
598
|
+
* Nothing else loads until the page confirms with `logged_out`, and nothing
|
|
599
|
+
* loads for these credentials after it, so pass the next customer's in to
|
|
600
|
+
* start their chat. Clear your own stored `ticketId` and `session` too.
|
|
601
|
+
*/
|
|
602
|
+
const logout = useCallback(() => {
|
|
603
|
+
observedRef.current = null;
|
|
604
|
+
setClosed(null);
|
|
605
|
+
setClearing((previous) => {
|
|
606
|
+
if (previous !== null) return {
|
|
607
|
+
...previous,
|
|
608
|
+
attempt: previous.attempt + 1
|
|
609
|
+
};
|
|
610
|
+
setLoggedOutIdentity(identity);
|
|
611
|
+
return {
|
|
612
|
+
attempt: 1,
|
|
613
|
+
baseUrl,
|
|
614
|
+
identity,
|
|
615
|
+
uri: new LorikeetClient({
|
|
616
|
+
api_base: baseUrl,
|
|
617
|
+
public_key: publicKey
|
|
618
|
+
}).getLogoutUrl(),
|
|
619
|
+
webviewProps: {
|
|
620
|
+
...webviewProps,
|
|
621
|
+
injectedJavaScript: void 0,
|
|
622
|
+
injectedJavaScriptBeforeContentLoaded: void 0,
|
|
623
|
+
onMessage: void 0,
|
|
624
|
+
onShouldStartLoadWithRequest: void 0
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
});
|
|
628
|
+
}, [
|
|
629
|
+
baseUrl,
|
|
630
|
+
identity,
|
|
631
|
+
publicKey,
|
|
632
|
+
webviewProps
|
|
633
|
+
]);
|
|
634
|
+
/**
|
|
635
|
+
* Show a minimised conversation again, exactly as the customer left it.
|
|
636
|
+
*
|
|
637
|
+
* A no-op unless something is minimised, so a host with its own launcher can
|
|
638
|
+
* call it without first asking.
|
|
639
|
+
*/
|
|
640
|
+
const reopen = useCallback(() => {
|
|
641
|
+
setClosed((previous) => previous === "minimised" ? null : previous);
|
|
642
|
+
}, []);
|
|
643
|
+
/**
|
|
644
|
+
* What the widget is actually on, for a host that has to store it or draw its
|
|
645
|
+
* own launcher around it.
|
|
646
|
+
*/
|
|
647
|
+
const getConversation = useCallback(() => {
|
|
648
|
+
const resumable = resumableConversation(observedRef.current, identity, resumingTicketId);
|
|
649
|
+
if (resumable === null) return null;
|
|
650
|
+
return {
|
|
651
|
+
minimised: closed === "minimised",
|
|
652
|
+
session: resumable.session,
|
|
653
|
+
ticketId: resumable.ticketId
|
|
654
|
+
};
|
|
655
|
+
}, [
|
|
656
|
+
closed,
|
|
657
|
+
identity,
|
|
658
|
+
resumingTicketId
|
|
659
|
+
]);
|
|
660
|
+
useImperativeHandle(ref, () => ({
|
|
661
|
+
getConversation,
|
|
662
|
+
logout,
|
|
663
|
+
reopen
|
|
664
|
+
}), [
|
|
665
|
+
getConversation,
|
|
666
|
+
logout,
|
|
667
|
+
reopen
|
|
668
|
+
]);
|
|
669
|
+
const clearingAttempt = clearing === null || clearing === void 0 ? void 0 : clearing.attempt;
|
|
670
|
+
useEffect(() => {
|
|
671
|
+
if (clearingAttempt === void 0) return;
|
|
672
|
+
const timer = setTimeout(() => {
|
|
673
|
+
var _onEventRef$current;
|
|
674
|
+
(_onEventRef$current = onEventRef.current) === null || _onEventRef$current === void 0 || _onEventRef$current.call(onEventRef, { type: "logout_failed" });
|
|
675
|
+
}, LOGOUT_CONFIRMATION_TIMEOUT_MS);
|
|
676
|
+
return () => {
|
|
677
|
+
clearTimeout(timer);
|
|
678
|
+
};
|
|
679
|
+
}, [clearingAttempt]);
|
|
680
|
+
const handleClearingEvent = useCallback((event) => {
|
|
681
|
+
if (event.type === "logged_out") setClearing((previous) => (previous === null || previous === void 0 ? void 0 : previous.attempt) === clearingAttempt ? null : previous);
|
|
682
|
+
onEvent === null || onEvent === void 0 || onEvent(event);
|
|
683
|
+
}, [clearingAttempt, onEvent]);
|
|
545
684
|
const reload = useCallback(() => {
|
|
546
685
|
const resume = resumableConversation(observedRef.current, identity, resumingTicketId);
|
|
547
686
|
if (lorikeetClient !== null && resume !== null) {
|
|
@@ -573,7 +712,7 @@ function LorikeetWidgetView({ publicKey, baseUrl, authToken, customisations, sty
|
|
|
573
712
|
};
|
|
574
713
|
}, [reload]);
|
|
575
714
|
const startNewChat = useCallback(() => {
|
|
576
|
-
setClosed(
|
|
715
|
+
setClosed(null);
|
|
577
716
|
if (lorikeetClient !== null && resumingTicketId === void 0) {
|
|
578
717
|
observedRef.current = null;
|
|
579
718
|
load(lorikeetClient, identity, void 0, null, true);
|
|
@@ -599,7 +738,7 @@ function LorikeetWidgetView({ publicKey, baseUrl, authToken, customisations, sty
|
|
|
599
738
|
intendedTicketId: resumingTicketId
|
|
600
739
|
};
|
|
601
740
|
}
|
|
602
|
-
if (event.type === "close") setClosed(
|
|
741
|
+
if (event.type === "close") setClosed(event.reason === "minimised" ? "minimised" : "ended");
|
|
603
742
|
if (event.type === "new_chat") startNewChat();
|
|
604
743
|
if (event.type === "error" && attemptedTicketId !== void 0 && isUnusableConversationCode(event.error)) {
|
|
605
744
|
observedRef.current = null;
|
|
@@ -623,7 +762,31 @@ function LorikeetWidgetView({ publicKey, baseUrl, authToken, customisations, sty
|
|
|
623
762
|
onWebViewMessage === null || onWebViewMessage === void 0 || onWebViewMessage(event);
|
|
624
763
|
webviewProps === null || webviewProps === void 0 || (_webviewProps$onMessa2 = webviewProps.onMessage) === null || _webviewProps$onMessa2 === void 0 || _webviewProps$onMessa2.call(webviewProps, event);
|
|
625
764
|
}, [onWebViewMessage, webviewProps]);
|
|
626
|
-
if (
|
|
765
|
+
if (clearing !== null) return /* @__PURE__ */ jsx(SafeAreaView, {
|
|
766
|
+
edges: [
|
|
767
|
+
"top",
|
|
768
|
+
"left",
|
|
769
|
+
"right"
|
|
770
|
+
],
|
|
771
|
+
style: stylesheet.root,
|
|
772
|
+
children: /* @__PURE__ */ jsx(View, {
|
|
773
|
+
accessibilityElementsHidden: true,
|
|
774
|
+
importantForAccessibility: "no-hide-descendants",
|
|
775
|
+
pointerEvents: "none",
|
|
776
|
+
style: stylesheet.closed,
|
|
777
|
+
children: /* @__PURE__ */ jsx(LorikeetWidgetWebView, {
|
|
778
|
+
baseUrl: clearing.baseUrl,
|
|
779
|
+
onEvent: handleClearingEvent,
|
|
780
|
+
uri: clearing.uri,
|
|
781
|
+
webviewProps: {
|
|
782
|
+
...clearing.webviewProps,
|
|
783
|
+
style: stylesheet.webView
|
|
784
|
+
}
|
|
785
|
+
}, `logout|${clearing.attempt}`)
|
|
786
|
+
})
|
|
787
|
+
});
|
|
788
|
+
if (lorikeetClient === null || loaded === null || loaded.identity !== identity || loaded.intendedTicketId !== resumingTicketId || loggedOutIdentity === identity) return null;
|
|
789
|
+
const isClosed = closed !== null;
|
|
627
790
|
return /* @__PURE__ */ jsx(SafeAreaView, {
|
|
628
791
|
edges: [
|
|
629
792
|
"top",
|
|
@@ -633,45 +796,65 @@ function LorikeetWidgetView({ publicKey, baseUrl, authToken, customisations, sty
|
|
|
633
796
|
style: stylesheet.root,
|
|
634
797
|
children: /* @__PURE__ */ jsxs(View, {
|
|
635
798
|
style: stylesheet.container,
|
|
636
|
-
children: [
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
799
|
+
children: [
|
|
800
|
+
hideHeader && closed !== "minimised" ? /* @__PURE__ */ jsx(LorikeetWidgetHeader, {
|
|
801
|
+
onIconPress: startNewChat,
|
|
802
|
+
style: styles === null || styles === void 0 ? void 0 : styles.header,
|
|
803
|
+
title: (_customisations$title = customisations === null || customisations === void 0 ? void 0 : customisations.title) !== null && _customisations$title !== void 0 ? _customisations$title : "Lorikeet AI Agent"
|
|
804
|
+
}) : null,
|
|
805
|
+
closed === "minimised" ? /* @__PURE__ */ jsx(TouchableOpacity, {
|
|
806
|
+
onPress: reopen,
|
|
807
|
+
style: stylesheet.closedControl,
|
|
808
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
809
|
+
style: stylesheet.headerText,
|
|
810
|
+
children: "Reopen chat"
|
|
811
|
+
})
|
|
812
|
+
}) : null,
|
|
813
|
+
!hideHeader && closed === "ended" ? /* @__PURE__ */ jsx(TouchableOpacity, {
|
|
814
|
+
onPress: startNewChat,
|
|
815
|
+
style: stylesheet.closedControl,
|
|
816
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
817
|
+
style: stylesheet.headerText,
|
|
818
|
+
children: "Start new chat"
|
|
819
|
+
})
|
|
820
|
+
}) : null,
|
|
821
|
+
/* @__PURE__ */ jsx(View, {
|
|
822
|
+
accessibilityElementsHidden: isClosed,
|
|
823
|
+
importantForAccessibility: isClosed ? "no-hide-descendants" : "auto",
|
|
824
|
+
pointerEvents: isClosed ? "none" : "auto",
|
|
825
|
+
style: isClosed ? stylesheet.closed : stylesheet.webView,
|
|
826
|
+
children: /* @__PURE__ */ jsx(LorikeetWidgetWebView, {
|
|
827
|
+
baseUrl: lorikeetClient.baseUrl,
|
|
828
|
+
onEvent: handleEvent,
|
|
829
|
+
ref: webViewRef,
|
|
830
|
+
uri: loaded.uri,
|
|
831
|
+
webviewProps: {
|
|
832
|
+
allowsInlineMediaPlayback: true,
|
|
833
|
+
mediaPlaybackRequiresUserAction: false,
|
|
834
|
+
scalesPageToFit: true,
|
|
835
|
+
startInLoadingState: true,
|
|
836
|
+
style: stylesheet.webView,
|
|
837
|
+
...webviewProps,
|
|
838
|
+
onMessage: handleMessage,
|
|
839
|
+
onRenderProcessGone: (event) => {
|
|
840
|
+
var _webviewProps$onRende;
|
|
841
|
+
if (generation === generationRef.current) reload();
|
|
842
|
+
webviewProps === null || webviewProps === void 0 || (_webviewProps$onRende = webviewProps.onRenderProcessGone) === null || _webviewProps$onRende === void 0 || _webviewProps$onRende.call(webviewProps, event);
|
|
843
|
+
},
|
|
844
|
+
onContentProcessDidTerminate: (event) => {
|
|
845
|
+
var _webviewProps$onConte;
|
|
846
|
+
if (generation === generationRef.current) reload();
|
|
847
|
+
webviewProps === null || webviewProps === void 0 || (_webviewProps$onConte = webviewProps.onContentProcessDidTerminate) === null || _webviewProps$onConte === void 0 || _webviewProps$onConte.call(webviewProps, event);
|
|
848
|
+
}
|
|
667
849
|
}
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
|
|
850
|
+
}, `${identity}|${resumingTicketId !== null && resumingTicketId !== void 0 ? resumingTicketId : ""}|${generation}`)
|
|
851
|
+
})
|
|
852
|
+
]
|
|
671
853
|
})
|
|
672
854
|
});
|
|
673
|
-
}
|
|
855
|
+
});
|
|
856
|
+
LorikeetWidgetView.displayName = "LorikeetWidgetView";
|
|
674
857
|
|
|
675
858
|
//#endregion
|
|
676
|
-
export { LORIKEET_ERROR_CODES, LorikeetClient, LorikeetWidgetHeader, LorikeetWidgetView, LorikeetWidgetWebView, isUnusableConversation, isUnusableConversationCode };
|
|
859
|
+
export { LOGOUT_CONFIRMATION_TIMEOUT_MS, LORIKEET_ERROR_CODES, LorikeetClient, LorikeetWidgetHeader, LorikeetWidgetView, LorikeetWidgetWebView, isUnusableConversation, isUnusableConversationCode };
|
|
677
860
|
//# sourceMappingURL=index.js.map
|