@getecho-ai/react-native-sdk 1.0.0 → 1.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/README.md +83 -0
- package/dist/components/EchoProvider.d.ts +3 -1
- package/dist/components/EchoProvider.js +84 -0
- package/dist/index.d.ts +1 -1
- package/dist/types/index.d.ts +15 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -202,6 +202,8 @@ const {
|
|
|
202
202
|
userId, // Current user ID
|
|
203
203
|
chatId, // Current chat ID
|
|
204
204
|
isReady, // SDK initialized
|
|
205
|
+
identify, // Identify user (login)
|
|
206
|
+
logout, // Reset to anonymous user
|
|
205
207
|
} = useEcho();
|
|
206
208
|
```
|
|
207
209
|
|
|
@@ -252,6 +254,87 @@ Control visibility of UI elements:
|
|
|
252
254
|
>
|
|
253
255
|
```
|
|
254
256
|
|
|
257
|
+
## User Authentication
|
|
258
|
+
|
|
259
|
+
Echo SDK supports three user states:
|
|
260
|
+
|
|
261
|
+
1. **Anonymous** - Default state. A random UUID is generated and persisted automatically.
|
|
262
|
+
2. **Identified** - User is linked to an email or identifier. Chat history migrates from anonymous to identified user.
|
|
263
|
+
3. **Logged out** - User is reset to a new anonymous state with a fresh UUID.
|
|
264
|
+
|
|
265
|
+
### Identifying Users
|
|
266
|
+
|
|
267
|
+
**Option 1: Via config (automatic)**
|
|
268
|
+
|
|
269
|
+
Pass `userEmail` or `userIdentifier` in the provider config. The SDK auto-identifies on mount:
|
|
270
|
+
|
|
271
|
+
```tsx
|
|
272
|
+
<EchoProvider
|
|
273
|
+
config={{
|
|
274
|
+
apiKey: 'your-key',
|
|
275
|
+
callbacks,
|
|
276
|
+
userEmail: user.email,
|
|
277
|
+
userIdentifier: user.id,
|
|
278
|
+
}}
|
|
279
|
+
>
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Option 2: Via hook (imperative)**
|
|
283
|
+
|
|
284
|
+
Call `identify()` after login for full control:
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
const { identify } = useEcho();
|
|
288
|
+
|
|
289
|
+
const result = await identify({
|
|
290
|
+
email: 'user@example.com',
|
|
291
|
+
userIdentifier: 'user-123',
|
|
292
|
+
firstName: 'John',
|
|
293
|
+
lastName: 'Doe',
|
|
294
|
+
phone: '+905551234567',
|
|
295
|
+
traits: { plan: 'premium' },
|
|
296
|
+
});
|
|
297
|
+
// result: { success: true, userId: '...', userIdChanged: true }
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Logging Out
|
|
301
|
+
|
|
302
|
+
Call `logout()` to reset to anonymous state. This generates a new anonymous UUID and clears chat history:
|
|
303
|
+
|
|
304
|
+
```tsx
|
|
305
|
+
const { logout } = useEcho();
|
|
306
|
+
|
|
307
|
+
await logout();
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Login / Logout Flow Example
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
import { useEcho } from '@getecho-ai/react-native-sdk';
|
|
314
|
+
|
|
315
|
+
function ProfileScreen() {
|
|
316
|
+
const { identify, logout, userId } = useEcho();
|
|
317
|
+
|
|
318
|
+
const handleLogin = async (email: string) => {
|
|
319
|
+
// Your auth logic...
|
|
320
|
+
await identify({ email });
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const handleLogout = async () => {
|
|
324
|
+
// Your auth logic...
|
|
325
|
+
await logout();
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
return (
|
|
329
|
+
<View>
|
|
330
|
+
<Text>User: {userId}</Text>
|
|
331
|
+
<Button title="Login" onPress={() => handleLogin('user@example.com')} />
|
|
332
|
+
<Button title="Logout" onPress={handleLogout} />
|
|
333
|
+
</View>
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
255
338
|
## Troubleshooting
|
|
256
339
|
|
|
257
340
|
### "Network request failed" on Android emulator
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - Bridge initialization
|
|
8
8
|
*/
|
|
9
9
|
import type React from "react";
|
|
10
|
-
import type { AddToCartResult, EchoConfig, GetCartResult, Product } from "../types";
|
|
10
|
+
import type { AddToCartResult, EchoConfig, GetCartResult, IdentifyParams, IdentifyResult, Product } from "../types";
|
|
11
11
|
type EchoContextType = {
|
|
12
12
|
config: EchoConfig;
|
|
13
13
|
userId: string;
|
|
@@ -15,6 +15,8 @@ type EchoContextType = {
|
|
|
15
15
|
isReady: boolean;
|
|
16
16
|
updateUserId: (newUserId: string) => Promise<void>;
|
|
17
17
|
updateChatId: (newChatId: string) => Promise<void>;
|
|
18
|
+
identify: (params: IdentifyParams) => Promise<IdentifyResult>;
|
|
19
|
+
logout: () => Promise<void>;
|
|
18
20
|
handleAddToCart: (product: Product, quantity?: number) => Promise<AddToCartResult>;
|
|
19
21
|
handleGetCart: () => Promise<GetCartResult>;
|
|
20
22
|
handleNavigateToProduct: (productId: string) => void;
|
|
@@ -14,6 +14,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
14
14
|
exports.EchoProvider = exports.useEcho = void 0;
|
|
15
15
|
const async_storage_1 = __importDefault(require("@react-native-async-storage/async-storage"));
|
|
16
16
|
const react_1 = require("react");
|
|
17
|
+
const WebViewBridge_1 = __importDefault(require("../bridge/WebViewBridge"));
|
|
17
18
|
const resolveApiUrl_1 = require("../utils/resolveApiUrl");
|
|
18
19
|
/**
|
|
19
20
|
* Generate a UUID v4 string
|
|
@@ -45,6 +46,7 @@ const EchoProvider = ({ config, children, }) => {
|
|
|
45
46
|
const [userId, setUserId] = (0, react_1.useState)(config.userId || "");
|
|
46
47
|
const [chatId, setChatId] = (0, react_1.useState)("");
|
|
47
48
|
const [isReady, setIsReady] = (0, react_1.useState)(false);
|
|
49
|
+
const suppressAutoIdentifyRef = (0, react_1.useRef)(false);
|
|
48
50
|
// Initialize user ID from storage or generate new one
|
|
49
51
|
(0, react_1.useEffect)(() => {
|
|
50
52
|
const syncUserWithServer = async (id) => {
|
|
@@ -175,9 +177,89 @@ const EchoProvider = ({ config, children, }) => {
|
|
|
175
177
|
config.userIdentifier,
|
|
176
178
|
updateUserId,
|
|
177
179
|
]);
|
|
180
|
+
const identify = (0, react_1.useCallback)(async (params) => {
|
|
181
|
+
const baseUrl = (0, resolveApiUrl_1.resolveApiUrl)(config.apiUrl).replace(/\/+$/, "");
|
|
182
|
+
try {
|
|
183
|
+
const response = await fetch(`${baseUrl}/api/user/identify`, {
|
|
184
|
+
method: "POST",
|
|
185
|
+
headers: {
|
|
186
|
+
"Content-Type": "application/json",
|
|
187
|
+
"X-API-Key": config.apiKey,
|
|
188
|
+
},
|
|
189
|
+
body: JSON.stringify({
|
|
190
|
+
userId,
|
|
191
|
+
email: params.email,
|
|
192
|
+
userIdentifier: params.userIdentifier,
|
|
193
|
+
firstName: params.firstName,
|
|
194
|
+
lastName: params.lastName,
|
|
195
|
+
phone: params.phone,
|
|
196
|
+
traits: params.traits,
|
|
197
|
+
}),
|
|
198
|
+
});
|
|
199
|
+
const data = await response.json();
|
|
200
|
+
if (data.userIdChanged && data.userId) {
|
|
201
|
+
await updateUserId(data.userId);
|
|
202
|
+
}
|
|
203
|
+
suppressAutoIdentifyRef.current = false;
|
|
204
|
+
WebViewBridge_1.default.sendRawToWebView({
|
|
205
|
+
type: "echo:runtime:update_identity",
|
|
206
|
+
userId: data.userId || userId,
|
|
207
|
+
email: params.email,
|
|
208
|
+
userIdentifier: params.userIdentifier,
|
|
209
|
+
});
|
|
210
|
+
return {
|
|
211
|
+
success: true,
|
|
212
|
+
userId: data.userId || userId,
|
|
213
|
+
email: params.email,
|
|
214
|
+
userIdentifier: params.userIdentifier,
|
|
215
|
+
userIdChanged: !!data.userIdChanged,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
console.error("[EchoSDK] identify() failed:", error);
|
|
220
|
+
return {
|
|
221
|
+
success: false,
|
|
222
|
+
userId,
|
|
223
|
+
userIdChanged: false,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}, [userId, config.apiKey, config.apiUrl, updateUserId]);
|
|
227
|
+
const logout = (0, react_1.useCallback)(async () => {
|
|
228
|
+
const newAnonymousId = generateUUID();
|
|
229
|
+
setUserId(newAnonymousId);
|
|
230
|
+
setChatId("");
|
|
231
|
+
try {
|
|
232
|
+
await async_storage_1.default.removeItem("echo_chat_id");
|
|
233
|
+
await async_storage_1.default.setItem("echo_user_id", newAnonymousId);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
console.error("[EchoSDK] logout() storage cleanup failed:", error);
|
|
237
|
+
}
|
|
238
|
+
suppressAutoIdentifyRef.current = true;
|
|
239
|
+
// Fire-and-forget user sync
|
|
240
|
+
const baseUrl = (0, resolveApiUrl_1.resolveApiUrl)(config.apiUrl).replace(/\/+$/, "");
|
|
241
|
+
fetch(`${baseUrl}/api/user`, {
|
|
242
|
+
method: "POST",
|
|
243
|
+
headers: {
|
|
244
|
+
"X-API-Key": config.apiKey,
|
|
245
|
+
"Content-Type": "application/json",
|
|
246
|
+
},
|
|
247
|
+
body: JSON.stringify({ userId: newAnonymousId }),
|
|
248
|
+
}).catch((err) => {
|
|
249
|
+
console.warn("[EchoSDK] logout user sync failed:", err.message);
|
|
250
|
+
});
|
|
251
|
+
WebViewBridge_1.default.sendRawToWebView({
|
|
252
|
+
type: "echo:runtime:reset_user",
|
|
253
|
+
userId: newAnonymousId,
|
|
254
|
+
});
|
|
255
|
+
}, [config.apiKey, config.apiUrl]);
|
|
178
256
|
// Auto-identify user when ready and email/identifier is provided
|
|
179
257
|
(0, react_1.useEffect)(() => {
|
|
180
258
|
if (isReady && userId && (config.userEmail || config.userIdentifier)) {
|
|
259
|
+
if (suppressAutoIdentifyRef.current) {
|
|
260
|
+
suppressAutoIdentifyRef.current = false;
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
181
263
|
identifyUser();
|
|
182
264
|
}
|
|
183
265
|
}, [isReady, userId, config.userEmail, config.userIdentifier, identifyUser]);
|
|
@@ -228,6 +310,8 @@ const EchoProvider = ({ config, children, }) => {
|
|
|
228
310
|
isReady,
|
|
229
311
|
updateUserId,
|
|
230
312
|
updateChatId,
|
|
313
|
+
identify,
|
|
314
|
+
logout,
|
|
231
315
|
handleAddToCart,
|
|
232
316
|
handleGetCart,
|
|
233
317
|
handleNavigateToProduct,
|
package/dist/index.d.ts
CHANGED
|
@@ -55,5 +55,5 @@ export { EchoChatModal } from "./components/EchoChatModal";
|
|
|
55
55
|
export { EchoProvider, useEcho } from "./components/EchoProvider";
|
|
56
56
|
export type { UseSimpleCartOptions, UseSimpleCartReturn, } from "./hooks/useSimpleCart";
|
|
57
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";
|
|
58
|
+
export type { ActionType, AddToCartResult, BridgeMessage, BridgeMessageType, CallbackResult, Cart, CartItem, ChatMessage, EchoCallbacks, EchoConfig, EchoTheme, GetCartResult, IdentifyParams, IdentifyResult, MessageAction, Product, UISettings, } from "./types";
|
|
59
59
|
export { getLocalhostUrl, resolveApiUrl } from "./utils/resolveApiUrl";
|
package/dist/types/index.d.ts
CHANGED
|
@@ -154,3 +154,18 @@ export type CallbackResult = {
|
|
|
154
154
|
data?: any;
|
|
155
155
|
error?: string;
|
|
156
156
|
};
|
|
157
|
+
export type IdentifyParams = {
|
|
158
|
+
email?: string;
|
|
159
|
+
userIdentifier?: string;
|
|
160
|
+
firstName?: string;
|
|
161
|
+
lastName?: string;
|
|
162
|
+
phone?: string;
|
|
163
|
+
traits?: Record<string, unknown>;
|
|
164
|
+
};
|
|
165
|
+
export type IdentifyResult = {
|
|
166
|
+
success: boolean;
|
|
167
|
+
userId: string;
|
|
168
|
+
email?: string;
|
|
169
|
+
userIdentifier?: string;
|
|
170
|
+
userIdChanged: boolean;
|
|
171
|
+
};
|