@runwayml/avatars-react 0.2.1 → 0.4.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 CHANGED
@@ -56,9 +56,18 @@ The styles use CSS custom properties for easy customization:
56
56
 
57
57
  See [`examples/`](./examples) for complete working examples:
58
58
  - [`nextjs`](./examples/nextjs) - Next.js App Router
59
+ - [`nextjs-server-actions`](./examples/nextjs-server-actions) - Next.js with Server Actions
59
60
  - [`react-router`](./examples/react-router) - React Router v7 framework mode
60
61
  - [`express`](./examples/express) - Express + Vite
61
62
 
63
+ Scaffold an example with one command:
64
+
65
+ ```bash
66
+ npx degit runwayml/avatars-sdk-react/examples/nextjs my-avatar-app
67
+ cd my-avatar-app
68
+ npm install
69
+ ```
70
+
62
71
  ## How It Works
63
72
 
64
73
  1. **Client** calls your server endpoint with the `avatarId`
package/dist/api.cjs CHANGED
@@ -2,31 +2,10 @@
2
2
 
3
3
  // src/api/config.ts
4
4
  var DEFAULT_BASE_URL = "https://api.dev.runwayml.com";
5
- function getBaseUrl() {
6
- try {
7
- const envUrl = process.env.RUNWAYML_BASE_URL;
8
- if (envUrl) return envUrl;
9
- } catch {
10
- }
11
- return DEFAULT_BASE_URL;
12
- }
13
- var config = null;
14
- function configure(options) {
15
- config = { ...getConfig(), ...options };
16
- }
17
- function getConfig() {
18
- if (!config) {
19
- config = { baseUrl: getBaseUrl() };
20
- }
21
- return config;
22
- }
23
- function resetConfig() {
24
- config = null;
25
- }
26
5
 
27
6
  // src/api/consume.ts
28
7
  async function consumeSession(options) {
29
- const { sessionId, sessionKey, baseUrl = getConfig().baseUrl } = options;
8
+ const { sessionId, sessionKey, baseUrl = DEFAULT_BASE_URL } = options;
30
9
  const url = `${baseUrl}/v1/realtime_sessions/${sessionId}/consume`;
31
10
  const response = await fetch(url, {
32
11
  method: "POST",
@@ -44,9 +23,6 @@ async function consumeSession(options) {
44
23
  return response.json();
45
24
  }
46
25
 
47
- exports.configure = configure;
48
26
  exports.consumeSession = consumeSession;
49
- exports.getConfig = getConfig;
50
- exports.resetConfig = resetConfig;
51
27
  //# sourceMappingURL=api.cjs.map
52
28
  //# sourceMappingURL=api.cjs.map
package/dist/api.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/api/config.ts","../src/api/consume.ts"],"names":[],"mappings":";;;AAIA,IAAM,gBAAA,GAAmB,8BAAA;AAEzB,SAAS,UAAA,GAAqB;AAC5B,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,GAAS,QAAQ,GAAA,CAAI,iBAAA;AAC3B,IAAA,IAAI,QAAQ,OAAO,MAAA;AAAA,EACrB,CAAA,CAAA,MAAQ;AAAA,EAER;AACA,EAAA,OAAO,gBAAA;AACT;AAEA,IAAI,MAAA,GAA2B,IAAA;AAExB,SAAS,UAAU,OAAA,EAAmC;AAC3D,EAAA,MAAA,GAAS,EAAE,GAAG,SAAA,EAAU,EAAG,GAAG,OAAA,EAAQ;AACxC;AAEO,SAAS,SAAA,GAAuB;AACrC,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,MAAA,GAAS,EAAE,OAAA,EAAS,UAAA,EAAW,EAAE;AAAA,EACnC;AACA,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,WAAA,GAAoB;AAClC,EAAA,MAAA,GAAS,IAAA;AACX;;;AC5BA,eAAsB,eACpB,OAAA,EACiC;AACjC,EAAA,MAAM,EAAE,SAAA,EAAW,UAAA,EAAY,UAAU,SAAA,EAAU,CAAE,SAAQ,GAAI,OAAA;AAEjE,EAAA,MAAM,GAAA,GAAM,CAAA,EAAG,OAAO,CAAA,sBAAA,EAAyB,SAAS,CAAA,QAAA,CAAA;AACxD,EAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,IAChC,MAAA,EAAQ,MAAA;AAAA,IACR,OAAA,EAAS;AAAA,MACP,cAAA,EAAgB,kBAAA;AAAA,MAChB,aAAA,EAAe,UAAU,UAAU,CAAA;AAAA;AACrC,GACD,CAAA;AAED,EAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,IAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK;AACtC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,2BAAA,EAA8B,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,SAAS,CAAA;AAAA,KAC5D;AAAA,EACF;AAEA,EAAA,OAAO,SAAS,IAAA,EAAK;AACvB","file":"api.cjs","sourcesContent":["export interface ApiConfig {\n baseUrl: string;\n}\n\nconst DEFAULT_BASE_URL = 'https://api.dev.runwayml.com';\n\nfunction getBaseUrl(): string {\n try {\n const envUrl = process.env.RUNWAYML_BASE_URL;\n if (envUrl) return envUrl;\n } catch {\n // process not available in browser\n }\n return DEFAULT_BASE_URL;\n}\n\nlet config: ApiConfig | null = null;\n\nexport function configure(options: Partial<ApiConfig>): void {\n config = { ...getConfig(), ...options };\n}\n\nexport function getConfig(): ApiConfig {\n if (!config) {\n config = { baseUrl: getBaseUrl() };\n }\n return config;\n}\n\nexport function resetConfig(): void {\n config = null;\n}\n","import type { ConsumeSessionOptions, ConsumeSessionResponse } from '../types';\nimport { getConfig } from './config';\n\nexport async function consumeSession(\n options: ConsumeSessionOptions,\n): Promise<ConsumeSessionResponse> {\n const { sessionId, sessionKey, baseUrl = getConfig().baseUrl } = options;\n\n const url = `${baseUrl}/v1/realtime_sessions/${sessionId}/consume`;\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${sessionKey}`,\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Failed to consume session: ${response.status} ${errorText}`,\n );\n }\n\n return response.json();\n}\n"]}
1
+ {"version":3,"sources":["../src/api/config.ts","../src/api/consume.ts"],"names":[],"mappings":";;;AAAO,IAAM,gBAAA,GAAmB,8BAAA;;;ACGhC,eAAsB,eACpB,OAAA,EACiC;AACjC,EAAA,MAAM,EAAE,SAAA,EAAW,UAAA,EAAY,OAAA,GAAU,kBAAiB,GAAI,OAAA;AAE9D,EAAA,MAAM,GAAA,GAAM,CAAA,EAAG,OAAO,CAAA,sBAAA,EAAyB,SAAS,CAAA,QAAA,CAAA;AACxD,EAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,IAChC,MAAA,EAAQ,MAAA;AAAA,IACR,OAAA,EAAS;AAAA,MACP,cAAA,EAAgB,kBAAA;AAAA,MAChB,aAAA,EAAe,UAAU,UAAU,CAAA;AAAA;AACrC,GACD,CAAA;AAED,EAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,IAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK;AACtC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,2BAAA,EAA8B,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,SAAS,CAAA;AAAA,KAC5D;AAAA,EACF;AAEA,EAAA,OAAO,SAAS,IAAA,EAAK;AACvB","file":"api.cjs","sourcesContent":["export const DEFAULT_BASE_URL = 'https://api.dev.runwayml.com';\n","import type { ConsumeSessionOptions, ConsumeSessionResponse } from '../types';\nimport { DEFAULT_BASE_URL } from './config';\n\nexport async function consumeSession(\n options: ConsumeSessionOptions,\n): Promise<ConsumeSessionResponse> {\n const { sessionId, sessionKey, baseUrl = DEFAULT_BASE_URL } = options;\n\n const url = `${baseUrl}/v1/realtime_sessions/${sessionId}/consume`;\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${sessionKey}`,\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Failed to consume session: ${response.status} ${errorText}`,\n );\n }\n\n return response.json();\n}\n"]}
package/dist/api.d.cts CHANGED
@@ -21,13 +21,6 @@ interface ConsumeSessionOptions {
21
21
  baseUrl?: string;
22
22
  }
23
23
 
24
- interface ApiConfig {
25
- baseUrl: string;
26
- }
27
- declare function configure(options: Partial<ApiConfig>): void;
28
- declare function getConfig(): ApiConfig;
29
- declare function resetConfig(): void;
30
-
31
24
  declare function consumeSession(options: ConsumeSessionOptions): Promise<ConsumeSessionResponse>;
32
25
 
33
- export { type ApiConfig, type ConsumeSessionOptions, type ConsumeSessionResponse, configure, consumeSession, getConfig, resetConfig };
26
+ export { type ConsumeSessionOptions, type ConsumeSessionResponse, consumeSession };
package/dist/api.d.ts CHANGED
@@ -21,13 +21,6 @@ interface ConsumeSessionOptions {
21
21
  baseUrl?: string;
22
22
  }
23
23
 
24
- interface ApiConfig {
25
- baseUrl: string;
26
- }
27
- declare function configure(options: Partial<ApiConfig>): void;
28
- declare function getConfig(): ApiConfig;
29
- declare function resetConfig(): void;
30
-
31
24
  declare function consumeSession(options: ConsumeSessionOptions): Promise<ConsumeSessionResponse>;
32
25
 
33
- export { type ApiConfig, type ConsumeSessionOptions, type ConsumeSessionResponse, configure, consumeSession, getConfig, resetConfig };
26
+ export { type ConsumeSessionOptions, type ConsumeSessionResponse, consumeSession };
package/dist/api.js CHANGED
@@ -1,30 +1,9 @@
1
1
  // src/api/config.ts
2
2
  var DEFAULT_BASE_URL = "https://api.dev.runwayml.com";
3
- function getBaseUrl() {
4
- try {
5
- const envUrl = process.env.RUNWAYML_BASE_URL;
6
- if (envUrl) return envUrl;
7
- } catch {
8
- }
9
- return DEFAULT_BASE_URL;
10
- }
11
- var config = null;
12
- function configure(options) {
13
- config = { ...getConfig(), ...options };
14
- }
15
- function getConfig() {
16
- if (!config) {
17
- config = { baseUrl: getBaseUrl() };
18
- }
19
- return config;
20
- }
21
- function resetConfig() {
22
- config = null;
23
- }
24
3
 
25
4
  // src/api/consume.ts
26
5
  async function consumeSession(options) {
27
- const { sessionId, sessionKey, baseUrl = getConfig().baseUrl } = options;
6
+ const { sessionId, sessionKey, baseUrl = DEFAULT_BASE_URL } = options;
28
7
  const url = `${baseUrl}/v1/realtime_sessions/${sessionId}/consume`;
29
8
  const response = await fetch(url, {
30
9
  method: "POST",
@@ -42,6 +21,6 @@ async function consumeSession(options) {
42
21
  return response.json();
43
22
  }
44
23
 
45
- export { configure, consumeSession, getConfig, resetConfig };
24
+ export { consumeSession };
46
25
  //# sourceMappingURL=api.js.map
47
26
  //# sourceMappingURL=api.js.map
package/dist/api.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/api/config.ts","../src/api/consume.ts"],"names":[],"mappings":";AAIA,IAAM,gBAAA,GAAmB,8BAAA;AAEzB,SAAS,UAAA,GAAqB;AAC5B,EAAA,IAAI;AACF,IAAA,MAAM,MAAA,GAAS,QAAQ,GAAA,CAAI,iBAAA;AAC3B,IAAA,IAAI,QAAQ,OAAO,MAAA;AAAA,EACrB,CAAA,CAAA,MAAQ;AAAA,EAER;AACA,EAAA,OAAO,gBAAA;AACT;AAEA,IAAI,MAAA,GAA2B,IAAA;AAExB,SAAS,UAAU,OAAA,EAAmC;AAC3D,EAAA,MAAA,GAAS,EAAE,GAAG,SAAA,EAAU,EAAG,GAAG,OAAA,EAAQ;AACxC;AAEO,SAAS,SAAA,GAAuB;AACrC,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,MAAA,GAAS,EAAE,OAAA,EAAS,UAAA,EAAW,EAAE;AAAA,EACnC;AACA,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,WAAA,GAAoB;AAClC,EAAA,MAAA,GAAS,IAAA;AACX;;;AC5BA,eAAsB,eACpB,OAAA,EACiC;AACjC,EAAA,MAAM,EAAE,SAAA,EAAW,UAAA,EAAY,UAAU,SAAA,EAAU,CAAE,SAAQ,GAAI,OAAA;AAEjE,EAAA,MAAM,GAAA,GAAM,CAAA,EAAG,OAAO,CAAA,sBAAA,EAAyB,SAAS,CAAA,QAAA,CAAA;AACxD,EAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,IAChC,MAAA,EAAQ,MAAA;AAAA,IACR,OAAA,EAAS;AAAA,MACP,cAAA,EAAgB,kBAAA;AAAA,MAChB,aAAA,EAAe,UAAU,UAAU,CAAA;AAAA;AACrC,GACD,CAAA;AAED,EAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,IAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK;AACtC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,2BAAA,EAA8B,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,SAAS,CAAA;AAAA,KAC5D;AAAA,EACF;AAEA,EAAA,OAAO,SAAS,IAAA,EAAK;AACvB","file":"api.js","sourcesContent":["export interface ApiConfig {\n baseUrl: string;\n}\n\nconst DEFAULT_BASE_URL = 'https://api.dev.runwayml.com';\n\nfunction getBaseUrl(): string {\n try {\n const envUrl = process.env.RUNWAYML_BASE_URL;\n if (envUrl) return envUrl;\n } catch {\n // process not available in browser\n }\n return DEFAULT_BASE_URL;\n}\n\nlet config: ApiConfig | null = null;\n\nexport function configure(options: Partial<ApiConfig>): void {\n config = { ...getConfig(), ...options };\n}\n\nexport function getConfig(): ApiConfig {\n if (!config) {\n config = { baseUrl: getBaseUrl() };\n }\n return config;\n}\n\nexport function resetConfig(): void {\n config = null;\n}\n","import type { ConsumeSessionOptions, ConsumeSessionResponse } from '../types';\nimport { getConfig } from './config';\n\nexport async function consumeSession(\n options: ConsumeSessionOptions,\n): Promise<ConsumeSessionResponse> {\n const { sessionId, sessionKey, baseUrl = getConfig().baseUrl } = options;\n\n const url = `${baseUrl}/v1/realtime_sessions/${sessionId}/consume`;\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${sessionKey}`,\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Failed to consume session: ${response.status} ${errorText}`,\n );\n }\n\n return response.json();\n}\n"]}
1
+ {"version":3,"sources":["../src/api/config.ts","../src/api/consume.ts"],"names":[],"mappings":";AAAO,IAAM,gBAAA,GAAmB,8BAAA;;;ACGhC,eAAsB,eACpB,OAAA,EACiC;AACjC,EAAA,MAAM,EAAE,SAAA,EAAW,UAAA,EAAY,OAAA,GAAU,kBAAiB,GAAI,OAAA;AAE9D,EAAA,MAAM,GAAA,GAAM,CAAA,EAAG,OAAO,CAAA,sBAAA,EAAyB,SAAS,CAAA,QAAA,CAAA;AACxD,EAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,IAChC,MAAA,EAAQ,MAAA;AAAA,IACR,OAAA,EAAS;AAAA,MACP,cAAA,EAAgB,kBAAA;AAAA,MAChB,aAAA,EAAe,UAAU,UAAU,CAAA;AAAA;AACrC,GACD,CAAA;AAED,EAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,IAAA,MAAM,SAAA,GAAY,MAAM,QAAA,CAAS,IAAA,EAAK;AACtC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,2BAAA,EAA8B,QAAA,CAAS,MAAM,CAAA,CAAA,EAAI,SAAS,CAAA;AAAA,KAC5D;AAAA,EACF;AAEA,EAAA,OAAO,SAAS,IAAA,EAAK;AACvB","file":"api.js","sourcesContent":["export const DEFAULT_BASE_URL = 'https://api.dev.runwayml.com';\n","import type { ConsumeSessionOptions, ConsumeSessionResponse } from '../types';\nimport { DEFAULT_BASE_URL } from './config';\n\nexport async function consumeSession(\n options: ConsumeSessionOptions,\n): Promise<ConsumeSessionResponse> {\n const { sessionId, sessionKey, baseUrl = DEFAULT_BASE_URL } = options;\n\n const url = `${baseUrl}/v1/realtime_sessions/${sessionId}/consume`;\n const response = await fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${sessionKey}`,\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `Failed to consume session: ${response.status} ${errorText}`,\n );\n }\n\n return response.json();\n}\n"]}
package/dist/index.cjs CHANGED
@@ -7,25 +7,10 @@ var jsxRuntime = require('react/jsx-runtime');
7
7
 
8
8
  // src/api/config.ts
9
9
  var DEFAULT_BASE_URL = "https://api.dev.runwayml.com";
10
- function getBaseUrl() {
11
- try {
12
- const envUrl = process.env.RUNWAYML_BASE_URL;
13
- if (envUrl) return envUrl;
14
- } catch {
15
- }
16
- return DEFAULT_BASE_URL;
17
- }
18
- var config = null;
19
- function getConfig() {
20
- if (!config) {
21
- config = { baseUrl: getBaseUrl() };
22
- }
23
- return config;
24
- }
25
10
 
26
11
  // src/api/consume.ts
27
12
  async function consumeSession(options) {
28
- const { sessionId, sessionKey, baseUrl = getConfig().baseUrl } = options;
13
+ const { sessionId, sessionKey, baseUrl = DEFAULT_BASE_URL } = options;
29
14
  const url = `${baseUrl}/v1/realtime_sessions/${sessionId}/consume`;
30
15
  const response = await fetch(url, {
31
16
  method: "POST",
@@ -43,92 +28,88 @@ async function consumeSession(options) {
43
28
  return response.json();
44
29
  }
45
30
 
31
+ // src/utils/suspense-resource.ts
32
+ function createSuspenseResource(promise) {
33
+ let status = "pending";
34
+ let result;
35
+ let error;
36
+ const suspender = promise.then(
37
+ (value) => {
38
+ status = "fulfilled";
39
+ result = value;
40
+ },
41
+ (err) => {
42
+ status = "rejected";
43
+ error = err;
44
+ }
45
+ );
46
+ return {
47
+ read() {
48
+ switch (status) {
49
+ case "pending":
50
+ throw suspender;
51
+ case "rejected":
52
+ throw error;
53
+ case "fulfilled":
54
+ return result;
55
+ }
56
+ }
57
+ };
58
+ }
59
+
46
60
  // src/hooks/useCredentials.ts
47
- function credentialsReducer(_state, action) {
48
- switch (action.type) {
49
- case "CONNECT":
50
- return { status: "connecting", credentials: null, error: null };
51
- case "CONNECTED":
52
- return {
53
- status: "connected",
54
- credentials: action.credentials,
55
- error: null
56
- };
57
- case "ERROR":
58
- return { status: "error", credentials: null, error: action.error };
61
+ var resourceCache = /* @__PURE__ */ new Map();
62
+ function computeKey(options) {
63
+ if (options.credentials) return `direct:${options.credentials.sessionId}`;
64
+ if (options.sessionId && options.sessionKey)
65
+ return `session:${options.sessionId}`;
66
+ return `connect:${options.avatarId}:${options.connectUrl ?? "custom"}`;
67
+ }
68
+ async function fetchCredentials(options) {
69
+ const { avatarId, sessionId, sessionKey, connectUrl, connect, baseUrl } = options;
70
+ if (sessionId && sessionKey) {
71
+ const { url, token, roomName } = await consumeSession({
72
+ sessionId,
73
+ sessionKey,
74
+ baseUrl
75
+ });
76
+ return { sessionId, serverUrl: url, token, roomName };
59
77
  }
78
+ if (connect) {
79
+ return connect(avatarId);
80
+ }
81
+ if (connectUrl) {
82
+ const response = await fetch(connectUrl, {
83
+ method: "POST",
84
+ headers: { "Content-Type": "application/json" },
85
+ body: JSON.stringify({ avatarId })
86
+ });
87
+ if (!response.ok) {
88
+ const errorText = await response.text();
89
+ throw new Error(`Failed to connect: ${response.status} ${errorText}`);
90
+ }
91
+ return response.json();
92
+ }
93
+ throw new Error(
94
+ "AvatarCall requires one of: credentials, sessionId+sessionKey, connectUrl, or connect"
95
+ );
60
96
  }
61
97
  function useCredentials(options) {
62
- const {
63
- avatarId,
64
- sessionId,
65
- sessionKey,
66
- credentials: directCredentials,
67
- connectUrl,
68
- connect,
69
- onError
70
- } = options;
71
- const [state, dispatch] = react.useReducer(credentialsReducer, {
72
- status: "idle",
73
- credentials: null,
74
- error: null
75
- });
76
- const fetchedForRef = react.useRef(null);
77
- const onErrorRef = react.useRef(onError);
78
- onErrorRef.current = onError;
79
- const mode = directCredentials ? "direct" : sessionId && sessionKey ? "session" : connectUrl || connect ? "connect" : null;
80
- react.useEffect(() => {
81
- if (mode !== "direct" || !directCredentials) return;
82
- dispatch({ type: "CONNECTED", credentials: directCredentials });
83
- }, [mode, directCredentials]);
84
- react.useEffect(() => {
85
- if (mode !== "session" || !sessionId || !sessionKey) return;
86
- if (fetchedForRef.current === sessionId) return;
87
- fetchedForRef.current = sessionId;
88
- dispatch({ type: "CONNECT" });
89
- consumeSession({ sessionId, sessionKey }).then(({ url, token, roomName }) => {
90
- dispatch({
91
- type: "CONNECTED",
92
- credentials: { sessionId, serverUrl: url, token, roomName }
93
- });
94
- }).catch((err) => {
95
- const error = err instanceof Error ? err : new Error(String(err));
96
- dispatch({ type: "ERROR", error });
97
- onErrorRef.current?.(error);
98
- });
99
- }, [mode, sessionId, sessionKey]);
98
+ const key = computeKey(options);
100
99
  react.useEffect(() => {
101
- if (mode !== "connect") return;
102
- if (fetchedForRef.current === avatarId) return;
103
- fetchedForRef.current = avatarId;
104
- dispatch({ type: "CONNECT" });
105
- async function fetchCredentials() {
106
- if (connect) {
107
- return connect(avatarId);
108
- }
109
- if (connectUrl) {
110
- const response = await fetch(connectUrl, {
111
- method: "POST",
112
- headers: { "Content-Type": "application/json" },
113
- body: JSON.stringify({ avatarId })
114
- });
115
- if (!response.ok) {
116
- const errorText = await response.text();
117
- throw new Error(`Failed to connect: ${response.status} ${errorText}`);
118
- }
119
- return response.json();
120
- }
121
- throw new Error("No connect method available");
122
- }
123
- fetchCredentials().then((credentials) => {
124
- dispatch({ type: "CONNECTED", credentials });
125
- }).catch((err) => {
126
- const error = err instanceof Error ? err : new Error(String(err));
127
- dispatch({ type: "ERROR", error });
128
- onErrorRef.current?.(error);
129
- });
130
- }, [mode, avatarId, connectUrl, connect]);
131
- return state;
100
+ return () => {
101
+ resourceCache.delete(key);
102
+ };
103
+ }, [key]);
104
+ if (options.credentials) {
105
+ return options.credentials;
106
+ }
107
+ let resource = resourceCache.get(key);
108
+ if (!resource) {
109
+ resource = createSuspenseResource(fetchCredentials(options));
110
+ resourceCache.set(key, resource);
111
+ }
112
+ return resource.read();
132
113
  }
133
114
  function useLatest(value) {
134
115
  const ref = react.useRef(value);
@@ -263,19 +244,35 @@ function useAvatarSession() {
263
244
  const context = useAvatarSessionContext();
264
245
  return context;
265
246
  }
266
- function AvatarVideo({ children, ...props }) {
247
+
248
+ // src/hooks/useAvatarStatus.ts
249
+ function useAvatarStatus() {
267
250
  const session = useAvatarSession();
268
251
  const { videoTrackRef, hasVideo } = useAvatar();
269
- const isConnecting = session.state === "connecting";
270
- const state = {
271
- hasVideo,
272
- isConnecting,
273
- trackRef: videoTrackRef
274
- };
252
+ switch (session.state) {
253
+ case "connecting":
254
+ case "idle":
255
+ return { status: "connecting" };
256
+ case "active":
257
+ if (hasVideo && videoTrackRef) {
258
+ return { status: "ready", videoTrackRef };
259
+ }
260
+ return { status: "waiting" };
261
+ case "ending":
262
+ return { status: "ending" };
263
+ case "ended":
264
+ return { status: "ended" };
265
+ case "error":
266
+ return { status: "error", error: session.error };
267
+ }
268
+ }
269
+ function AvatarVideo({ children, ...props }) {
270
+ const avatar = useAvatarStatus();
271
+ const videoStatus = avatar.status === "ready" ? avatar : avatar.status === "connecting" ? { status: "connecting" } : { status: "waiting" };
275
272
  if (children) {
276
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children(state) });
273
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: children(videoStatus) });
277
274
  }
278
- return /* @__PURE__ */ jsxRuntime.jsx("div", { ...props, "data-has-video": hasVideo, "data-connecting": isConnecting, children: hasVideo && videoTrackRef && componentsReact.isTrackReference(videoTrackRef) && /* @__PURE__ */ jsxRuntime.jsx(componentsReact.VideoTrack, { trackRef: videoTrackRef }) });
275
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ...props, "data-avatar-video": "", "data-status": videoStatus.status, children: videoStatus.status === "ready" && componentsReact.isTrackReference(videoStatus.videoTrackRef) && /* @__PURE__ */ jsxRuntime.jsx(componentsReact.VideoTrack, { trackRef: videoStatus.videoTrackRef }) });
279
276
  }
280
277
  function useLocalMedia() {
281
278
  const { localParticipant } = componentsReact.useLocalParticipant();
@@ -487,6 +484,7 @@ function AvatarCall({
487
484
  credentials: directCredentials,
488
485
  connectUrl,
489
486
  connect,
487
+ baseUrl,
490
488
  avatarImageUrl,
491
489
  onEnd,
492
490
  onError,
@@ -495,50 +493,24 @@ function AvatarCall({
495
493
  ...props
496
494
  }) {
497
495
  const onErrorRef = useLatest(onError);
498
- const { status, credentials, error } = useCredentials({
496
+ const credentials = useCredentials({
499
497
  avatarId,
500
498
  sessionId,
501
499
  sessionKey,
502
500
  credentials: directCredentials,
503
501
  connectUrl,
504
502
  connect,
505
- onError
503
+ baseUrl
506
504
  });
507
505
  const handleSessionError = (err) => {
508
506
  onErrorRef.current?.(err);
509
507
  };
510
508
  const backgroundStyle = avatarImageUrl ? { "--avatar-image": `url(${avatarImageUrl})` } : void 0;
511
- if (status === "idle" || status === "connecting") {
512
- return /* @__PURE__ */ jsxRuntime.jsx(
513
- "div",
514
- {
515
- ...props,
516
- "data-avatar-call": "",
517
- "data-state": "connecting",
518
- "data-avatar-id": avatarId,
519
- style: { ...props.style, ...backgroundStyle }
520
- }
521
- );
522
- }
523
- if (status === "error" || !credentials) {
524
- return /* @__PURE__ */ jsxRuntime.jsx(
525
- "div",
526
- {
527
- ...props,
528
- "data-avatar-call": "",
529
- "data-state": "error",
530
- "data-avatar-id": avatarId,
531
- "data-error": error?.message,
532
- style: { ...props.style, ...backgroundStyle }
533
- }
534
- );
535
- }
536
509
  return /* @__PURE__ */ jsxRuntime.jsx(
537
510
  "div",
538
511
  {
539
512
  ...props,
540
513
  "data-avatar-call": "",
541
- "data-state": "connected",
542
514
  "data-avatar-id": avatarId,
543
515
  style: { ...props.style, ...backgroundStyle },
544
516
  children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -586,6 +558,10 @@ Object.defineProperty(exports, "AudioRenderer", {
586
558
  enumerable: true,
587
559
  get: function () { return componentsReact.RoomAudioRenderer; }
588
560
  });
561
+ Object.defineProperty(exports, "VideoTrack", {
562
+ enumerable: true,
563
+ get: function () { return componentsReact.VideoTrack; }
564
+ });
589
565
  exports.AvatarCall = AvatarCall;
590
566
  exports.AvatarSession = AvatarSession;
591
567
  exports.AvatarVideo = AvatarVideo;
@@ -594,6 +570,7 @@ exports.ScreenShareVideo = ScreenShareVideo;
594
570
  exports.UserVideo = UserVideo;
595
571
  exports.useAvatar = useAvatar;
596
572
  exports.useAvatarSession = useAvatarSession;
573
+ exports.useAvatarStatus = useAvatarStatus;
597
574
  exports.useLocalMedia = useLocalMedia;
598
575
  //# sourceMappingURL=index.cjs.map
599
576
  //# sourceMappingURL=index.cjs.map