@gradio/client 0.19.4 → 0.20.1

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +8 -1
  3. package/dist/client.d.ts +4 -0
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/constants.d.ts +4 -0
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/helpers/api_info.d.ts +1 -0
  8. package/dist/helpers/api_info.d.ts.map +1 -1
  9. package/dist/helpers/data.d.ts.map +1 -1
  10. package/dist/helpers/init_helpers.d.ts +4 -1
  11. package/dist/helpers/init_helpers.d.ts.map +1 -1
  12. package/dist/index.js +235 -61
  13. package/dist/test/handlers.d.ts.map +1 -1
  14. package/dist/test/test_data.d.ts.map +1 -1
  15. package/dist/types.d.ts +6 -0
  16. package/dist/types.d.ts.map +1 -1
  17. package/dist/utils/duplicate.d.ts.map +1 -1
  18. package/dist/utils/post_data.d.ts.map +1 -1
  19. package/dist/utils/predict.d.ts.map +1 -1
  20. package/dist/utils/submit.d.ts.map +1 -1
  21. package/dist/utils/upload_files.d.ts.map +1 -1
  22. package/dist/utils/view_api.d.ts.map +1 -1
  23. package/package.json +1 -1
  24. package/src/client.ts +70 -28
  25. package/src/constants.ts +5 -0
  26. package/src/helpers/api_info.ts +44 -17
  27. package/src/helpers/data.ts +3 -2
  28. package/src/helpers/init_helpers.ts +103 -9
  29. package/src/test/api_info.test.ts +69 -4
  30. package/src/test/data.test.ts +4 -4
  31. package/src/test/handlers.ts +249 -2
  32. package/src/test/init.test.ts +2 -2
  33. package/src/test/init_helpers.test.ts +53 -1
  34. package/src/test/test_data.ts +3 -0
  35. package/src/types.ts +6 -0
  36. package/src/utils/duplicate.ts +27 -2
  37. package/src/utils/post_data.ts +2 -1
  38. package/src/utils/predict.ts +4 -2
  39. package/src/utils/submit.ts +37 -8
  40. package/src/utils/upload_files.ts +2 -1
  41. package/src/utils/view_api.ts +7 -4
@@ -195,7 +195,7 @@ describe("skip_queue", () => {
195
195
 
196
196
  it("should not skip queue when global and dependency queue is enabled", () => {
197
197
  config.enable_queue = true;
198
- config.dependencies[id].queue = true;
198
+ config.dependencies.find((dep) => dep.id === id)!.queue = true;
199
199
 
200
200
  const result = skip_queue(id, config_response);
201
201
 
@@ -204,7 +204,7 @@ describe("skip_queue", () => {
204
204
 
205
205
  it("should not skip queue when global queue is disabled and dependency queue is enabled", () => {
206
206
  config.enable_queue = false;
207
- config.dependencies[id].queue = true;
207
+ config.dependencies.find((dep) => dep.id === id)!.queue = true;
208
208
 
209
209
  const result = skip_queue(id, config_response);
210
210
 
@@ -213,7 +213,7 @@ describe("skip_queue", () => {
213
213
 
214
214
  it("should should skip queue when global queue and dependency queue is disabled", () => {
215
215
  config.enable_queue = false;
216
- config.dependencies[id].queue = false;
216
+ config.dependencies.find((dep) => dep.id === id)!.queue = false;
217
217
 
218
218
  const result = skip_queue(id, config_response);
219
219
 
@@ -222,7 +222,7 @@ describe("skip_queue", () => {
222
222
 
223
223
  it("should should skip queue when global queue is enabled and dependency queue is disabled", () => {
224
224
  config.enable_queue = true;
225
- config.dependencies[id].queue = false;
225
+ config.dependencies.find((dep) => dep.id === id)!.queue = false;
226
226
 
227
227
  const result = skip_queue(id, config_response);
228
228
 
@@ -6,7 +6,8 @@ import {
6
6
  RUNTIME_URL,
7
7
  SLEEPTIME_URL,
8
8
  UPLOAD_URL,
9
- BROKEN_CONNECTION_MSG
9
+ BROKEN_CONNECTION_MSG,
10
+ LOGIN_URL
10
11
  } from "../constants";
11
12
  import {
12
13
  response_api_info,
@@ -22,16 +23,24 @@ const root_url = "https://huggingface.co";
22
23
 
23
24
  const direct_space_url = "https://hmb-hello-world.hf.space";
24
25
  const private_space_url = "https://hmb-secret-world.hf.space";
26
+ const private_auth_space_url = "https://hmb-private-auth-space.hf.space";
25
27
 
26
28
  const server_error_space_url = "https://hmb-server-error.hf.space";
27
29
  const upload_server_test_space_url = "https://hmb-server-test.hf.space";
28
- const server_error_reference = "hmb/server_error";
30
+ const auth_app_space_url = "https://hmb-auth-space.hf.space";
31
+ const unauth_app_space_url = "https://hmb-unauth-space.hf.space";
32
+ const invalid_auth_space_url = "https://hmb-invalid-auth-space.hf.space";
29
33
 
34
+ const server_error_reference = "hmb/server_error";
30
35
  const app_reference = "hmb/hello_world";
31
36
  const broken_app_reference = "hmb/bye_world";
32
37
  const duplicate_app_reference = "gradio/hello_world";
33
38
  const private_app_reference = "hmb/secret_world";
34
39
  const server_test_app_reference = "hmb/server_test";
40
+ const auth_app_reference = "hmb/auth_space";
41
+ const unauth_app_reference = "hmb/unauth_space";
42
+ const invalid_auth_app_reference = "hmb/invalid_auth_space";
43
+ const private_auth_app_reference = "hmb/private_auth_space";
35
44
 
36
45
  export const handlers: RequestHandler[] = [
37
46
  // /host requests
@@ -58,6 +67,23 @@ export const handlers: RequestHandler[] = [
58
67
  }
59
68
  });
60
69
  }),
70
+ http.get(
71
+ `${root_url}/api/spaces/${private_auth_app_reference}/${HOST_URL}`,
72
+ () => {
73
+ return new HttpResponse(
74
+ JSON.stringify({
75
+ subdomain: "hmb-private-auth-space",
76
+ host: "https://hmb-private-auth-space.hf.space"
77
+ }),
78
+ {
79
+ status: 200,
80
+ headers: {
81
+ "Content-Type": "application/json"
82
+ }
83
+ }
84
+ );
85
+ }
86
+ ),
61
87
  http.get(
62
88
  `${root_url}/api/spaces/${private_app_reference}/${HOST_URL}`,
63
89
  ({ request }) => {
@@ -120,6 +146,68 @@ export const handlers: RequestHandler[] = [
120
146
  );
121
147
  }
122
148
  ),
149
+ http.get(`${root_url}/api/spaces/${auth_app_reference}/${HOST_URL}`, () => {
150
+ return new HttpResponse(
151
+ JSON.stringify({
152
+ subdomain: "hmb-auth-space",
153
+ host: "https://hmb-auth-space.hf.space"
154
+ }),
155
+ {
156
+ status: 200,
157
+ headers: {
158
+ "Content-Type": "application/json"
159
+ }
160
+ }
161
+ );
162
+ }),
163
+ http.get(
164
+ `${root_url}/api/spaces/${invalid_auth_app_reference}/${HOST_URL}`,
165
+ () => {
166
+ return new HttpResponse(
167
+ JSON.stringify({
168
+ subdomain: "hmb-invalid-auth-space",
169
+ host: "https://hmb-invalid-auth-space.hf.space"
170
+ }),
171
+ {
172
+ status: 200,
173
+ headers: {
174
+ "Content-Type": "application/json"
175
+ }
176
+ }
177
+ );
178
+ }
179
+ ),
180
+ http.get(
181
+ `${root_url}/api/spaces/${duplicate_app_reference}/${HOST_URL}`,
182
+ () => {
183
+ return new HttpResponse(
184
+ JSON.stringify({
185
+ subdomain: "gradio-hello-world",
186
+ host: "https://gradio-hello-world.hf.space"
187
+ }),
188
+ {
189
+ status: 200,
190
+ headers: {
191
+ "Content-Type": "application/json"
192
+ }
193
+ }
194
+ );
195
+ }
196
+ ),
197
+ http.get(`${root_url}/api/spaces/${unauth_app_reference}/${HOST_URL}`, () => {
198
+ return new HttpResponse(
199
+ JSON.stringify({
200
+ subdomain: "hmb-unath-space",
201
+ host: "https://hmb-unauth-space.hf.space"
202
+ }),
203
+ {
204
+ status: 200,
205
+ headers: {
206
+ "Content-Type": "application/json"
207
+ }
208
+ }
209
+ );
210
+ }),
123
211
  // /info requests
124
212
  http.get(`${direct_space_url}/${API_INFO_URL}`, () => {
125
213
  return new HttpResponse(JSON.stringify(response_api_info), {
@@ -153,6 +241,22 @@ export const handlers: RequestHandler[] = [
153
241
  }
154
242
  });
155
243
  }),
244
+ http.get(`${auth_app_space_url}/${API_INFO_URL}`, async () => {
245
+ return new HttpResponse(JSON.stringify(response_api_info), {
246
+ status: 200,
247
+ headers: {
248
+ "Content-Type": "application/json"
249
+ }
250
+ });
251
+ }),
252
+ http.get(`${private_auth_space_url}/${API_INFO_URL}`, async () => {
253
+ return new HttpResponse(JSON.stringify(response_api_info), {
254
+ status: 200,
255
+ headers: {
256
+ "Content-Type": "application/json"
257
+ }
258
+ });
259
+ }),
156
260
  // /config requests
157
261
  http.get(`${direct_space_url}/${CONFIG_URL}`, () => {
158
262
  return new HttpResponse(JSON.stringify(config_response), {
@@ -190,6 +294,20 @@ export const handlers: RequestHandler[] = [
190
294
  }
191
295
  );
192
296
  }),
297
+ http.get(`${private_auth_space_url}/${CONFIG_URL}`, () => {
298
+ return new HttpResponse(
299
+ JSON.stringify({
300
+ ...config_response,
301
+ root: "https://hmb-private-auth-space.hf.space"
302
+ }),
303
+ {
304
+ status: 200,
305
+ headers: {
306
+ "Content-Type": "application/json"
307
+ }
308
+ }
309
+ );
310
+ }),
193
311
  http.get(`${direct_space_url}/${CONFIG_URL}`, () => {
194
312
  return new HttpResponse(JSON.stringify(config_response), {
195
313
  status: 500,
@@ -206,6 +324,42 @@ export const handlers: RequestHandler[] = [
206
324
  }
207
325
  });
208
326
  }),
327
+ http.get(`${invalid_auth_space_url}/${CONFIG_URL}`, () => {
328
+ return new HttpResponse(JSON.stringify({ detail: "Unauthorized" }), {
329
+ status: 401,
330
+ headers: {
331
+ "Content-Type": "application/json"
332
+ }
333
+ });
334
+ }),
335
+ http.get(`${auth_app_space_url}/${CONFIG_URL}`, ({ request }) => {
336
+ return new HttpResponse(
337
+ JSON.stringify({
338
+ ...config_response,
339
+ root: "https://hmb-auth-space.hf.space",
340
+ space_id: "hmb/auth_space"
341
+ }),
342
+ {
343
+ status: 200,
344
+ headers: {
345
+ "Content-Type": "application/json"
346
+ }
347
+ }
348
+ );
349
+ }),
350
+ http.get(`${unauth_app_space_url}/${CONFIG_URL}`, () => {
351
+ return new HttpResponse(
352
+ JSON.stringify({
353
+ detail: "Unauthorized"
354
+ }),
355
+ {
356
+ status: 401,
357
+ headers: {
358
+ "Content-Type": "application/json"
359
+ }
360
+ }
361
+ );
362
+ }),
209
363
  // /whoami requests
210
364
  http.get(`${root_url}/api/whoami-v2`, () => {
211
365
  return new HttpResponse(JSON.stringify(whoami_response), {
@@ -387,6 +541,20 @@ export const handlers: RequestHandler[] = [
387
541
  }
388
542
  });
389
543
  }),
544
+ http.get(`${root_url}/api/spaces/${unauth_app_reference}`, () => {
545
+ return new HttpResponse(
546
+ JSON.stringify({
547
+ id: unauth_app_reference,
548
+ runtime: { ...runtime_response }
549
+ }),
550
+ {
551
+ status: 200,
552
+ headers: {
553
+ "Content-Type": "application/json"
554
+ }
555
+ }
556
+ );
557
+ }),
390
558
  // jwt requests
391
559
  http.get(`${root_url}/api/spaces/${app_reference}/jwt`, () => {
392
560
  return new HttpResponse(
@@ -434,5 +602,84 @@ export const handlers: RequestHandler[] = [
434
602
  "Content-Type": "application/json"
435
603
  }
436
604
  });
605
+ }),
606
+ // login requests
607
+ http.post(`${auth_app_space_url}/${LOGIN_URL}`, async ({ request }) => {
608
+ let username;
609
+ let password;
610
+
611
+ await request.formData().then((data) => {
612
+ username = data.get("username");
613
+ password = data.get("password");
614
+ });
615
+
616
+ if (username === "admin" && password === "pass1234") {
617
+ return new HttpResponse(
618
+ JSON.stringify({
619
+ success: true
620
+ }),
621
+ {
622
+ status: 200,
623
+ headers: {
624
+ "Content-Type": "application/json",
625
+ "Set-Cookie":
626
+ "access-token-123=abc; HttpOnly; Path=/; SameSite=none; Secure",
627
+ // @ts-ignore - multiple Set-Cookie headers are returned
628
+ "Set-Cookie":
629
+ "access-token-unsecure-123=abc; HttpOnly; Path=/; SameSite=none; Secure"
630
+ }
631
+ }
632
+ );
633
+ }
634
+
635
+ return new HttpResponse(null, {
636
+ status: 401,
637
+ headers: {
638
+ "Content-Type": "application/json"
639
+ }
640
+ });
641
+ }),
642
+ http.post(`${invalid_auth_space_url}/${LOGIN_URL}`, async () => {
643
+ return new HttpResponse(null, {
644
+ status: 401,
645
+ headers: {
646
+ "Content-Type": "application/json"
647
+ }
648
+ });
649
+ }),
650
+ http.post(`${private_auth_space_url}/${LOGIN_URL}`, async ({ request }) => {
651
+ let username;
652
+ let password;
653
+
654
+ await request.formData().then((data) => {
655
+ username = data.get("username");
656
+ password = data.get("password");
657
+ });
658
+
659
+ if (username === "admin" && password === "pass1234") {
660
+ return new HttpResponse(
661
+ JSON.stringify({
662
+ success: true
663
+ }),
664
+ {
665
+ status: 200,
666
+ headers: {
667
+ "Content-Type": "application/json",
668
+ "Set-Cookie":
669
+ "access-token-123=abc; HttpOnly; Path=/; SameSite=none; Secure",
670
+ // @ts-ignore - multiple Set-Cookie headers are returned
671
+ "Set-Cookie":
672
+ "access-token-unsecure-123=abc; HttpOnly; Path=/; SameSite=none; Secure"
673
+ }
674
+ }
675
+ );
676
+ }
677
+
678
+ return new HttpResponse(null, {
679
+ status: 401,
680
+ headers: {
681
+ "Content-Type": "application/json"
682
+ }
683
+ });
437
684
  })
438
685
  ];
@@ -67,7 +67,7 @@ describe("Client class", () => {
67
67
  Client.connect("hmb/secret_world", {
68
68
  hf_token: "hf_bad_token"
69
69
  })
70
- ).rejects.toThrow(CONFIG_ERROR_MSG);
70
+ ).rejects.toThrowError(SPACE_METADATA_ERROR_MSG);
71
71
  });
72
72
 
73
73
  test("viewing the api info of a running app", async () => {
@@ -77,7 +77,7 @@ describe("Client class", () => {
77
77
 
78
78
  test("viewing the api info of a non-existent app", async () => {
79
79
  const app = Client.connect(broken_app_reference);
80
- await expect(app).rejects.toThrow(CONFIG_ERROR_MSG);
80
+ await expect(app).rejects.toThrowError();
81
81
  });
82
82
  });
83
83
 
@@ -1,10 +1,13 @@
1
1
  import {
2
2
  resolve_root,
3
3
  get_jwt,
4
- determine_protocol
4
+ determine_protocol,
5
+ parse_and_set_cookies
5
6
  } from "../helpers/init_helpers";
6
7
  import { initialise_server } from "./server";
7
8
  import { beforeAll, afterEach, afterAll, it, expect, describe } from "vitest";
9
+ import { Client } from "../client";
10
+ import { INVALID_CREDENTIALS_MSG, MISSING_CREDENTIALS_MSG } from "../constants";
8
11
 
9
12
  const server = initialise_server();
10
13
 
@@ -92,3 +95,52 @@ describe("determine_protocol", () => {
92
95
  });
93
96
  });
94
97
  });
98
+
99
+ describe("parse_and_set_cookies", () => {
100
+ it("should return an empty array when the cookie header is empty", () => {
101
+ const cookie_header = "";
102
+ const result = parse_and_set_cookies(cookie_header);
103
+ expect(result).toEqual([]);
104
+ });
105
+
106
+ it("should parse the cookie header and return an array of cookies", () => {
107
+ const cookie_header = "access-token-123=abc;access-token-unsecured-456=def";
108
+ const result = parse_and_set_cookies(cookie_header);
109
+ expect(result).toEqual(["access-token-123=abc"]);
110
+ });
111
+ });
112
+
113
+ describe("resolve_cookies", () => {
114
+ it("should set the cookies when correct auth credentials are provided", async () => {
115
+ const client = await Client.connect("hmb/auth_space", {
116
+ auth: ["admin", "pass1234"]
117
+ });
118
+
119
+ const api = client.view_api();
120
+ expect((await api).named_endpoints["/predict"]).toBeDefined();
121
+ });
122
+
123
+ it("should connect to a private and authenticated space", async () => {
124
+ const client = await Client.connect("hmb/private_auth_space", {
125
+ hf_token: "hf_123",
126
+ auth: ["admin", "pass1234"]
127
+ });
128
+
129
+ const api = client.view_api();
130
+ expect((await api).named_endpoints["/predict"]).toBeDefined();
131
+ });
132
+
133
+ it("should not set the cookies when auth credentials are invalid", async () => {
134
+ await expect(
135
+ Client.connect("hmb/invalid_auth_space", {
136
+ auth: ["admin", "wrong_password"]
137
+ })
138
+ ).rejects.toThrowError(INVALID_CREDENTIALS_MSG);
139
+ });
140
+
141
+ it("should not set the cookies when auth option is not provided in an auth space", async () => {
142
+ await expect(Client.connect("hmb/unauth_space")).rejects.toThrowError(
143
+ MISSING_CREDENTIALS_MSG
144
+ );
145
+ });
146
+ });
@@ -376,6 +376,7 @@ export const config_response: Config = {
376
376
  },
377
377
  dependencies: [
378
378
  {
379
+ id: 0,
379
380
  targets: [
380
381
  [9, "click"],
381
382
  [1, "submit"]
@@ -404,6 +405,7 @@ export const config_response: Config = {
404
405
  zerogpu: false
405
406
  },
406
407
  {
408
+ id: 1,
407
409
  targets: [[8, "click"]],
408
410
  inputs: [],
409
411
  outputs: [1, 2],
@@ -429,6 +431,7 @@ export const config_response: Config = {
429
431
  zerogpu: false
430
432
  },
431
433
  {
434
+ id: 2,
432
435
  targets: [[8, "click"]],
433
436
  inputs: [],
434
437
  outputs: [5],
package/src/types.ts CHANGED
@@ -154,6 +154,7 @@ export interface Config {
154
154
  }
155
155
 
156
156
  export interface Dependency {
157
+ id: number;
157
158
  targets: [number, string][];
158
159
  inputs: number[];
159
160
  outputs: number[];
@@ -179,6 +180,7 @@ export interface Dependency {
179
180
  final_event: Payload | null;
180
181
  show_api: boolean;
181
182
  zerogpu?: boolean;
183
+ rendered_in: number | null;
182
184
  }
183
185
 
184
186
  export interface DependencyTypes {
@@ -215,6 +217,7 @@ export interface DuplicateOptions extends ClientOptions {
215
217
  export interface ClientOptions {
216
218
  hf_token?: `hf_${string}`;
217
219
  status_callback?: SpaceStatusCallback | null;
220
+ auth?: [string, string] | null;
218
221
  }
219
222
 
220
223
  export interface FileData {
@@ -255,6 +258,8 @@ export interface RenderMessage {
255
258
  data: {
256
259
  components: any[];
257
260
  layout: any;
261
+ dependencies: Dependency[];
262
+ render_id: number;
258
263
  };
259
264
  }
260
265
 
@@ -276,4 +281,5 @@ export interface Status {
276
281
  desc: string | null;
277
282
  }[];
278
283
  time?: Date;
284
+ changed_state_ids?: number[];
279
285
  }
@@ -6,12 +6,17 @@ import {
6
6
  import type { DuplicateOptions } from "../types";
7
7
  import { Client } from "../client";
8
8
  import { SPACE_METADATA_ERROR_MSG } from "../constants";
9
+ import {
10
+ get_cookie_header,
11
+ parse_and_set_cookies
12
+ } from "../helpers/init_helpers";
13
+ import { process_endpoint } from "../helpers/api_info";
9
14
 
10
15
  export async function duplicate(
11
16
  app_reference: string,
12
17
  options: DuplicateOptions
13
18
  ): Promise<Client> {
14
- const { hf_token, private: _private, hardware, timeout } = options;
19
+ const { hf_token, private: _private, hardware, timeout, auth } = options;
15
20
 
16
21
  if (hardware && !hardware_types.includes(hardware)) {
17
22
  throw new Error(
@@ -20,9 +25,29 @@ export async function duplicate(
20
25
  .join(",")}.`
21
26
  );
22
27
  }
28
+
29
+ const { http_protocol, host } = await process_endpoint(
30
+ app_reference,
31
+ hf_token
32
+ );
33
+
34
+ let cookies: string[] | null = null;
35
+
36
+ if (auth) {
37
+ const cookie_header = await get_cookie_header(
38
+ http_protocol,
39
+ host,
40
+ auth,
41
+ fetch
42
+ );
43
+
44
+ if (cookie_header) cookies = parse_and_set_cookies(cookie_header);
45
+ }
46
+
23
47
  const headers = {
24
48
  Authorization: `Bearer ${hf_token}`,
25
- "Content-Type": "application/json"
49
+ "Content-Type": "application/json",
50
+ ...(cookies ? { Cookie: cookies.join("; ") } : {})
26
51
  };
27
52
 
28
53
  const user = (
@@ -19,7 +19,8 @@ export async function post_data(
19
19
  var response = await this.fetch(url, {
20
20
  method: "POST",
21
21
  body: JSON.stringify(body),
22
- headers: { ...headers, ...additional_headers }
22
+ headers: { ...headers, ...additional_headers },
23
+ credentials: "include"
23
24
  });
24
25
  } catch (e) {
25
26
  return [{ error: BROKEN_CONNECTION_MSG }, 500];
@@ -15,10 +15,12 @@ export async function predict(
15
15
  }
16
16
 
17
17
  if (typeof endpoint === "number") {
18
- dependency = this.config.dependencies[endpoint];
18
+ dependency = this.config.dependencies.find((dep) => dep.id == endpoint)!;
19
19
  } else {
20
20
  const trimmed_endpoint = endpoint.replace(/^\//, "");
21
- dependency = this.config.dependencies[this.api_map[trimmed_endpoint]];
21
+ dependency = this.config.dependencies.find(
22
+ (dep) => dep.id == this.api_map[trimmed_endpoint]
23
+ )!;
22
24
  }
23
25
 
24
26
  if (dependency?.types.continuous) {
@@ -162,6 +162,35 @@ export function submit(
162
162
  }
163
163
  }
164
164
 
165
+ const resolve_heartbeat = async (config: Config): Promise<void> => {
166
+ await this._resolve_hearbeat(config);
167
+ };
168
+
169
+ async function handle_render_config(render_config: any): Promise<void> {
170
+ if (!config) return;
171
+ let render_id: number = render_config.render_id;
172
+ config.components = [
173
+ ...config.components.filter((c) => c.props.rendered_in !== render_id),
174
+ ...render_config.components
175
+ ];
176
+ config.dependencies = [
177
+ ...config.dependencies.filter((d) => d.rendered_in !== render_id),
178
+ ...render_config.dependencies
179
+ ];
180
+ const any_state = config.components.some((c) => c.type === "state");
181
+ const any_unload = config.dependencies.some((d) =>
182
+ d.targets.some((t) => t[1] === "unload")
183
+ );
184
+ config.connect_heartbeat = any_state || any_unload;
185
+ await resolve_heartbeat(config);
186
+ fire_event({
187
+ type: "render",
188
+ data: render_config,
189
+ endpoint: _endpoint,
190
+ fn_index
191
+ });
192
+ }
193
+
165
194
  this.handle_blob(config.root, resolved_data, endpoint_info).then(
166
195
  async (_payload) => {
167
196
  payload = {
@@ -201,6 +230,9 @@ export function submit(
201
230
  event_data,
202
231
  trigger_id
203
232
  });
233
+ if (output.render_config) {
234
+ handle_render_config(output.render_config);
235
+ }
204
236
 
205
237
  fire_event({
206
238
  type: "status",
@@ -606,12 +638,7 @@ export function submit(
606
638
  fn_index
607
639
  });
608
640
  if (data.render_config) {
609
- fire_event({
610
- type: "render",
611
- data: data.render_config,
612
- endpoint: _endpoint,
613
- fn_index
614
- });
641
+ await handle_render_config(data.render_config);
615
642
  }
616
643
 
617
644
  if (complete) {
@@ -698,13 +725,15 @@ function get_endpoint_info(
698
725
  if (typeof endpoint === "number") {
699
726
  fn_index = endpoint;
700
727
  endpoint_info = api_info.unnamed_endpoints[fn_index];
701
- dependency = config.dependencies[endpoint];
728
+ dependency = config.dependencies.find((dep) => dep.id == endpoint)!;
702
729
  } else {
703
730
  const trimmed_endpoint = endpoint.replace(/^\//, "");
704
731
 
705
732
  fn_index = api_map[trimmed_endpoint];
706
733
  endpoint_info = api_info.named_endpoints[endpoint.trim()];
707
- dependency = config.dependencies[api_map[trimmed_endpoint]];
734
+ dependency = config.dependencies.find(
735
+ (dep) => dep.id == api_map[trimmed_endpoint]
736
+ )!;
708
737
  }
709
738
 
710
739
  if (typeof fn_index !== "number") {
@@ -33,7 +33,8 @@ export async function upload_files(
33
33
  response = await this.fetch(upload_url, {
34
34
  method: "POST",
35
35
  body: formData,
36
- headers
36
+ headers,
37
+ credentials: "include"
37
38
  });
38
39
  } catch (e) {
39
40
  throw new Error(BROKEN_CONNECTION_MSG + (e as Error).message);
@@ -3,7 +3,7 @@ import semiver from "semiver";
3
3
  import { API_INFO_URL, BROKEN_CONNECTION_MSG } from "../constants";
4
4
  import { Client } from "../client";
5
5
  import { SPACE_FETCHER_URL } from "../constants";
6
- import { transform_api_info } from "../helpers/api_info";
6
+ import { join_urls, transform_api_info } from "../helpers/api_info";
7
7
 
8
8
  export async function view_api(this: Client): Promise<any> {
9
9
  if (this.api_info) return this.api_info;
@@ -34,11 +34,14 @@ export async function view_api(this: Client): Promise<any> {
34
34
  serialize: false,
35
35
  config: JSON.stringify(config)
36
36
  }),
37
- headers
37
+ headers,
38
+ credentials: "include"
38
39
  });
39
40
  } else {
40
- response = await this.fetch(`${config?.root}/${API_INFO_URL}`, {
41
- headers
41
+ const url = join_urls(config.root, API_INFO_URL);
42
+ response = await this.fetch(url, {
43
+ headers,
44
+ credentials: "include"
42
45
  });
43
46
  }
44
47