@runwayml/avatars-react 0.2.2 → 0.5.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 };
77
+ }
78
+ if (connect) {
79
+ return connect(avatarId);
59
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,43 @@ 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(
276
+ "div",
277
+ {
278
+ ...props,
279
+ "data-avatar-video": "",
280
+ "data-avatar-status": videoStatus.status,
281
+ children: videoStatus.status === "ready" && componentsReact.isTrackReference(videoStatus.videoTrackRef) && /* @__PURE__ */ jsxRuntime.jsx(componentsReact.VideoTrack, { trackRef: videoStatus.videoTrackRef })
282
+ }
283
+ );
279
284
  }
280
285
  function useLocalMedia() {
281
286
  const { localParticipant } = componentsReact.useLocalParticipant();
@@ -330,13 +335,22 @@ function ControlBar({
330
335
  ...props
331
336
  }) {
332
337
  const session = useAvatarSession();
333
- const { isMicEnabled, isCameraEnabled, toggleMic, toggleCamera } = useLocalMedia();
338
+ const {
339
+ isMicEnabled,
340
+ isCameraEnabled,
341
+ isScreenShareEnabled,
342
+ toggleMic,
343
+ toggleCamera,
344
+ toggleScreenShare
345
+ } = useLocalMedia();
334
346
  const isActive = session.state === "active";
335
347
  const state = {
336
348
  isMicEnabled,
337
349
  isCameraEnabled,
350
+ isScreenShareEnabled,
338
351
  toggleMic,
339
352
  toggleCamera,
353
+ toggleScreenShare,
340
354
  endCall: session.end,
341
355
  isActive
342
356
  };
@@ -346,14 +360,14 @@ function ControlBar({
346
360
  if (!isActive) {
347
361
  return null;
348
362
  }
349
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...props, "data-active": isActive, children: [
363
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...props, "data-avatar-control-bar": "", "data-avatar-active": isActive, children: [
350
364
  showMicrophone && /* @__PURE__ */ jsxRuntime.jsx(
351
365
  "button",
352
366
  {
353
367
  type: "button",
354
368
  onClick: toggleMic,
355
- "data-control": "microphone",
356
- "data-enabled": isMicEnabled,
369
+ "data-avatar-control": "microphone",
370
+ "data-avatar-enabled": isMicEnabled,
357
371
  "aria-label": isMicEnabled ? "Mute microphone" : "Unmute microphone",
358
372
  children: microphoneIcon
359
373
  }
@@ -363,8 +377,8 @@ function ControlBar({
363
377
  {
364
378
  type: "button",
365
379
  onClick: toggleCamera,
366
- "data-control": "camera",
367
- "data-enabled": isCameraEnabled,
380
+ "data-avatar-control": "camera",
381
+ "data-avatar-enabled": isCameraEnabled,
368
382
  "aria-label": isCameraEnabled ? "Turn off camera" : "Turn on camera",
369
383
  children: cameraIcon
370
384
  }
@@ -374,7 +388,8 @@ function ControlBar({
374
388
  {
375
389
  source: livekitClient.Track.Source.ScreenShare,
376
390
  showIcon: false,
377
- "data-control": "screen-share",
391
+ "data-avatar-control": "screen-share",
392
+ "data-avatar-enabled": isScreenShareEnabled,
378
393
  "aria-label": "Toggle screen share",
379
394
  children: screenShareIcon
380
395
  }
@@ -384,7 +399,8 @@ function ControlBar({
384
399
  {
385
400
  type: "button",
386
401
  onClick: session.end,
387
- "data-control": "end-call",
402
+ "data-avatar-control": "end-call",
403
+ "data-avatar-enabled": true,
388
404
  "aria-label": "End call",
389
405
  children: phoneIcon
390
406
  }
@@ -458,7 +474,11 @@ var phoneIcon = /* @__PURE__ */ jsxRuntime.jsx(
458
474
  children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12.8429 22.5693L11.4018 21.0986C11.2675 20.9626 11.1625 20.7995 11.0935 20.6197C11.0245 20.4399 10.9931 20.2474 11.0013 20.0545C11.0094 19.8616 11.0569 19.6726 11.1408 19.4995C11.2247 19.3265 11.343 19.1732 11.4883 19.0495C13.127 17.7049 15.0519 16.7714 17.1083 16.3239C19.0064 15.892 20.9744 15.892 22.8725 16.3239C24.9374 16.7743 26.8693 17.7147 28.5117 19.0691C28.6565 19.1924 28.7746 19.3451 28.8585 19.5176C28.9423 19.69 28.99 19.8784 28.9986 20.0707C29.0072 20.263 28.9764 20.455 28.9083 20.6345C28.8402 20.814 28.7362 20.9771 28.603 21.1133L27.1619 22.584C26.9311 22.8242 26.6226 22.9706 26.2938 22.9959C25.9651 23.0211 25.6385 22.9235 25.3751 22.7212C24.8531 22.3127 24.2875 21.9657 23.689 21.6869C23.4525 21.5774 23.2517 21.4009 23.1103 21.1785C22.969 20.9561 22.8931 20.697 22.8917 20.4319V19.1867C21.0053 18.6573 19.0139 18.6573 17.1275 19.1867V20.4319C17.1261 20.697 17.0502 20.9561 16.9089 21.1785C16.7676 21.4009 16.5667 21.5774 16.3302 21.6869C15.7317 21.9657 15.1661 22.3127 14.6442 22.7212C14.3779 22.9258 14.0473 23.0233 13.7152 22.9953C13.383 22.9673 13.0726 22.8156 12.8429 22.5693Z" })
459
475
  }
460
476
  );
461
- function UserVideo({ children, mirror = true, ...props }) {
477
+ function UserVideo({
478
+ children,
479
+ mirror = true,
480
+ ...props
481
+ }) {
462
482
  const { localVideoTrackRef, isCameraEnabled } = useLocalMedia();
463
483
  const hasVideo = localVideoTrackRef !== null && componentsReact.isTrackReference(localVideoTrackRef);
464
484
  const state = {
@@ -473,9 +493,10 @@ function UserVideo({ children, mirror = true, ...props }) {
473
493
  "div",
474
494
  {
475
495
  ...props,
476
- "data-has-video": hasVideo,
477
- "data-camera-enabled": isCameraEnabled,
478
- "data-mirror": mirror,
496
+ "data-avatar-user-video": "",
497
+ "data-avatar-has-video": hasVideo,
498
+ "data-avatar-camera-enabled": isCameraEnabled,
499
+ "data-avatar-mirror": mirror,
479
500
  children: hasVideo && localVideoTrackRef && componentsReact.isTrackReference(localVideoTrackRef) && /* @__PURE__ */ jsxRuntime.jsx(componentsReact.VideoTrack, { trackRef: localVideoTrackRef })
480
501
  }
481
502
  );
@@ -487,6 +508,7 @@ function AvatarCall({
487
508
  credentials: directCredentials,
488
509
  connectUrl,
489
510
  connect,
511
+ baseUrl,
490
512
  avatarImageUrl,
491
513
  onEnd,
492
514
  onError,
@@ -495,50 +517,24 @@ function AvatarCall({
495
517
  ...props
496
518
  }) {
497
519
  const onErrorRef = useLatest(onError);
498
- const { status, credentials, error } = useCredentials({
520
+ const credentials = useCredentials({
499
521
  avatarId,
500
522
  sessionId,
501
523
  sessionKey,
502
524
  credentials: directCredentials,
503
525
  connectUrl,
504
526
  connect,
505
- onError
527
+ baseUrl
506
528
  });
507
529
  const handleSessionError = (err) => {
508
530
  onErrorRef.current?.(err);
509
531
  };
510
532
  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
533
  return /* @__PURE__ */ jsxRuntime.jsx(
537
534
  "div",
538
535
  {
539
536
  ...props,
540
537
  "data-avatar-call": "",
541
- "data-state": "connected",
542
538
  "data-avatar-id": avatarId,
543
539
  style: { ...props.style, ...backgroundStyle },
544
540
  children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -558,7 +554,10 @@ function AvatarCall({
558
554
  }
559
555
  );
560
556
  }
561
- function ScreenShareVideo({ children, ...props }) {
557
+ function ScreenShareVideo({
558
+ children,
559
+ ...props
560
+ }) {
562
561
  const { localParticipant } = componentsReact.useLocalParticipant();
563
562
  const tracks = componentsReact.useTracks(
564
563
  [{ source: livekitClient.Track.Source.ScreenShare, withPlaceholder: false }],
@@ -579,13 +578,17 @@ function ScreenShareVideo({ children, ...props }) {
579
578
  if (!isSharing) {
580
579
  return null;
581
580
  }
582
- return /* @__PURE__ */ jsxRuntime.jsx("div", { ...props, "data-sharing": isSharing, children: screenShareTrackRef && componentsReact.isTrackReference(screenShareTrackRef) && /* @__PURE__ */ jsxRuntime.jsx(componentsReact.VideoTrack, { trackRef: screenShareTrackRef }) });
581
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ...props, "data-avatar-screen-share": "", "data-avatar-sharing": isSharing, children: screenShareTrackRef && componentsReact.isTrackReference(screenShareTrackRef) && /* @__PURE__ */ jsxRuntime.jsx(componentsReact.VideoTrack, { trackRef: screenShareTrackRef }) });
583
582
  }
584
583
 
585
584
  Object.defineProperty(exports, "AudioRenderer", {
586
585
  enumerable: true,
587
586
  get: function () { return componentsReact.RoomAudioRenderer; }
588
587
  });
588
+ Object.defineProperty(exports, "VideoTrack", {
589
+ enumerable: true,
590
+ get: function () { return componentsReact.VideoTrack; }
591
+ });
589
592
  exports.AvatarCall = AvatarCall;
590
593
  exports.AvatarSession = AvatarSession;
591
594
  exports.AvatarVideo = AvatarVideo;
@@ -594,6 +597,7 @@ exports.ScreenShareVideo = ScreenShareVideo;
594
597
  exports.UserVideo = UserVideo;
595
598
  exports.useAvatar = useAvatar;
596
599
  exports.useAvatarSession = useAvatarSession;
600
+ exports.useAvatarStatus = useAvatarStatus;
597
601
  exports.useLocalMedia = useLocalMedia;
598
602
  //# sourceMappingURL=index.cjs.map
599
603
  //# sourceMappingURL=index.cjs.map