@getecho-ai/react-native-sdk 1.0.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.
@@ -0,0 +1,240 @@
1
+ "use strict";
2
+ /**
3
+ * EchoProvider - Main context provider for Echo SDK
4
+ *
5
+ * Provides:
6
+ * - SDK configuration (apiKey, callbacks)
7
+ * - User session management
8
+ * - Bridge initialization
9
+ */
10
+ var __importDefault = (this && this.__importDefault) || function (mod) {
11
+ return (mod && mod.__esModule) ? mod : { "default": mod };
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.EchoProvider = exports.useEcho = void 0;
15
+ const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
16
+ const react_1 = require("react");
17
+ const resolveApiUrl_1 = require("../utils/resolveApiUrl");
18
+ /**
19
+ * Generate a UUID v4 string
20
+ */
21
+ function generateUUID() {
22
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
23
+ const r = (Math.random() * 16) | 0;
24
+ const v = c === "x" ? r : (r & 0x3) | 0x8;
25
+ return v.toString(16);
26
+ });
27
+ }
28
+ /**
29
+ * Check if a string is a valid UUID v4
30
+ */
31
+ function isValidUUID(str) {
32
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
33
+ return uuidRegex.test(str);
34
+ }
35
+ const EchoContext = (0, react_1.createContext)(null);
36
+ const useEcho = () => {
37
+ const context = (0, react_1.useContext)(EchoContext);
38
+ if (!context) {
39
+ throw new Error("useEcho must be used within EchoProvider");
40
+ }
41
+ return context;
42
+ };
43
+ exports.useEcho = useEcho;
44
+ const EchoProvider = ({ config, children, }) => {
45
+ const [userId, setUserId] = (0, react_1.useState)(config.userId || "");
46
+ const [chatId, setChatId] = (0, react_1.useState)("");
47
+ const [isReady, setIsReady] = (0, react_1.useState)(false);
48
+ // Initialize user ID from storage or generate new one
49
+ (0, react_1.useEffect)(() => {
50
+ const syncUserWithServer = async (id) => {
51
+ const baseUrl = (0, resolveApiUrl_1.resolveApiUrl)(config.apiUrl).replace(/\/+$/, "");
52
+ const controller = new AbortController();
53
+ const timeoutId = setTimeout(() => controller.abort(), 5000);
54
+ try {
55
+ const response = await fetch(`${baseUrl}/api/user`, {
56
+ method: "POST",
57
+ headers: {
58
+ "X-API-Key": config.apiKey,
59
+ "Content-Type": "application/json",
60
+ },
61
+ body: JSON.stringify({ userId: id }),
62
+ signal: controller.signal,
63
+ });
64
+ clearTimeout(timeoutId);
65
+ if (response.ok) {
66
+ const data = await response.json();
67
+ if (data.userIdChanged && data.userId) {
68
+ await async_storage_1.default.setItem("echo_user_id", data.userId);
69
+ setUserId(data.userId);
70
+ }
71
+ }
72
+ }
73
+ catch (err) {
74
+ clearTimeout(timeoutId);
75
+ if (err instanceof Error && err.name !== "AbortError") {
76
+ console.warn("[EchoSDK] User sync failed:", err.message);
77
+ }
78
+ }
79
+ };
80
+ const initUser = async () => {
81
+ try {
82
+ let storedUserId = config.userId;
83
+ let storedChatId = null;
84
+ if (!storedUserId) {
85
+ storedUserId =
86
+ (await async_storage_1.default.getItem("echo_user_id")) || undefined;
87
+ }
88
+ const shouldUseStored = storedUserId && (config.userId || isValidUUID(storedUserId));
89
+ if (!shouldUseStored) {
90
+ // Generate new UUID if no stored ID or old format (non-UUID)
91
+ storedUserId = generateUUID();
92
+ await async_storage_1.default.setItem("echo_user_id", storedUserId);
93
+ }
94
+ setUserId(storedUserId || "");
95
+ await syncUserWithServer(storedUserId || "");
96
+ try {
97
+ storedChatId = await async_storage_1.default.getItem("echo_chat_id");
98
+ }
99
+ catch {
100
+ storedChatId = null;
101
+ }
102
+ if (storedChatId && isValidUUID(storedChatId)) {
103
+ setChatId(storedChatId);
104
+ }
105
+ setIsReady(true);
106
+ }
107
+ catch (error) {
108
+ console.error("[EchoSDK] Failed to initialize user:", error);
109
+ // Fallback to temp user ID (UUID format)
110
+ const tempId = generateUUID();
111
+ setUserId(tempId);
112
+ await syncUserWithServer(tempId);
113
+ setIsReady(true);
114
+ }
115
+ };
116
+ initUser();
117
+ }, [config.userId, config.apiKey, config.apiUrl]);
118
+ const updateUserId = (0, react_1.useCallback)(async (newUserId) => {
119
+ setUserId(newUserId);
120
+ try {
121
+ await async_storage_1.default.setItem("echo_user_id", newUserId);
122
+ }
123
+ catch (error) {
124
+ console.error("[EchoSDK] Failed to persist user ID:", error);
125
+ }
126
+ }, []);
127
+ const updateChatId = (0, react_1.useCallback)(async (newChatId) => {
128
+ setChatId(newChatId);
129
+ try {
130
+ await async_storage_1.default.setItem("echo_chat_id", newChatId);
131
+ }
132
+ catch (error) {
133
+ console.error("[EchoSDK] Failed to persist chat ID:", error);
134
+ }
135
+ }, []);
136
+ /**
137
+ * Identify user with backend (matches web embed behavior)
138
+ * Calls /api/user/identify if userEmail or userIdentifier is provided
139
+ * Updates userId in AsyncStorage if backend returns userIdChanged
140
+ */
141
+ const identifyUser = (0, react_1.useCallback)(async () => {
142
+ if (!config.userEmail && !config.userIdentifier) {
143
+ return; // Nothing to identify
144
+ }
145
+ try {
146
+ const baseUrl = (0, resolveApiUrl_1.resolveApiUrl)(config.apiUrl).replace(/\/+$/, "");
147
+ const response = await fetch(`${baseUrl}/api/user/identify`, {
148
+ method: "POST",
149
+ headers: {
150
+ "Content-Type": "application/json",
151
+ "X-API-Key": config.apiKey,
152
+ },
153
+ body: JSON.stringify({
154
+ userId,
155
+ email: config.userEmail,
156
+ userIdentifier: config.userIdentifier,
157
+ }),
158
+ });
159
+ const data = await response.json();
160
+ // Handle userIdChanged (same as web embed)
161
+ if (data.userIdChanged && data.userId) {
162
+ console.log("[EchoSDK] userId changed:", userId, "→", data.userId);
163
+ await updateUserId(data.userId);
164
+ }
165
+ }
166
+ catch (error) {
167
+ console.error("[EchoSDK] Identification failed:", error);
168
+ // Don't block - continue without identification
169
+ }
170
+ }, [
171
+ userId,
172
+ config.apiKey,
173
+ config.apiUrl,
174
+ config.userEmail,
175
+ config.userIdentifier,
176
+ updateUserId,
177
+ ]);
178
+ // Auto-identify user when ready and email/identifier is provided
179
+ (0, react_1.useEffect)(() => {
180
+ if (isReady && userId && (config.userEmail || config.userIdentifier)) {
181
+ identifyUser();
182
+ }
183
+ }, [isReady, userId, config.userEmail, config.userIdentifier, identifyUser]);
184
+ // Wrap callbacks with error handling
185
+ const handleAddToCart = (0, react_1.useCallback)(async (product, quantity) => {
186
+ try {
187
+ return await config.callbacks.onAddToCart(product, quantity);
188
+ }
189
+ catch (error) {
190
+ console.error("[EchoSDK] onAddToCart error:", error);
191
+ return {
192
+ success: false,
193
+ error: error instanceof Error ? error.message : "ADD_TO_CART_FAILED",
194
+ };
195
+ }
196
+ }, [config.callbacks]);
197
+ const handleGetCart = (0, react_1.useCallback)(async () => {
198
+ try {
199
+ return await config.callbacks.onGetCart();
200
+ }
201
+ catch (error) {
202
+ console.error("[EchoSDK] onGetCart error:", error);
203
+ return {
204
+ success: false,
205
+ error: error instanceof Error ? error.message : "GET_CART_FAILED",
206
+ };
207
+ }
208
+ }, [config.callbacks]);
209
+ const handleNavigateToProduct = (0, react_1.useCallback)((productId) => {
210
+ if (config.callbacks.onNavigateToProduct) {
211
+ config.callbacks.onNavigateToProduct(productId);
212
+ }
213
+ }, [config.callbacks.onNavigateToProduct]);
214
+ const handleNavigateToCheckout = (0, react_1.useCallback)(() => {
215
+ if (config.callbacks.onNavigateToCheckout) {
216
+ config.callbacks.onNavigateToCheckout();
217
+ }
218
+ }, [config.callbacks.onNavigateToCheckout]);
219
+ const handleAuthRequired = (0, react_1.useCallback)(() => {
220
+ if (config.callbacks.onAuthRequired) {
221
+ config.callbacks.onAuthRequired();
222
+ }
223
+ }, [config.callbacks.onAuthRequired]);
224
+ const value = {
225
+ config,
226
+ userId,
227
+ chatId,
228
+ isReady,
229
+ updateUserId,
230
+ updateChatId,
231
+ handleAddToCart,
232
+ handleGetCart,
233
+ handleNavigateToProduct,
234
+ handleNavigateToCheckout,
235
+ handleAuthRequired,
236
+ };
237
+ return <EchoContext.Provider value={value}>{children}</EchoContext.Provider>;
238
+ };
239
+ exports.EchoProvider = EchoProvider;
240
+ exports.default = exports.EchoProvider;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * useSimpleCart - Ready-to-use cart management hook
3
+ *
4
+ * Provides a minimal cart implementation for quick SDK integration.
5
+ * For production apps, you'll likely want to replace this with your own cart logic.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * import { EchoProvider, EchoChat, useSimpleCart } from '@getecho-ai/react-native-sdk';
10
+ *
11
+ * function App() {
12
+ * const { cart, callbacks } = useSimpleCart();
13
+ *
14
+ * return (
15
+ * <EchoProvider config={{ apiKey: 'your-key', callbacks }}>
16
+ * <YourApp cart={cart} />
17
+ * <EchoChat />
18
+ * </EchoProvider>
19
+ * );
20
+ * }
21
+ * ```
22
+ */
23
+ import type { Cart, EchoCallbacks } from "../types";
24
+ export type UseSimpleCartOptions = {
25
+ /**
26
+ * Initial cart state
27
+ */
28
+ initialCart?: Cart;
29
+ /**
30
+ * Called when cart changes (for persistence or sync)
31
+ */
32
+ onCartChange?: (cart: Cart) => void;
33
+ /**
34
+ * Called when navigating to a product
35
+ */
36
+ onNavigateToProduct?: (productId: string) => void;
37
+ /**
38
+ * Called when navigating to checkout
39
+ */
40
+ onNavigateToCheckout?: () => void;
41
+ /**
42
+ * Called when auth is required
43
+ */
44
+ onAuthRequired?: () => void;
45
+ };
46
+ export type UseSimpleCartReturn = {
47
+ /**
48
+ * Current cart state
49
+ */
50
+ cart: Cart;
51
+ /**
52
+ * Update cart directly (for external sync)
53
+ */
54
+ setCart: React.Dispatch<React.SetStateAction<Cart>>;
55
+ /**
56
+ * Clear all items from cart
57
+ */
58
+ clearCart: () => void;
59
+ /**
60
+ * Remove a specific item from cart
61
+ */
62
+ removeItem: (productId: string) => void;
63
+ /**
64
+ * Update quantity for a specific item
65
+ */
66
+ updateQuantity: (productId: string, quantity: number) => void;
67
+ /**
68
+ * Ready-to-use callbacks for EchoProvider
69
+ */
70
+ callbacks: EchoCallbacks;
71
+ };
72
+ /**
73
+ * Simple cart management hook for quick Echo SDK integration
74
+ */
75
+ export declare function useSimpleCart(options?: UseSimpleCartOptions): UseSimpleCartReturn;
76
+ export default useSimpleCart;
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ /**
3
+ * useSimpleCart - Ready-to-use cart management hook
4
+ *
5
+ * Provides a minimal cart implementation for quick SDK integration.
6
+ * For production apps, you'll likely want to replace this with your own cart logic.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * import { EchoProvider, EchoChat, useSimpleCart } from '@getecho-ai/react-native-sdk';
11
+ *
12
+ * function App() {
13
+ * const { cart, callbacks } = useSimpleCart();
14
+ *
15
+ * return (
16
+ * <EchoProvider config={{ apiKey: 'your-key', callbacks }}>
17
+ * <YourApp cart={cart} />
18
+ * <EchoChat />
19
+ * </EchoProvider>
20
+ * );
21
+ * }
22
+ * ```
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.useSimpleCart = useSimpleCart;
26
+ const react_1 = require("react");
27
+ const DEFAULT_CART = {
28
+ items: [],
29
+ itemCount: 0,
30
+ total: 0,
31
+ currency: "TRY",
32
+ };
33
+ /**
34
+ * Simple cart management hook for quick Echo SDK integration
35
+ */
36
+ function useSimpleCart(options = {}) {
37
+ const { initialCart = DEFAULT_CART, onCartChange, onNavigateToProduct, onNavigateToCheckout, onAuthRequired, } = options;
38
+ const [cart, setCart] = (0, react_1.useState)(initialCart);
39
+ const updateCart = (0, react_1.useCallback)((newCart) => {
40
+ setCart(newCart);
41
+ onCartChange?.(newCart);
42
+ }, [onCartChange]);
43
+ const clearCart = (0, react_1.useCallback)(() => {
44
+ updateCart(DEFAULT_CART);
45
+ }, [updateCart]);
46
+ const removeItem = (0, react_1.useCallback)((productId) => {
47
+ setCart((prev) => {
48
+ const newItems = prev.items.filter((item) => item.productId !== productId);
49
+ const newCart = calculateCartTotals(newItems, prev.currency);
50
+ onCartChange?.(newCart);
51
+ return newCart;
52
+ });
53
+ }, [onCartChange]);
54
+ const updateQuantity = (0, react_1.useCallback)((productId, quantity) => {
55
+ if (quantity <= 0) {
56
+ removeItem(productId);
57
+ return;
58
+ }
59
+ setCart((prev) => {
60
+ const newItems = prev.items.map((item) => item.productId === productId ? { ...item, quantity } : item);
61
+ const newCart = calculateCartTotals(newItems, prev.currency);
62
+ onCartChange?.(newCart);
63
+ return newCart;
64
+ });
65
+ }, [onCartChange, removeItem]);
66
+ // Echo SDK callbacks
67
+ const onAddToCart = (0, react_1.useCallback)(async (product, quantity = 1) => {
68
+ setCart((prev) => {
69
+ const existingIndex = prev.items.findIndex((item) => item.productId === product.id);
70
+ let newItems;
71
+ if (existingIndex >= 0) {
72
+ // Update existing item quantity
73
+ newItems = prev.items.map((item, index) => index === existingIndex
74
+ ? { ...item, quantity: item.quantity + quantity }
75
+ : item);
76
+ }
77
+ else {
78
+ // Add new item
79
+ const newItem = {
80
+ productId: product.id,
81
+ quantity,
82
+ product,
83
+ };
84
+ newItems = [...prev.items, newItem];
85
+ }
86
+ const newCart = calculateCartTotals(newItems, product.currency || prev.currency);
87
+ onCartChange?.(newCart);
88
+ return newCart;
89
+ });
90
+ // Return success with updated count (we calculate it here for immediate response)
91
+ return {
92
+ success: true,
93
+ cartItemCount: cart.itemCount + quantity,
94
+ };
95
+ }, [cart.itemCount, onCartChange]);
96
+ const onGetCart = (0, react_1.useCallback)(async () => {
97
+ return {
98
+ success: true,
99
+ cart,
100
+ };
101
+ }, [cart]);
102
+ const callbacks = {
103
+ onAddToCart,
104
+ onGetCart,
105
+ onNavigateToProduct,
106
+ onNavigateToCheckout,
107
+ onAuthRequired,
108
+ };
109
+ return {
110
+ cart,
111
+ setCart,
112
+ clearCart,
113
+ removeItem,
114
+ updateQuantity,
115
+ callbacks,
116
+ };
117
+ }
118
+ /**
119
+ * Calculate cart totals from items
120
+ */
121
+ function calculateCartTotals(items, currency) {
122
+ const itemCount = items.reduce((sum, item) => sum + item.quantity, 0);
123
+ const total = items.reduce((sum, item) => {
124
+ const price = item.product?.priceAmount || 0;
125
+ return sum + price * item.quantity;
126
+ }, 0);
127
+ return {
128
+ items,
129
+ itemCount,
130
+ total,
131
+ currency: currency || "TRY",
132
+ };
133
+ }
134
+ exports.default = useSimpleCart;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Echo React Native SDK
3
+ *
4
+ * AI-powered chat and e-commerce SDK for React Native apps.
5
+ *
6
+ * @example Quick Start (with built-in cart)
7
+ * ```tsx
8
+ * import { EchoProvider, EchoChat, useSimpleCart } from '@getecho-ai/react-native-sdk';
9
+ *
10
+ * function App() {
11
+ * const { callbacks } = useSimpleCart();
12
+ *
13
+ * return (
14
+ * <EchoProvider config={{ apiKey: 'your-key', callbacks }}>
15
+ * <YourApp />
16
+ * <EchoChat />
17
+ * </EchoProvider>
18
+ * );
19
+ * }
20
+ * ```
21
+ *
22
+ * @example Production Setup (custom cart)
23
+ * ```tsx
24
+ * import { EchoProvider, EchoChat } from '@getecho-ai/react-native-sdk';
25
+ *
26
+ * function App() {
27
+ * return (
28
+ * <EchoProvider
29
+ * config={{
30
+ * apiKey: 'your-api-key',
31
+ * callbacks: {
32
+ * onAddToCart: async (product, quantity) => {
33
+ * // Your add to cart logic
34
+ * return { success: true, cartItemCount: 5 };
35
+ * },
36
+ * onGetCart: async () => {
37
+ * // Return your cart
38
+ * return { success: true, cart: yourCart };
39
+ * },
40
+ * }
41
+ * }}
42
+ * >
43
+ * <YourApp />
44
+ * <EchoChat />
45
+ * </EchoProvider>
46
+ * );
47
+ * }
48
+ * ```
49
+ */
50
+ export { default as callbackManager } from "./bridge/CallbackManager";
51
+ export { default as webViewBridge } from "./bridge/WebViewBridge";
52
+ export { EchoChat } from "./components/EchoChat";
53
+ export { EchoChatButton } from "./components/EchoChatButton";
54
+ export { EchoChatModal } from "./components/EchoChatModal";
55
+ export { EchoProvider, useEcho } from "./components/EchoProvider";
56
+ export type { UseSimpleCartOptions, UseSimpleCartReturn, } from "./hooks/useSimpleCart";
57
+ export { useSimpleCart } from "./hooks/useSimpleCart";
58
+ export type { ActionType, AddToCartResult, BridgeMessage, BridgeMessageType, CallbackResult, Cart, CartItem, ChatMessage, EchoCallbacks, EchoConfig, EchoTheme, GetCartResult, MessageAction, Product, UISettings, } from "./types";
59
+ export { getLocalhostUrl, resolveApiUrl } from "./utils/resolveApiUrl";
package/dist/index.js ADDED
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ /**
3
+ * Echo React Native SDK
4
+ *
5
+ * AI-powered chat and e-commerce SDK for React Native apps.
6
+ *
7
+ * @example Quick Start (with built-in cart)
8
+ * ```tsx
9
+ * import { EchoProvider, EchoChat, useSimpleCart } from '@getecho-ai/react-native-sdk';
10
+ *
11
+ * function App() {
12
+ * const { callbacks } = useSimpleCart();
13
+ *
14
+ * return (
15
+ * <EchoProvider config={{ apiKey: 'your-key', callbacks }}>
16
+ * <YourApp />
17
+ * <EchoChat />
18
+ * </EchoProvider>
19
+ * );
20
+ * }
21
+ * ```
22
+ *
23
+ * @example Production Setup (custom cart)
24
+ * ```tsx
25
+ * import { EchoProvider, EchoChat } from '@getecho-ai/react-native-sdk';
26
+ *
27
+ * function App() {
28
+ * return (
29
+ * <EchoProvider
30
+ * config={{
31
+ * apiKey: 'your-api-key',
32
+ * callbacks: {
33
+ * onAddToCart: async (product, quantity) => {
34
+ * // Your add to cart logic
35
+ * return { success: true, cartItemCount: 5 };
36
+ * },
37
+ * onGetCart: async () => {
38
+ * // Return your cart
39
+ * return { success: true, cart: yourCart };
40
+ * },
41
+ * }
42
+ * }}
43
+ * >
44
+ * <YourApp />
45
+ * <EchoChat />
46
+ * </EchoProvider>
47
+ * );
48
+ * }
49
+ * ```
50
+ */
51
+ var __importDefault = (this && this.__importDefault) || function (mod) {
52
+ return (mod && mod.__esModule) ? mod : { "default": mod };
53
+ };
54
+ Object.defineProperty(exports, "__esModule", { value: true });
55
+ exports.resolveApiUrl = exports.getLocalhostUrl = exports.useSimpleCart = exports.useEcho = exports.EchoProvider = exports.EchoChatModal = exports.EchoChatButton = exports.EchoChat = exports.webViewBridge = exports.callbackManager = void 0;
56
+ var CallbackManager_1 = require("./bridge/CallbackManager");
57
+ Object.defineProperty(exports, "callbackManager", { enumerable: true, get: function () { return __importDefault(CallbackManager_1).default; } });
58
+ // Bridge utilities (advanced usage)
59
+ var WebViewBridge_1 = require("./bridge/WebViewBridge");
60
+ Object.defineProperty(exports, "webViewBridge", { enumerable: true, get: function () { return __importDefault(WebViewBridge_1).default; } });
61
+ var EchoChat_1 = require("./components/EchoChat");
62
+ Object.defineProperty(exports, "EchoChat", { enumerable: true, get: function () { return EchoChat_1.EchoChat; } });
63
+ var EchoChatButton_1 = require("./components/EchoChatButton");
64
+ Object.defineProperty(exports, "EchoChatButton", { enumerable: true, get: function () { return EchoChatButton_1.EchoChatButton; } });
65
+ var EchoChatModal_1 = require("./components/EchoChatModal");
66
+ Object.defineProperty(exports, "EchoChatModal", { enumerable: true, get: function () { return EchoChatModal_1.EchoChatModal; } });
67
+ // Components
68
+ var EchoProvider_1 = require("./components/EchoProvider");
69
+ Object.defineProperty(exports, "EchoProvider", { enumerable: true, get: function () { return EchoProvider_1.EchoProvider; } });
70
+ Object.defineProperty(exports, "useEcho", { enumerable: true, get: function () { return EchoProvider_1.useEcho; } });
71
+ // Hooks
72
+ var useSimpleCart_1 = require("./hooks/useSimpleCart");
73
+ Object.defineProperty(exports, "useSimpleCart", { enumerable: true, get: function () { return useSimpleCart_1.useSimpleCart; } });
74
+ // Utilities
75
+ var resolveApiUrl_1 = require("./utils/resolveApiUrl");
76
+ Object.defineProperty(exports, "getLocalhostUrl", { enumerable: true, get: function () { return resolveApiUrl_1.getLocalhostUrl; } });
77
+ Object.defineProperty(exports, "resolveApiUrl", { enumerable: true, get: function () { return resolveApiUrl_1.resolveApiUrl; } });