@sendoracloud/sdk-react-native 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contact-widget.cjs +127 -0
- package/dist/contact-widget.d.cts +70 -0
- package/dist/contact-widget.d.ts +70 -0
- package/dist/contact-widget.js +91 -0
- package/package.json +15 -3
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/contact-widget.tsx
|
|
31
|
+
var contact_widget_exports = {};
|
|
32
|
+
__export(contact_widget_exports, {
|
|
33
|
+
ContactWidget: () => ContactWidget,
|
|
34
|
+
useContactWidget: () => useContactWidget
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(contact_widget_exports);
|
|
37
|
+
var React = __toESM(require("react"), 1);
|
|
38
|
+
var import_react_native = require("react-native");
|
|
39
|
+
var import_react_native_webview = require("react-native-webview");
|
|
40
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
41
|
+
function ContactWidget({
|
|
42
|
+
visible,
|
|
43
|
+
widgetId,
|
|
44
|
+
workerHost = "https://go.sendoracloud.com",
|
|
45
|
+
theme = "auto",
|
|
46
|
+
prefillName,
|
|
47
|
+
prefillEmail,
|
|
48
|
+
onClose
|
|
49
|
+
}) {
|
|
50
|
+
const didFire = React.useRef(false);
|
|
51
|
+
React.useEffect(() => {
|
|
52
|
+
if (visible) didFire.current = false;
|
|
53
|
+
}, [visible]);
|
|
54
|
+
if (!widgetId) return null;
|
|
55
|
+
const params = new URLSearchParams({ widgetId, theme });
|
|
56
|
+
if (prefillName) params.set("prefillName", prefillName);
|
|
57
|
+
if (prefillEmail) params.set("prefillEmail", prefillEmail);
|
|
58
|
+
const uri = `${workerHost}/embed/contact?${params.toString()}`;
|
|
59
|
+
const handleClose = (result) => {
|
|
60
|
+
if (didFire.current) return;
|
|
61
|
+
didFire.current = true;
|
|
62
|
+
onClose(result);
|
|
63
|
+
};
|
|
64
|
+
const onNav = (req) => {
|
|
65
|
+
const url = req.url || "";
|
|
66
|
+
if (url.startsWith("sendora://close")) {
|
|
67
|
+
const queryStart = url.indexOf("?");
|
|
68
|
+
const params2 = queryStart >= 0 ? new URLSearchParams(url.slice(queryStart + 1)) : new URLSearchParams();
|
|
69
|
+
const ticketId = params2.get("ticketId") || "";
|
|
70
|
+
const portalUrl = params2.get("portalUrl") || null;
|
|
71
|
+
handleClose({ ticketId, portalUrl, submitted: ticketId !== "" });
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
if (!url.startsWith("https://") && !url.startsWith("about:")) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
};
|
|
79
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
80
|
+
import_react_native.Modal,
|
|
81
|
+
{
|
|
82
|
+
visible,
|
|
83
|
+
animationType: "slide",
|
|
84
|
+
presentationStyle: "pageSheet",
|
|
85
|
+
onRequestClose: () => handleClose({ ticketId: "", portalUrl: null, submitted: false }),
|
|
86
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native.View, { style: { flex: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
87
|
+
import_react_native_webview.WebView,
|
|
88
|
+
{
|
|
89
|
+
source: { uri },
|
|
90
|
+
onShouldStartLoadWithRequest: onNav,
|
|
91
|
+
originWhitelist: ["https://*", "about:*", "sendora://*"],
|
|
92
|
+
setSupportMultipleWindows: false,
|
|
93
|
+
javaScriptEnabled: true,
|
|
94
|
+
domStorageEnabled: true,
|
|
95
|
+
allowsBackForwardNavigationGestures: false
|
|
96
|
+
}
|
|
97
|
+
) })
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
function useContactWidget(widgetId, defaults) {
|
|
102
|
+
const [visible, setVisible] = React.useState(false);
|
|
103
|
+
const [lastResult, setLastResult] = React.useState(null);
|
|
104
|
+
const open = React.useCallback(() => setVisible(true), []);
|
|
105
|
+
const close = React.useCallback(() => setVisible(false), []);
|
|
106
|
+
const View2 = React.useCallback(
|
|
107
|
+
() => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
108
|
+
ContactWidget,
|
|
109
|
+
{
|
|
110
|
+
...defaults,
|
|
111
|
+
widgetId,
|
|
112
|
+
visible,
|
|
113
|
+
onClose: (r) => {
|
|
114
|
+
setLastResult(r);
|
|
115
|
+
setVisible(false);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
),
|
|
119
|
+
[defaults, widgetId, visible]
|
|
120
|
+
);
|
|
121
|
+
return { open, close, View: View2, lastResult };
|
|
122
|
+
}
|
|
123
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
124
|
+
0 && (module.exports = {
|
|
125
|
+
ContactWidget,
|
|
126
|
+
useContactWidget
|
|
127
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export { WebViewMessageEvent } from 'react-native-webview';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Wave 66 — ContactWidget for React Native apps.
|
|
6
|
+
*
|
|
7
|
+
* Renders the worker-hosted embed at
|
|
8
|
+
* `https://go.sendoracloud.com/embed/contact?widgetId=…` inside a
|
|
9
|
+
* `<WebView>` from `react-native-webview`. The form posts to
|
|
10
|
+
* `POST /widgets/:id/submit` server-side.
|
|
11
|
+
*
|
|
12
|
+
* On submit success the embed page navigates to
|
|
13
|
+
* `sendora://close?ticketId=…&portalUrl=…`. WebView's
|
|
14
|
+
* `onShouldStartLoadWithRequest` catches the scheme + fires
|
|
15
|
+
* `onClose(result)` so the host can hide the modal.
|
|
16
|
+
*
|
|
17
|
+
* Single source of truth = the worker route. UI updates = redeploy
|
|
18
|
+
* worker. Zero SDK release.
|
|
19
|
+
*
|
|
20
|
+
* Import from subpath so customers without react-native-webview can
|
|
21
|
+
* still use the main SDK without Metro choking on a missing dep:
|
|
22
|
+
*
|
|
23
|
+
* import { ContactWidget } from "@sendoracloud/sdk-react-native/contact-widget";
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
interface ContactWidgetResult {
|
|
27
|
+
/** UUID of the ticket created server-side, or empty if user closed without submitting. */
|
|
28
|
+
ticketId: string;
|
|
29
|
+
/** Operator's tracking-portal URL string, or null. */
|
|
30
|
+
portalUrl: string | null;
|
|
31
|
+
/** True if the user submitted the form; false if they closed without submitting. */
|
|
32
|
+
submitted: boolean;
|
|
33
|
+
}
|
|
34
|
+
interface ContactWidgetProps {
|
|
35
|
+
/** Whether the modal sheet is visible. */
|
|
36
|
+
visible: boolean;
|
|
37
|
+
/** UUID of the widget configured in the Sendora dashboard. */
|
|
38
|
+
widgetId: string;
|
|
39
|
+
/** Worker host. Defaults to production. */
|
|
40
|
+
workerHost?: string;
|
|
41
|
+
/** `"auto"` | `"light"` | `"dark"`. Default `"auto"`. */
|
|
42
|
+
theme?: "auto" | "light" | "dark";
|
|
43
|
+
/** Pre-fill name field for signed-in users. */
|
|
44
|
+
prefillName?: string;
|
|
45
|
+
/** Pre-fill email field for signed-in users. */
|
|
46
|
+
prefillEmail?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Fired when the user closes the sheet — either after a successful
|
|
49
|
+
* submit (`submitted = true`) or via the close button
|
|
50
|
+
* (`submitted = false`). Host should set `visible = false` here.
|
|
51
|
+
*/
|
|
52
|
+
onClose: (result: ContactWidgetResult) => void;
|
|
53
|
+
}
|
|
54
|
+
declare function ContactWidget({ visible, widgetId, workerHost, theme, prefillName, prefillEmail, onClose, }: ContactWidgetProps): React.ReactElement | null;
|
|
55
|
+
/**
|
|
56
|
+
* Helper hook so the host doesn't have to wire visibility state by
|
|
57
|
+
* hand.
|
|
58
|
+
*
|
|
59
|
+
* const contact = useContactWidget("widget-uuid");
|
|
60
|
+
* <Button onPress={contact.open} title="Contact us" />
|
|
61
|
+
* <contact.View />
|
|
62
|
+
*/
|
|
63
|
+
declare function useContactWidget(widgetId: string, defaults?: Omit<ContactWidgetProps, "visible" | "widgetId" | "onClose">): {
|
|
64
|
+
open: () => void;
|
|
65
|
+
close: () => void;
|
|
66
|
+
View: () => React.ReactElement | null;
|
|
67
|
+
lastResult: ContactWidgetResult | null;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export { ContactWidget, type ContactWidgetProps, type ContactWidgetResult, useContactWidget };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export { WebViewMessageEvent } from 'react-native-webview';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Wave 66 — ContactWidget for React Native apps.
|
|
6
|
+
*
|
|
7
|
+
* Renders the worker-hosted embed at
|
|
8
|
+
* `https://go.sendoracloud.com/embed/contact?widgetId=…` inside a
|
|
9
|
+
* `<WebView>` from `react-native-webview`. The form posts to
|
|
10
|
+
* `POST /widgets/:id/submit` server-side.
|
|
11
|
+
*
|
|
12
|
+
* On submit success the embed page navigates to
|
|
13
|
+
* `sendora://close?ticketId=…&portalUrl=…`. WebView's
|
|
14
|
+
* `onShouldStartLoadWithRequest` catches the scheme + fires
|
|
15
|
+
* `onClose(result)` so the host can hide the modal.
|
|
16
|
+
*
|
|
17
|
+
* Single source of truth = the worker route. UI updates = redeploy
|
|
18
|
+
* worker. Zero SDK release.
|
|
19
|
+
*
|
|
20
|
+
* Import from subpath so customers without react-native-webview can
|
|
21
|
+
* still use the main SDK without Metro choking on a missing dep:
|
|
22
|
+
*
|
|
23
|
+
* import { ContactWidget } from "@sendoracloud/sdk-react-native/contact-widget";
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
interface ContactWidgetResult {
|
|
27
|
+
/** UUID of the ticket created server-side, or empty if user closed without submitting. */
|
|
28
|
+
ticketId: string;
|
|
29
|
+
/** Operator's tracking-portal URL string, or null. */
|
|
30
|
+
portalUrl: string | null;
|
|
31
|
+
/** True if the user submitted the form; false if they closed without submitting. */
|
|
32
|
+
submitted: boolean;
|
|
33
|
+
}
|
|
34
|
+
interface ContactWidgetProps {
|
|
35
|
+
/** Whether the modal sheet is visible. */
|
|
36
|
+
visible: boolean;
|
|
37
|
+
/** UUID of the widget configured in the Sendora dashboard. */
|
|
38
|
+
widgetId: string;
|
|
39
|
+
/** Worker host. Defaults to production. */
|
|
40
|
+
workerHost?: string;
|
|
41
|
+
/** `"auto"` | `"light"` | `"dark"`. Default `"auto"`. */
|
|
42
|
+
theme?: "auto" | "light" | "dark";
|
|
43
|
+
/** Pre-fill name field for signed-in users. */
|
|
44
|
+
prefillName?: string;
|
|
45
|
+
/** Pre-fill email field for signed-in users. */
|
|
46
|
+
prefillEmail?: string;
|
|
47
|
+
/**
|
|
48
|
+
* Fired when the user closes the sheet — either after a successful
|
|
49
|
+
* submit (`submitted = true`) or via the close button
|
|
50
|
+
* (`submitted = false`). Host should set `visible = false` here.
|
|
51
|
+
*/
|
|
52
|
+
onClose: (result: ContactWidgetResult) => void;
|
|
53
|
+
}
|
|
54
|
+
declare function ContactWidget({ visible, widgetId, workerHost, theme, prefillName, prefillEmail, onClose, }: ContactWidgetProps): React.ReactElement | null;
|
|
55
|
+
/**
|
|
56
|
+
* Helper hook so the host doesn't have to wire visibility state by
|
|
57
|
+
* hand.
|
|
58
|
+
*
|
|
59
|
+
* const contact = useContactWidget("widget-uuid");
|
|
60
|
+
* <Button onPress={contact.open} title="Contact us" />
|
|
61
|
+
* <contact.View />
|
|
62
|
+
*/
|
|
63
|
+
declare function useContactWidget(widgetId: string, defaults?: Omit<ContactWidgetProps, "visible" | "widgetId" | "onClose">): {
|
|
64
|
+
open: () => void;
|
|
65
|
+
close: () => void;
|
|
66
|
+
View: () => React.ReactElement | null;
|
|
67
|
+
lastResult: ContactWidgetResult | null;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export { ContactWidget, type ContactWidgetProps, type ContactWidgetResult, useContactWidget };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// src/contact-widget.tsx
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { Modal, View } from "react-native";
|
|
4
|
+
import { WebView } from "react-native-webview";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
function ContactWidget({
|
|
7
|
+
visible,
|
|
8
|
+
widgetId,
|
|
9
|
+
workerHost = "https://go.sendoracloud.com",
|
|
10
|
+
theme = "auto",
|
|
11
|
+
prefillName,
|
|
12
|
+
prefillEmail,
|
|
13
|
+
onClose
|
|
14
|
+
}) {
|
|
15
|
+
const didFire = React.useRef(false);
|
|
16
|
+
React.useEffect(() => {
|
|
17
|
+
if (visible) didFire.current = false;
|
|
18
|
+
}, [visible]);
|
|
19
|
+
if (!widgetId) return null;
|
|
20
|
+
const params = new URLSearchParams({ widgetId, theme });
|
|
21
|
+
if (prefillName) params.set("prefillName", prefillName);
|
|
22
|
+
if (prefillEmail) params.set("prefillEmail", prefillEmail);
|
|
23
|
+
const uri = `${workerHost}/embed/contact?${params.toString()}`;
|
|
24
|
+
const handleClose = (result) => {
|
|
25
|
+
if (didFire.current) return;
|
|
26
|
+
didFire.current = true;
|
|
27
|
+
onClose(result);
|
|
28
|
+
};
|
|
29
|
+
const onNav = (req) => {
|
|
30
|
+
const url = req.url || "";
|
|
31
|
+
if (url.startsWith("sendora://close")) {
|
|
32
|
+
const queryStart = url.indexOf("?");
|
|
33
|
+
const params2 = queryStart >= 0 ? new URLSearchParams(url.slice(queryStart + 1)) : new URLSearchParams();
|
|
34
|
+
const ticketId = params2.get("ticketId") || "";
|
|
35
|
+
const portalUrl = params2.get("portalUrl") || null;
|
|
36
|
+
handleClose({ ticketId, portalUrl, submitted: ticketId !== "" });
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (!url.startsWith("https://") && !url.startsWith("about:")) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
};
|
|
44
|
+
return /* @__PURE__ */ jsx(
|
|
45
|
+
Modal,
|
|
46
|
+
{
|
|
47
|
+
visible,
|
|
48
|
+
animationType: "slide",
|
|
49
|
+
presentationStyle: "pageSheet",
|
|
50
|
+
onRequestClose: () => handleClose({ ticketId: "", portalUrl: null, submitted: false }),
|
|
51
|
+
children: /* @__PURE__ */ jsx(View, { style: { flex: 1 }, children: /* @__PURE__ */ jsx(
|
|
52
|
+
WebView,
|
|
53
|
+
{
|
|
54
|
+
source: { uri },
|
|
55
|
+
onShouldStartLoadWithRequest: onNav,
|
|
56
|
+
originWhitelist: ["https://*", "about:*", "sendora://*"],
|
|
57
|
+
setSupportMultipleWindows: false,
|
|
58
|
+
javaScriptEnabled: true,
|
|
59
|
+
domStorageEnabled: true,
|
|
60
|
+
allowsBackForwardNavigationGestures: false
|
|
61
|
+
}
|
|
62
|
+
) })
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
function useContactWidget(widgetId, defaults) {
|
|
67
|
+
const [visible, setVisible] = React.useState(false);
|
|
68
|
+
const [lastResult, setLastResult] = React.useState(null);
|
|
69
|
+
const open = React.useCallback(() => setVisible(true), []);
|
|
70
|
+
const close = React.useCallback(() => setVisible(false), []);
|
|
71
|
+
const View2 = React.useCallback(
|
|
72
|
+
() => /* @__PURE__ */ jsx(
|
|
73
|
+
ContactWidget,
|
|
74
|
+
{
|
|
75
|
+
...defaults,
|
|
76
|
+
widgetId,
|
|
77
|
+
visible,
|
|
78
|
+
onClose: (r) => {
|
|
79
|
+
setLastResult(r);
|
|
80
|
+
setVisible(false);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
),
|
|
84
|
+
[defaults, widgetId, visible]
|
|
85
|
+
);
|
|
86
|
+
return { open, close, View: View2, lastResult };
|
|
87
|
+
}
|
|
88
|
+
export {
|
|
89
|
+
ContactWidget,
|
|
90
|
+
useContactWidget
|
|
91
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sendoracloud/sdk-react-native",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Sendora Cloud React Native + Expo SDK — analytics, identity, push-token registration, auth, deep links (Branch / Firebase Dynamic Links parity). Auth + analytics + deep links work in Expo Go; push token registration requires a Dev Client (EAS Build or `npx expo prebuild`).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.js",
|
|
13
13
|
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./contact-widget": {
|
|
16
|
+
"types": "./dist/contact-widget.d.ts",
|
|
17
|
+
"import": "./dist/contact-widget.js",
|
|
18
|
+
"require": "./dist/contact-widget.cjs"
|
|
14
19
|
}
|
|
15
20
|
},
|
|
16
21
|
"files": [
|
|
@@ -32,7 +37,8 @@
|
|
|
32
37
|
},
|
|
33
38
|
"peerDependencies": {
|
|
34
39
|
"@react-native-async-storage/async-storage": ">=1.17.0",
|
|
35
|
-
"react-native": ">=0.70.0"
|
|
40
|
+
"react-native": ">=0.70.0",
|
|
41
|
+
"react-native-webview": ">=13.0.0"
|
|
36
42
|
},
|
|
37
43
|
"peerDependenciesMeta": {
|
|
38
44
|
"@react-native-async-storage/async-storage": {
|
|
@@ -40,13 +46,19 @@
|
|
|
40
46
|
},
|
|
41
47
|
"react-native": {
|
|
42
48
|
"optional": false
|
|
49
|
+
},
|
|
50
|
+
"react-native-webview": {
|
|
51
|
+
"optional": true
|
|
43
52
|
}
|
|
44
53
|
},
|
|
45
54
|
"scripts": {
|
|
46
|
-
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
55
|
+
"build": "tsup src/index.ts src/contact-widget.tsx --format esm,cjs --dts --clean --external react --external react-native --external react-native-webview",
|
|
47
56
|
"typecheck": "tsc --noEmit"
|
|
48
57
|
},
|
|
49
58
|
"devDependencies": {
|
|
59
|
+
"@types/react": "^19.2.16",
|
|
60
|
+
"react-native": "^0.85.3",
|
|
61
|
+
"react-native-webview": "^13.16.1",
|
|
50
62
|
"tsup": "^8.0.0",
|
|
51
63
|
"typescript": "^5.0.0"
|
|
52
64
|
},
|