@facepe/widget-mobile-app 1.5.0 → 1.5.2
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 +18 -3
- package/dist/FacePeMobileAvatar.js +33 -32
- package/dist/FacePeMobileProvider.js +40 -2
- package/dist/types.d.ts +6 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +61 -61
package/README.md
CHANGED
|
@@ -80,7 +80,14 @@ Exchange your `accessKey` and `secretKey` on your secure backend (Node.js / Expr
|
|
|
80
80
|
|
|
81
81
|
```javascript
|
|
82
82
|
// Express server route: GET /api/facepe-token
|
|
83
|
+
// A token lasts 2 hours (expiresIn, in seconds): keep one and fetch a new one a
|
|
84
|
+
// minute before it expires, or when the app asks with ?refresh=1 (after a 401).
|
|
85
|
+
let cached = null; // { token, until }
|
|
86
|
+
|
|
83
87
|
app.get('/api/facepe-token', async (req, res) => {
|
|
88
|
+
if (cached && cached.until > Date.now() && req.query.refresh !== '1') {
|
|
89
|
+
return res.send(cached.token);
|
|
90
|
+
}
|
|
84
91
|
const response = await fetch('https://api.facepe.ai/api/v1/sdk/token', {
|
|
85
92
|
method: 'POST',
|
|
86
93
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -91,10 +98,16 @@ app.get('/api/facepe-token', async (req, res) => {
|
|
|
91
98
|
}),
|
|
92
99
|
});
|
|
93
100
|
const data = await response.json();
|
|
94
|
-
res.
|
|
101
|
+
if (!response.ok) return res.status(response.status).json(data.error);
|
|
102
|
+
cached = { token: data.data.token, until: Date.now() + (data.data.expiresIn - 60) * 1000 };
|
|
103
|
+
res.send(cached.token);
|
|
95
104
|
});
|
|
96
105
|
```
|
|
97
106
|
|
|
107
|
+
**Tokens last 2 hours.** The SDK renews them by itself: it calls `getToken({ forceRefresh: true })`
|
|
108
|
+
about 5 minutes before the token expires, and again (then retries once) if FacePe answers
|
|
109
|
+
`401 SESSION_EXPIRED`. Renewal is silent: a conversation in progress is not interrupted.
|
|
110
|
+
|
|
98
111
|
---
|
|
99
112
|
|
|
100
113
|
### 2. React Native Component
|
|
@@ -118,8 +131,9 @@ export default function App() {
|
|
|
118
131
|
|
|
119
132
|
<FacePeMobileProvider
|
|
120
133
|
apiBaseUrl="https://api.facepe.ai/api/v1/sdk"
|
|
121
|
-
getToken={async () => {
|
|
122
|
-
|
|
134
|
+
getToken={async ({ forceRefresh }) => {
|
|
135
|
+
// Tokens last 2 hours: forceRefresh asks your server for a new one.
|
|
136
|
+
const res = await fetch('https://your-backend.com/api/facepe-token' + (forceRefresh ? '?refresh=1' : ''));
|
|
123
137
|
return await res.text();
|
|
124
138
|
}}
|
|
125
139
|
>
|
|
@@ -253,6 +267,7 @@ const styles = StyleSheet.create({
|
|
|
253
267
|
| `videoStyle` | `ViewStyle` | `undefined` | Style applied to native video view. |
|
|
254
268
|
| `objectFit` | `'cover' \| 'contain'` | `'cover'` | Video aspect scaling mode. |
|
|
255
269
|
| `mirror` | `boolean` | `false` | Mirror video orientation. |
|
|
270
|
+
| `showSpeakingIndicator` | `boolean` | `true` | The "Avatar speaking" / "Listening to you…" label on the video. Set `false` to show it yourself with `onSpeakStart`/`onSpeakEnd` and `onUserSpeakStart`/`onUserSpeakEnd`. |
|
|
256
271
|
| `autoStart` | `boolean` | `true` | Automatically connect and start stream on mount. |
|
|
257
272
|
| `posterUrl` | `string \| null` | avatar's portrait | Picture shown until the video starts. Defaults to the avatar's portrait from FacePe; pass `null` for none. |
|
|
258
273
|
| `enableMic` | `boolean` | `true` | Automatically enable microphone on connect. |
|
|
@@ -67,14 +67,26 @@ const joinNumberParts = (text) => text.replace(/\d+(?:[ \t]*[-\u2010\u2011\u2013
|
|
|
67
67
|
return digits.length >= 7 ? digits : m;
|
|
68
68
|
});
|
|
69
69
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
70
|
+
* A gateway call with the app's token. Answered 401 (the token expired or was
|
|
71
|
+
* revoked), it asks the app for a fresh token once and tries again. A server
|
|
72
|
+
* from before agents has only /avatars…, tried when /agents… is missing.
|
|
72
73
|
*/
|
|
73
|
-
async function gatewayFetch(base, agentPath, avatarPath, init) {
|
|
74
|
-
const
|
|
75
|
-
|
|
74
|
+
async function gatewayFetch(base, agentPath, avatarPath, init, tokenFor) {
|
|
75
|
+
const send = async (path, force) => {
|
|
76
|
+
const headers = { ...(init.headers ?? {}) };
|
|
77
|
+
const token = await tokenFor(force);
|
|
78
|
+
if (token)
|
|
79
|
+
headers.Authorization = `Bearer ${token}`;
|
|
80
|
+
return fetch(`${base}${path}`, { ...init, headers });
|
|
81
|
+
};
|
|
82
|
+
const attempt = async (force) => {
|
|
83
|
+
const res = await send(agentPath, force);
|
|
84
|
+
return res.status === 404 ? send(avatarPath, force) : res;
|
|
85
|
+
};
|
|
86
|
+
const res = await attempt(false);
|
|
87
|
+
return res.status === 401 ? attempt(true) : res;
|
|
76
88
|
}
|
|
77
|
-
export const FacePeMobileAvatar = forwardRef(({ agentId, avatarKey, language = 'en', style, videoStyle, objectFit = 'cover', mirror = false, autoStart = true, enableMic = true, onTranscript, onSpeakStart, onSpeakEnd, onUserSpeakStart, onUserSpeakEnd, onOrderUpdate, onCheckout, onStatusChange, onError, onConversationEnd, posterUrl, }, ref) => {
|
|
89
|
+
export const FacePeMobileAvatar = forwardRef(({ agentId, avatarKey, language = 'en', style, videoStyle, objectFit = 'cover', mirror = false, showSpeakingIndicator = true, autoStart = true, enableMic = true, onTranscript, onSpeakStart, onSpeakEnd, onUserSpeakStart, onUserSpeakEnd, onOrderUpdate, onCheckout, onStatusChange, onError, onConversationEnd, posterUrl, }, ref) => {
|
|
78
90
|
const { token, loading: tokenLoading, error: tokenError, apiBaseUrl, refreshToken } = useFacePeMobileContext();
|
|
79
91
|
// The agent to talk to (`avatarKey` is its name from before agents).
|
|
80
92
|
const chosenAgent = agentId ?? avatarKey;
|
|
@@ -87,9 +99,7 @@ export const FacePeMobileAvatar = forwardRef(({ agentId, avatarKey, language = '
|
|
|
87
99
|
let cancelled = false;
|
|
88
100
|
void (async () => {
|
|
89
101
|
try {
|
|
90
|
-
const res = await gatewayFetch(apiBaseUrl.replace(/\/+$/, ''), '/agents', '/avatars', {
|
|
91
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
92
|
-
});
|
|
102
|
+
const res = await gatewayFetch(apiBaseUrl.replace(/\/+$/, ''), '/agents', '/avatars', {}, tokenFor);
|
|
93
103
|
if (!res.ok)
|
|
94
104
|
return;
|
|
95
105
|
const data = (await res.json())?.data;
|
|
@@ -151,15 +161,14 @@ export const FacePeMobileAvatar = forwardRef(({ agentId, avatarKey, language = '
|
|
|
151
161
|
const transcript = conv.lines.map(({ role, text, at }) => ({ role, text, at }));
|
|
152
162
|
void (async () => {
|
|
153
163
|
let form = null;
|
|
154
|
-
|
|
155
|
-
if (conv.conversationId && transcript.length > 0 && token) {
|
|
164
|
+
if (conv.conversationId && transcript.length > 0) {
|
|
156
165
|
try {
|
|
157
166
|
const id = encodeURIComponent(conv.conversationId);
|
|
158
167
|
const res = await gatewayFetch(apiBaseUrl.replace(/\/+$/, ''), `/agent-sessions/${id}/form`, `/avatar-sessions/${id}/form`, {
|
|
159
168
|
method: 'POST',
|
|
160
|
-
headers: {
|
|
169
|
+
headers: { 'Content-Type': 'application/json' },
|
|
161
170
|
body: JSON.stringify({ transcript: transcript.map(({ role, text }) => ({ role, text })) }),
|
|
162
|
-
});
|
|
171
|
+
}, tokenFor);
|
|
163
172
|
if (res.ok)
|
|
164
173
|
form = (await res.json())?.data?.form ?? null;
|
|
165
174
|
}
|
|
@@ -192,6 +201,10 @@ export const FacePeMobileAvatar = forwardRef(({ agentId, avatarKey, language = '
|
|
|
192
201
|
const isConnectingRef = useRef(false);
|
|
193
202
|
const tokenRef = useRef(token);
|
|
194
203
|
tokenRef.current = token;
|
|
204
|
+
// The app's token for gateway calls, or (force) a fresh one after a 401.
|
|
205
|
+
const refreshRef = useRef(refreshToken);
|
|
206
|
+
refreshRef.current = refreshToken;
|
|
207
|
+
const tokenFor = useCallback(async (force) => (force ? refreshRef.current(true) : tokenRef.current ?? refreshRef.current()), []);
|
|
195
208
|
// Helper: Safely emit status changes
|
|
196
209
|
const updateStatus = useCallback((newStatus) => {
|
|
197
210
|
if (!mountedRef.current)
|
|
@@ -227,18 +240,14 @@ export const FacePeMobileAvatar = forwardRef(({ agentId, avatarKey, language = '
|
|
|
227
240
|
const endUsage = async () => {
|
|
228
241
|
const uId = usageIdRef.current;
|
|
229
242
|
usageIdRef.current = null;
|
|
230
|
-
|
|
231
|
-
if (!uId || !currentToken)
|
|
243
|
+
if (!uId)
|
|
232
244
|
return;
|
|
233
245
|
try {
|
|
234
246
|
const cleanBase = apiBaseUrl.replace(/\/+$/, '');
|
|
235
247
|
await gatewayFetch(cleanBase, `/agent-sessions/${uId}/end`, `/avatar-sessions/${uId}/end`, {
|
|
236
248
|
method: 'POST',
|
|
237
|
-
headers: {
|
|
238
|
-
|
|
239
|
-
Authorization: `Bearer ${currentToken}`,
|
|
240
|
-
},
|
|
241
|
-
});
|
|
249
|
+
headers: { 'Content-Type': 'application/json' },
|
|
250
|
+
}, tokenFor);
|
|
242
251
|
}
|
|
243
252
|
catch {
|
|
244
253
|
// ignore background teardown error
|
|
@@ -273,12 +282,7 @@ export const FacePeMobileAvatar = forwardRef(({ agentId, avatarKey, language = '
|
|
|
273
282
|
let targetAgentId = chosenAgent;
|
|
274
283
|
if (!targetAgentId) {
|
|
275
284
|
// No agent given: use the first one assigned to this token's user.
|
|
276
|
-
const listRes = await gatewayFetch(cleanBase, '/agents', '/avatars', {
|
|
277
|
-
method: 'GET',
|
|
278
|
-
headers: {
|
|
279
|
-
Authorization: `Bearer ${currentToken}`,
|
|
280
|
-
},
|
|
281
|
-
});
|
|
285
|
+
const listRes = await gatewayFetch(cleanBase, '/agents', '/avatars', { method: 'GET' }, tokenFor);
|
|
282
286
|
if (listRes.ok) {
|
|
283
287
|
const listData = (await listRes.json())?.data;
|
|
284
288
|
const agents = listData?.agents || listData?.avatars || [];
|
|
@@ -294,12 +298,9 @@ export const FacePeMobileAvatar = forwardRef(({ agentId, avatarKey, language = '
|
|
|
294
298
|
const agentPath = encodeURIComponent(targetAgentId);
|
|
295
299
|
const sessionResp = await gatewayFetch(cleanBase, `/agents/${agentPath}/sessions`, `/avatars/${agentPath}/sessions`, {
|
|
296
300
|
method: 'POST',
|
|
297
|
-
headers: {
|
|
298
|
-
'Content-Type': 'application/json',
|
|
299
|
-
Authorization: `Bearer ${currentToken}`,
|
|
300
|
-
},
|
|
301
|
+
headers: { 'Content-Type': 'application/json' },
|
|
301
302
|
body: JSON.stringify({ language }),
|
|
302
|
-
});
|
|
303
|
+
}, tokenFor);
|
|
303
304
|
if (!sessionResp.ok) {
|
|
304
305
|
const errBody = await sessionResp.json().catch(() => ({}));
|
|
305
306
|
const errMsg = errBody?.error?.message || errBody?.message || 'Failed to start the conversation';
|
|
@@ -722,7 +723,7 @@ export const FacePeMobileAvatar = forwardRef(({ agentId, avatarKey, language = '
|
|
|
722
723
|
// size — the video got no height and stayed invisible.
|
|
723
724
|
style: StyleSheet.flatten([styles.video, videoStyle]), objectFit: objectFit, mirror: mirror })) : (_jsxs(View, { style: [styles.container, styles.center, !!poster && (status === 'idle' || status === 'disconnected') && styles.overPoster], children: [poster && _jsx(Image, { source: { uri: poster }, style: StyleSheet.absoluteFill, resizeMode: "cover" }), poster && status !== 'idle' && status !== 'disconnected' && _jsx(View, { style: [StyleSheet.absoluteFill, styles.dim] }), status === 'connecting' || status === 'reconnecting' || status === 'connected' ? (
|
|
724
725
|
// connected: the stream is up and the first video frame is on its way.
|
|
725
|
-
_jsxs(_Fragment, { children: [_jsx(ActivityIndicator, { size: "large", color: "#8B5CF6" }), _jsx(Text, { style: styles.loadingText, children: status === 'reconnecting' ? 'Reconnecting stream…' : 'Connecting to FacePe AI Avatar…' })] })) : status === 'error' ? (_jsxs(_Fragment, { children: [_jsx(Text, { style: styles.errorTitle, children: "Connection Failed" }), _jsx(Text, { style: styles.errorText, children: errorMessage || 'Unable to connect to stream' }), _jsx(TouchableOpacity, { style: styles.retryButton, onPress: () => void startStream(), children: _jsx(Text, { style: styles.retryText, children: "Reconnect" }) })] })) : (_jsx(TouchableOpacity, { style: styles.startButton, onPress: () => void startStream(), children: _jsx(Text, { style: styles.startText, children: "Start Avatar Session" }) }))] })), status === 'connected' && (isAvatarSpeaking || isUserSpeaking) && (_jsxs(View, { style: styles.speakerPill, children: [_jsx(View, { style: [
|
|
726
|
+
_jsxs(_Fragment, { children: [_jsx(ActivityIndicator, { size: "large", color: "#8B5CF6" }), _jsx(Text, { style: styles.loadingText, children: status === 'reconnecting' ? 'Reconnecting stream…' : 'Connecting to FacePe AI Avatar…' })] })) : status === 'error' ? (_jsxs(_Fragment, { children: [_jsx(Text, { style: styles.errorTitle, children: "Connection Failed" }), _jsx(Text, { style: styles.errorText, children: errorMessage || 'Unable to connect to stream' }), _jsx(TouchableOpacity, { style: styles.retryButton, onPress: () => void startStream(), children: _jsx(Text, { style: styles.retryText, children: "Reconnect" }) })] })) : (_jsx(TouchableOpacity, { style: styles.startButton, onPress: () => void startStream(), children: _jsx(Text, { style: styles.startText, children: "Start Avatar Session" }) }))] })), showSpeakingIndicator && status === 'connected' && (isAvatarSpeaking || isUserSpeaking) && (_jsxs(View, { style: styles.speakerPill, children: [_jsx(View, { style: [
|
|
726
727
|
styles.speakerDot,
|
|
727
728
|
isAvatarSpeaking ? styles.dotAvatar : styles.dotUser,
|
|
728
729
|
] }), _jsx(Text, { style: styles.speakerText, children: isAvatarSpeaking ? 'Avatar speaking' : 'Listening to you…' })] }))] }));
|
|
@@ -1,25 +1,50 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createContext, useContext, useState, useEffect, useRef, useMemo } from 'react';
|
|
3
3
|
const FacePeContext = createContext(null);
|
|
4
|
+
/** When a token runs out (its JWT `exp`, in seconds), or null if it doesn't say. */
|
|
5
|
+
function expiryOf(token) {
|
|
6
|
+
try {
|
|
7
|
+
const part = token.split('.')[1] ?? '';
|
|
8
|
+
const b64 = part.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(part.length / 4) * 4, '=');
|
|
9
|
+
const exp = JSON.parse(globalThis.atob(b64))?.exp;
|
|
10
|
+
return typeof exp === 'number' ? exp : null;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** Fetch a new token this long before the current one runs out. */
|
|
17
|
+
const REFRESH_EARLY_MS = 5 * 60 * 1000;
|
|
4
18
|
export function FacePeMobileProvider({ children, getToken, apiBaseUrl = 'https://api.facepe.ai/api/v1/sdk', config = {}, }) {
|
|
5
19
|
const [token, setToken] = useState(null);
|
|
6
20
|
const [loading, setLoading] = useState(true);
|
|
7
21
|
const [error, setError] = useState(null);
|
|
8
22
|
const tokenPromiseRef = useRef(null);
|
|
23
|
+
const tokenRef = useRef(null);
|
|
24
|
+
// A refresh with a token already in hand is silent: no "loading" (which would
|
|
25
|
+
// replace a live call with the sign-in screen), and on failure the current
|
|
26
|
+
// token stays — the next call's 401 asks again.
|
|
9
27
|
const refreshToken = async (force = false) => {
|
|
10
28
|
if (tokenPromiseRef.current && !force) {
|
|
11
29
|
return tokenPromiseRef.current;
|
|
12
30
|
}
|
|
13
|
-
|
|
14
|
-
|
|
31
|
+
const silent = tokenRef.current !== null;
|
|
32
|
+
if (!silent) {
|
|
33
|
+
setLoading(true);
|
|
34
|
+
setError(null);
|
|
35
|
+
}
|
|
15
36
|
const promise = (async () => {
|
|
16
37
|
try {
|
|
17
38
|
const fetched = await getToken({ forceRefresh: force });
|
|
39
|
+
tokenRef.current = fetched;
|
|
18
40
|
setToken(fetched);
|
|
41
|
+
setError(null);
|
|
19
42
|
setLoading(false);
|
|
20
43
|
return fetched;
|
|
21
44
|
}
|
|
22
45
|
catch (err) {
|
|
46
|
+
if (silent)
|
|
47
|
+
return null;
|
|
23
48
|
const normalized = err instanceof Error ? err : new Error(String(err));
|
|
24
49
|
setError(normalized);
|
|
25
50
|
setLoading(false);
|
|
@@ -35,6 +60,19 @@ export function FacePeMobileProvider({ children, getToken, apiBaseUrl = 'https:/
|
|
|
35
60
|
useEffect(() => {
|
|
36
61
|
void refreshToken();
|
|
37
62
|
}, []);
|
|
63
|
+
// Tokens expire (2 hours by default): fetch a new one a little before this one
|
|
64
|
+
// runs out, so a conversation never starts on a token about to expire. A 401
|
|
65
|
+
// on any call also fetches a new one.
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (!token)
|
|
68
|
+
return;
|
|
69
|
+
const exp = expiryOf(token);
|
|
70
|
+
if (!exp)
|
|
71
|
+
return;
|
|
72
|
+
const wait = Math.min(Math.max(10000, exp * 1000 - Date.now() - REFRESH_EARLY_MS), 2 ** 31 - 1);
|
|
73
|
+
const timer = setTimeout(() => void refreshToken(true), wait);
|
|
74
|
+
return () => clearTimeout(timer);
|
|
75
|
+
}, [token]);
|
|
38
76
|
const value = useMemo(() => ({
|
|
39
77
|
config,
|
|
40
78
|
token,
|
package/dist/types.d.ts
CHANGED
|
@@ -111,6 +111,12 @@ export interface FacePeMobileAvatarProps {
|
|
|
111
111
|
objectFit?: 'cover' | 'contain';
|
|
112
112
|
/** Whether to mirror the video stream (default: false) */
|
|
113
113
|
mirror?: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Show the "Avatar speaking" / "Listening to you…" label on the video
|
|
116
|
+
* (default: true). Turn it off to show who is talking yourself, with
|
|
117
|
+
* onSpeakStart/onSpeakEnd and onUserSpeakStart/onUserSpeakEnd.
|
|
118
|
+
*/
|
|
119
|
+
showSpeakingIndicator?: boolean;
|
|
114
120
|
/** Auto start streaming when component mounts (default: true) */
|
|
115
121
|
autoStart?: boolean;
|
|
116
122
|
/** Whether microphone should be automatically enabled on stream start (default: true) */
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** This package's version, reported in conversation records. Keep in step with package.json. */
|
|
2
|
-
export declare const SDK_VERSION = "1.5.
|
|
2
|
+
export declare const SDK_VERSION = "1.5.2";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** This package's version, reported in conversation records. Keep in step with package.json. */
|
|
2
|
-
export const SDK_VERSION = '1.5.
|
|
2
|
+
export const SDK_VERSION = '1.5.2';
|
package/package.json
CHANGED
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@facepe/widget-mobile-app",
|
|
3
|
-
"version": "1.5.
|
|
4
|
-
"description": "FacePe AI 3D Video Avatar Native Live Streaming SDK for React Native (iOS & Android)",
|
|
5
|
-
"main": "./dist/index.js",
|
|
6
|
-
"module": "./dist/index.js",
|
|
7
|
-
"types": "./dist/index.d.ts",
|
|
8
|
-
"files": [
|
|
9
|
-
"dist",
|
|
10
|
-
"README.md"
|
|
11
|
-
],
|
|
12
|
-
"publishConfig": {
|
|
13
|
-
"access": "public"
|
|
14
|
-
},
|
|
15
|
-
"keywords": [
|
|
16
|
-
"facepe",
|
|
17
|
-
"avatar",
|
|
18
|
-
"ai-avatar",
|
|
19
|
-
"livekit",
|
|
20
|
-
"react-native",
|
|
21
|
-
"ios",
|
|
22
|
-
"android",
|
|
23
|
-
"webrtc",
|
|
24
|
-
"streaming",
|
|
25
|
-
"digital-human"
|
|
26
|
-
],
|
|
27
|
-
"author": "FacePe AI",
|
|
28
|
-
"license": "MIT",
|
|
29
|
-
"scripts": {
|
|
30
|
-
"lint": "tsc --noEmit",
|
|
31
|
-
"build": "tsc"
|
|
32
|
-
},
|
|
33
|
-
"peerDependencies": {
|
|
34
|
-
"@livekit/react-native": ">=2.0.0",
|
|
35
|
-
"@livekit/react-native-webrtc": ">=124.0.0",
|
|
36
|
-
"livekit-client": ">=2.0.0",
|
|
37
|
-
"react": ">=18.0.0",
|
|
38
|
-
"react-native": ">=0.70.0",
|
|
39
|
-
"@anam-ai/js-sdk": ">=4.27.0"
|
|
40
|
-
},
|
|
41
|
-
"peerDependenciesMeta": {
|
|
42
|
-
"@livekit/react-native": {
|
|
43
|
-
"optional": true
|
|
44
|
-
},
|
|
45
|
-
"@livekit/react-native-webrtc": {
|
|
46
|
-
"optional": true
|
|
47
|
-
},
|
|
48
|
-
"livekit-client": {
|
|
49
|
-
"optional": true
|
|
50
|
-
},
|
|
51
|
-
"@anam-ai/js-sdk": {
|
|
52
|
-
"optional": true
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
"devDependencies": {
|
|
56
|
-
"@types/react": "^18.3.3",
|
|
57
|
-
"react": "18.3.1",
|
|
58
|
-
"react-native": "0.74.0",
|
|
59
|
-
"typescript": "^5.4.5"
|
|
60
|
-
}
|
|
61
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@facepe/widget-mobile-app",
|
|
3
|
+
"version": "1.5.2",
|
|
4
|
+
"description": "FacePe AI 3D Video Avatar Native Live Streaming SDK for React Native (iOS & Android)",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"facepe",
|
|
17
|
+
"avatar",
|
|
18
|
+
"ai-avatar",
|
|
19
|
+
"livekit",
|
|
20
|
+
"react-native",
|
|
21
|
+
"ios",
|
|
22
|
+
"android",
|
|
23
|
+
"webrtc",
|
|
24
|
+
"streaming",
|
|
25
|
+
"digital-human"
|
|
26
|
+
],
|
|
27
|
+
"author": "FacePe AI",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"scripts": {
|
|
30
|
+
"lint": "tsc --noEmit",
|
|
31
|
+
"build": "tsc"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@livekit/react-native": ">=2.0.0",
|
|
35
|
+
"@livekit/react-native-webrtc": ">=124.0.0",
|
|
36
|
+
"livekit-client": ">=2.0.0",
|
|
37
|
+
"react": ">=18.0.0",
|
|
38
|
+
"react-native": ">=0.70.0",
|
|
39
|
+
"@anam-ai/js-sdk": ">=4.27.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependenciesMeta": {
|
|
42
|
+
"@livekit/react-native": {
|
|
43
|
+
"optional": true
|
|
44
|
+
},
|
|
45
|
+
"@livekit/react-native-webrtc": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"livekit-client": {
|
|
49
|
+
"optional": true
|
|
50
|
+
},
|
|
51
|
+
"@anam-ai/js-sdk": {
|
|
52
|
+
"optional": true
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/react": "^18.3.3",
|
|
57
|
+
"react": "18.3.1",
|
|
58
|
+
"react-native": "0.74.0",
|
|
59
|
+
"typescript": "^5.4.5"
|
|
60
|
+
}
|
|
61
|
+
}
|