@gradio/client 2.6.0 → 2.7.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/CHANGELOG.md +10 -0
- package/dist/browser.js +475 -445
- package/dist/client.d.ts +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/helpers/data.d.ts +9 -0
- package/dist/helpers/data.d.ts.map +1 -1
- package/dist/helpers/init_helpers.d.ts.map +1 -1
- package/dist/index.cjs +62 -12
- package/dist/index.js +62 -12
- package/dist/index.min.js +462 -432
- package/dist/utils/submit.d.ts.map +1 -1
- package/dist/utils/view_api.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +19 -15
- package/src/helpers/data.ts +77 -0
- package/src/helpers/init_helpers.ts +8 -10
- package/src/test/data.test.ts +95 -0
- package/src/test/init.test.ts +47 -1
- package/src/test/server.ts +3 -0
- package/src/test/submit.test.ts +37 -0
- package/src/utils/submit.ts +10 -1
- package/src/utils/view_api.ts +4 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"submit.d.ts","sourceRoot":"","sources":["../../src/utils/submit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGX,WAAW,EAMX,cAAc,EACd,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"submit.d.ts","sourceRoot":"","sources":["../../src/utils/submit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGX,WAAW,EAMX,cAAc,EACd,MAAM,UAAU,CAAC;AAyBlB,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAQnC,wBAAgB,MAAM,CACrB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,MAAM,EACzB,IAAI,GAAE,OAAO,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EAC9C,UAAU,CAAC,EAAE,OAAO,EACpB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,EAC1B,UAAU,CAAC,EAAE,OAAO,EACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACzC,cAAc,CAAC,WAAW,CAAC,CA0vB7B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"view_api.d.ts","sourceRoot":"","sources":["../../src/utils/view_api.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAInC,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"view_api.d.ts","sourceRoot":"","sources":["../../src/utils/view_api.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAInC,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAoDzD"}
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -35,6 +35,7 @@ import { check_and_wake_space, check_space_status } from "./helpers/spaces";
|
|
|
35
35
|
import { initialize_zerogpu_handshake } from "./helpers/zerogpu";
|
|
36
36
|
import { open_stream, readable_stream, close_stream } from "./utils/stream";
|
|
37
37
|
import { clear_run_history } from "./utils/run_history";
|
|
38
|
+
import { sign_config_file_urls, sign_file_urls } from "./helpers/data";
|
|
38
39
|
import {
|
|
39
40
|
API_INFO_ERROR_MSG,
|
|
40
41
|
APP_ID_URL,
|
|
@@ -48,6 +49,7 @@ export class Client {
|
|
|
48
49
|
app_reference: string;
|
|
49
50
|
options: ClientOptions;
|
|
50
51
|
deep_link: string | null = null;
|
|
52
|
+
page: string | null = null;
|
|
51
53
|
|
|
52
54
|
config: Config | undefined;
|
|
53
55
|
api_prefix = "";
|
|
@@ -199,6 +201,7 @@ export class Client {
|
|
|
199
201
|
) {
|
|
200
202
|
this.app_reference = app_reference;
|
|
201
203
|
this.deep_link = options.query_params?.deep_link || null;
|
|
204
|
+
this.page = options.query_params?.page ?? null;
|
|
202
205
|
if (!options.events) {
|
|
203
206
|
options.events = ["data"];
|
|
204
207
|
}
|
|
@@ -250,25 +253,19 @@ export class Client {
|
|
|
250
253
|
}
|
|
251
254
|
|
|
252
255
|
async _resolve_heartbeat(_config: Config): Promise<void> {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
this.api_prefix = _config.api_prefix || "";
|
|
256
|
-
|
|
257
|
-
if (this.config && this.config.connect_heartbeat) {
|
|
258
|
-
if (this.config.space_id && this.options.token) {
|
|
259
|
-
this.jwt = await get_jwt(
|
|
260
|
-
this.config.space_id,
|
|
261
|
-
this.options.token,
|
|
262
|
-
this.cookies
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
256
|
+
this.config = _config;
|
|
257
|
+
this.api_prefix = _config.api_prefix || "";
|
|
267
258
|
|
|
268
259
|
if (_config.space_id && this.options.token) {
|
|
269
|
-
this.jwt = await get_jwt(
|
|
260
|
+
this.jwt = await get_jwt(
|
|
261
|
+
_config.space_id,
|
|
262
|
+
this.options.token,
|
|
263
|
+
this.cookies
|
|
264
|
+
);
|
|
270
265
|
}
|
|
271
266
|
|
|
267
|
+
sign_config_file_urls(this.config, this.jwt);
|
|
268
|
+
|
|
272
269
|
if (this.config && this.config.connect_heartbeat) {
|
|
273
270
|
// connect to the heartbeat endpoint via GET request
|
|
274
271
|
const heartbeat_url = new URL(
|
|
@@ -344,6 +341,7 @@ export class Client {
|
|
|
344
341
|
this.config = config;
|
|
345
342
|
this.api_prefix = config.api_prefix || "";
|
|
346
343
|
this.api_map = map_names_to_ids(config.dependencies || []);
|
|
344
|
+
sign_config_file_urls(this.config, this.jwt);
|
|
347
345
|
try {
|
|
348
346
|
this.api_info = await this.view_api();
|
|
349
347
|
} catch (e) {
|
|
@@ -545,6 +543,12 @@ export class Client {
|
|
|
545
543
|
}
|
|
546
544
|
|
|
547
545
|
const output = await response.json();
|
|
546
|
+
sign_file_urls(
|
|
547
|
+
output,
|
|
548
|
+
this.config.root,
|
|
549
|
+
this.config.api_prefix || "",
|
|
550
|
+
this.jwt
|
|
551
|
+
);
|
|
548
552
|
return output;
|
|
549
553
|
} catch (e) {
|
|
550
554
|
console.warn(e);
|
package/src/helpers/data.ts
CHANGED
|
@@ -224,3 +224,80 @@ export function handle_payload(
|
|
|
224
224
|
|
|
225
225
|
return updated_payload;
|
|
226
226
|
}
|
|
227
|
+
|
|
228
|
+
const FILE_ROUTE_PREFIXES = ["/file=", "/file/", "/stream/", "/proxy="];
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Add the Space JWT required for browser-managed media requests.
|
|
232
|
+
*
|
|
233
|
+
* This is a bearer credential and must not be logged or persisted. Restrict it
|
|
234
|
+
* to Gradio routes that serve files; the user's Hugging Face token remains in
|
|
235
|
+
* the Authorization header used to obtain this credential.
|
|
236
|
+
*/
|
|
237
|
+
export function sign_file_urls(
|
|
238
|
+
data: unknown,
|
|
239
|
+
root: string,
|
|
240
|
+
api_prefix: string,
|
|
241
|
+
jwt: string | false
|
|
242
|
+
): void {
|
|
243
|
+
if (!jwt || data === null || typeof data !== "object") return;
|
|
244
|
+
|
|
245
|
+
const signature = jwt;
|
|
246
|
+
const root_url = new URL(root);
|
|
247
|
+
const root_path = root_url.pathname.replace(/\/+$/, "");
|
|
248
|
+
const normalized_api_prefix = api_prefix
|
|
249
|
+
? `/${api_prefix.replace(/^\/+|\/+$/g, "")}`
|
|
250
|
+
: "";
|
|
251
|
+
const route_bases = new Set([
|
|
252
|
+
`${root_path}${normalized_api_prefix}`,
|
|
253
|
+
normalized_api_prefix
|
|
254
|
+
]);
|
|
255
|
+
if (!normalized_api_prefix) {
|
|
256
|
+
route_bases.add(`${root_path}/gradio_api`);
|
|
257
|
+
route_bases.add("/gradio_api");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function visit(value: unknown): void {
|
|
261
|
+
if (value === null || typeof value !== "object") return;
|
|
262
|
+
|
|
263
|
+
const object = value as Record<string, unknown>;
|
|
264
|
+
const meta = object.meta as Record<string, unknown> | undefined;
|
|
265
|
+
if (meta?._type === "gradio.FileData" && typeof object.url === "string") {
|
|
266
|
+
const file_url = new URL(
|
|
267
|
+
object.url,
|
|
268
|
+
`${root_url.toString().replace(/\/$/, "")}/`
|
|
269
|
+
);
|
|
270
|
+
const is_file_route = [...route_bases].some((base) =>
|
|
271
|
+
FILE_ROUTE_PREFIXES.some((route) =>
|
|
272
|
+
file_url.pathname.startsWith(`${base}${route}`)
|
|
273
|
+
)
|
|
274
|
+
);
|
|
275
|
+
if (
|
|
276
|
+
(file_url.protocol === "http:" || file_url.protocol === "https:") &&
|
|
277
|
+
file_url.origin === root_url.origin &&
|
|
278
|
+
is_file_route
|
|
279
|
+
) {
|
|
280
|
+
file_url.searchParams.set("__sign", signature);
|
|
281
|
+
object.url = file_url.toString();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
for (const child of Object.values(object)) visit(child);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
visit(data);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function sign_config_file_urls(
|
|
292
|
+
config: Config,
|
|
293
|
+
jwt: string | false
|
|
294
|
+
): void {
|
|
295
|
+
for (const component of config.components || []) {
|
|
296
|
+
sign_file_urls(
|
|
297
|
+
component.props?.value,
|
|
298
|
+
config.root,
|
|
299
|
+
config.api_prefix || "",
|
|
300
|
+
jwt
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
@@ -129,12 +129,10 @@ export async function resolve_config(
|
|
|
129
129
|
window.gradio_config.dev_mode ||
|
|
130
130
|
(typeof window !== "undefined" && window?.BUILD_MODE === "dev")
|
|
131
131
|
) {
|
|
132
|
-
let config_url = join_urls(
|
|
133
|
-
|
|
134
|
-
this.deep_link
|
|
135
|
-
|
|
136
|
-
: CONFIG_URL
|
|
137
|
-
);
|
|
132
|
+
let config_url = new URL(join_urls(endpoint, CONFIG_URL));
|
|
133
|
+
if (this.deep_link)
|
|
134
|
+
config_url.searchParams.set("deep_link", this.deep_link);
|
|
135
|
+
if (this.page !== null) config_url.searchParams.set("page", this.page);
|
|
138
136
|
const response = await this.fetch(config_url, {
|
|
139
137
|
headers,
|
|
140
138
|
credentials: this.options.credentials ?? "same-origin"
|
|
@@ -160,10 +158,10 @@ export async function resolve_config(
|
|
|
160
158
|
);
|
|
161
159
|
return config;
|
|
162
160
|
} else if (endpoint) {
|
|
163
|
-
let config_url = join_urls(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
);
|
|
161
|
+
let config_url = new URL(join_urls(endpoint, CONFIG_URL));
|
|
162
|
+
if (this.deep_link)
|
|
163
|
+
config_url.searchParams.set("deep_link", this.deep_link);
|
|
164
|
+
if (this.page !== null) config_url.searchParams.set("page", this.page);
|
|
167
165
|
|
|
168
166
|
const response = await this.fetch(config_url, {
|
|
169
167
|
headers,
|
package/src/test/data.test.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
skip_queue,
|
|
6
6
|
post_message,
|
|
7
7
|
handle_file,
|
|
8
|
+
sign_file_urls,
|
|
8
9
|
handle_payload
|
|
9
10
|
} from "../helpers/data";
|
|
10
11
|
import { config_response, endpoint_info } from "./test_data";
|
|
@@ -359,6 +360,100 @@ describe("post_message", () => {
|
|
|
359
360
|
);
|
|
360
361
|
});
|
|
361
362
|
|
|
363
|
+
describe("sign_file_urls", () => {
|
|
364
|
+
it("signs nested file and stream URLs on the private Space origin", () => {
|
|
365
|
+
const data = {
|
|
366
|
+
image: {
|
|
367
|
+
path: "/tmp/cat.png",
|
|
368
|
+
url: "gradio_api/file=/tmp/cat.png?download=true",
|
|
369
|
+
meta: { _type: "gradio.FileData" }
|
|
370
|
+
},
|
|
371
|
+
gallery: [
|
|
372
|
+
{
|
|
373
|
+
image: {
|
|
374
|
+
path: "/tmp/tone.wav",
|
|
375
|
+
url: "/gradio_api/stream/tone.wav",
|
|
376
|
+
is_stream: true,
|
|
377
|
+
meta: { _type: "gradio.FileData" }
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
],
|
|
381
|
+
proxied: {
|
|
382
|
+
path: "https://nested-space.hf.space/gradio_api/file=/tmp/cat.png",
|
|
383
|
+
url: "/app/gradio_api/proxy=https://nested-space.hf.space/gradio_api/file=/tmp/cat.png",
|
|
384
|
+
meta: { _type: "gradio.FileData" }
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
sign_file_urls(
|
|
389
|
+
data,
|
|
390
|
+
"https://private-space.hf.space/app",
|
|
391
|
+
"/gradio_api",
|
|
392
|
+
"jwt_123"
|
|
393
|
+
);
|
|
394
|
+
|
|
395
|
+
expect(data.image.url).toBe(
|
|
396
|
+
"https://private-space.hf.space/app/gradio_api/file=/tmp/cat.png?download=true&__sign=jwt_123"
|
|
397
|
+
);
|
|
398
|
+
expect(data.gallery[0].image.url).toBe(
|
|
399
|
+
"https://private-space.hf.space/gradio_api/stream/tone.wav?__sign=jwt_123"
|
|
400
|
+
);
|
|
401
|
+
expect(data.proxied.url).toBe(
|
|
402
|
+
"https://private-space.hf.space/app/gradio_api/proxy=https://nested-space.hf.space/gradio_api/file=/tmp/cat.png?__sign=jwt_123"
|
|
403
|
+
);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
it("does not sign external or unrelated same-origin URLs", () => {
|
|
407
|
+
const data = {
|
|
408
|
+
external: {
|
|
409
|
+
path: "https://cdn.example.com/cat.png",
|
|
410
|
+
url: "https://cdn.example.com/cat.png",
|
|
411
|
+
meta: { _type: "gradio.FileData" }
|
|
412
|
+
},
|
|
413
|
+
plain: {
|
|
414
|
+
url: "https://private-space.hf.space/gradio_api/file=/tmp/cat.png"
|
|
415
|
+
},
|
|
416
|
+
unrelated: {
|
|
417
|
+
path: "/tmp/cat.png",
|
|
418
|
+
url: "https://private-space.hf.space/api/custom-file",
|
|
419
|
+
meta: { _type: "gradio.FileData" }
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
sign_file_urls(
|
|
424
|
+
data,
|
|
425
|
+
"https://private-space.hf.space",
|
|
426
|
+
"/gradio_api",
|
|
427
|
+
"jwt_123"
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
expect(data.external.url).toBe("https://cdn.example.com/cat.png");
|
|
431
|
+
expect(data.plain.url).toBe(
|
|
432
|
+
"https://private-space.hf.space/gradio_api/file=/tmp/cat.png"
|
|
433
|
+
);
|
|
434
|
+
expect(data.unrelated.url).toBe(
|
|
435
|
+
"https://private-space.hf.space/api/custom-file"
|
|
436
|
+
);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it("does nothing without a Space JWT", () => {
|
|
440
|
+
const file = {
|
|
441
|
+
path: "/tmp/cat.png",
|
|
442
|
+
url: "https://private-space.hf.space/gradio_api/file=/tmp/cat.png",
|
|
443
|
+
meta: { _type: "gradio.FileData" }
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
sign_file_urls(
|
|
447
|
+
file,
|
|
448
|
+
"https://private-space.hf.space",
|
|
449
|
+
"/gradio_api",
|
|
450
|
+
false
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
expect(file.url).not.toContain("__sign");
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
|
|
362
457
|
describe("handle_file", () => {
|
|
363
458
|
it("should handle a Blob object and return the blob", () => {
|
|
364
459
|
const blob = new Blob(["test data"], { type: "image/png" });
|
package/src/test/init.test.ts
CHANGED
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
expect,
|
|
8
8
|
vi
|
|
9
9
|
} from "vitest";
|
|
10
|
-
|
|
11
10
|
import { Client, client, duplicate } from "..";
|
|
12
11
|
import {
|
|
13
12
|
transformed_api_info,
|
|
@@ -16,6 +15,7 @@ import {
|
|
|
16
15
|
} from "./test_data";
|
|
17
16
|
import { initialise_server } from "./server";
|
|
18
17
|
import { SPACE_NOT_FOUND_MSG } from "../constants";
|
|
18
|
+
import { HttpResponse, http } from "msw";
|
|
19
19
|
|
|
20
20
|
const app_reference = "hmb/hello_world";
|
|
21
21
|
const broken_app_reference = "hmb/bye_world";
|
|
@@ -65,6 +65,29 @@ describe("Client class", () => {
|
|
|
65
65
|
expect(app.config).toEqual(config_response);
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
+
test("forwards a page query when resolving config and API info", async () => {
|
|
69
|
+
const requested_urls: string[] = [];
|
|
70
|
+
server.use(
|
|
71
|
+
http.get(`${direct_app_reference}/config`, ({ request }) => {
|
|
72
|
+
requested_urls.push(request.url);
|
|
73
|
+
return HttpResponse.json(config_response);
|
|
74
|
+
}),
|
|
75
|
+
http.get(`${direct_app_reference}/info`, ({ request }) => {
|
|
76
|
+
requested_urls.push(request.url);
|
|
77
|
+
return HttpResponse.json(response_api_info);
|
|
78
|
+
})
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
await Client.connect(direct_app_reference, {
|
|
82
|
+
query_params: { page: "details" }
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(requested_urls).toEqual([
|
|
86
|
+
`${direct_app_reference}/config?page=details`,
|
|
87
|
+
`${direct_app_reference}/info?page=details`
|
|
88
|
+
]);
|
|
89
|
+
});
|
|
90
|
+
|
|
68
91
|
test("connecting successfully to a private running app with a space reference", async () => {
|
|
69
92
|
const app = await Client.connect("hmb/secret_world", {
|
|
70
93
|
token: "hf_123"
|
|
@@ -76,6 +99,29 @@ describe("Client class", () => {
|
|
|
76
99
|
});
|
|
77
100
|
});
|
|
78
101
|
|
|
102
|
+
test("signs initial file values in a private Space config", async () => {
|
|
103
|
+
const props = config_response.components[0].props;
|
|
104
|
+
props.value = {
|
|
105
|
+
path: "/tmp/cat.png",
|
|
106
|
+
url: `${secret_direct_app_reference}/gradio_api/file=/tmp/cat.png`,
|
|
107
|
+
meta: { _type: "gradio.FileData" }
|
|
108
|
+
};
|
|
109
|
+
try {
|
|
110
|
+
const app = await Client.connect("hmb/secret_world", {
|
|
111
|
+
token: "hf_123"
|
|
112
|
+
});
|
|
113
|
+
const value = app.config?.components[0].props.value as {
|
|
114
|
+
url: string;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
expect(value.url).toBe(
|
|
118
|
+
`${secret_direct_app_reference}/gradio_api/file=/tmp/cat.png?__sign=jwt_123`
|
|
119
|
+
);
|
|
120
|
+
} finally {
|
|
121
|
+
delete props.value;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
79
125
|
test("connecting successfully to a private running app with a direct app URL ", async () => {
|
|
80
126
|
const app = await Client.connect(secret_direct_app_reference, {
|
|
81
127
|
token: "hf_123"
|
package/src/test/server.ts
CHANGED
|
@@ -8,6 +8,7 @@ const IS_NODE =
|
|
|
8
8
|
interface MockServer {
|
|
9
9
|
start: (opts: StartOptions) => void | ReturnType<SetupWorker["start"]>;
|
|
10
10
|
stop: () => void | Promise<void>;
|
|
11
|
+
use: (...handlers: any[]) => void;
|
|
11
12
|
resetHandlers: (...handlers: any[]) => void;
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -18,6 +19,7 @@ export async function initialise_server(): Promise<MockServer> {
|
|
|
18
19
|
return {
|
|
19
20
|
start: (opts: StartOptions) => server.listen(opts),
|
|
20
21
|
stop: () => server.close(),
|
|
22
|
+
use: (...h) => server.use(...h),
|
|
21
23
|
resetHandlers: (...h) => server.resetHandlers(...h)
|
|
22
24
|
};
|
|
23
25
|
}
|
|
@@ -26,6 +28,7 @@ export async function initialise_server(): Promise<MockServer> {
|
|
|
26
28
|
return {
|
|
27
29
|
start: (opts: StartOptions) => worker.start(opts),
|
|
28
30
|
stop: () => worker.stop(),
|
|
31
|
+
use: (...h) => worker.use(...h),
|
|
29
32
|
resetHandlers: (...h) => worker.resetHandlers(...h)
|
|
30
33
|
};
|
|
31
34
|
}
|
package/src/test/submit.test.ts
CHANGED
|
@@ -37,6 +37,43 @@ async function race_with_timeout<T>(
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
describe("submit iterator", () => {
|
|
40
|
+
test("signs private Space file URLs before publishing data events", async () => {
|
|
41
|
+
const app = await Client.connect("hmb/hello_world", {
|
|
42
|
+
token: "hf_123",
|
|
43
|
+
events: ["data", "status"]
|
|
44
|
+
});
|
|
45
|
+
app.stream_status.open = true;
|
|
46
|
+
|
|
47
|
+
const iterator = app.submit("/predict", ["hi"]);
|
|
48
|
+
const event_id = await iterator.wait_for_id();
|
|
49
|
+
const callback = app.event_callbacks[event_id as string];
|
|
50
|
+
|
|
51
|
+
const events: any[] = [];
|
|
52
|
+
const consumer = (async () => {
|
|
53
|
+
for await (const event of iterator) events.push(event);
|
|
54
|
+
})();
|
|
55
|
+
|
|
56
|
+
await callback({
|
|
57
|
+
msg: "process_completed",
|
|
58
|
+
output: {
|
|
59
|
+
data: [
|
|
60
|
+
{
|
|
61
|
+
path: "/tmp/cat.png",
|
|
62
|
+
url: `${direct_space_url}/gradio_api/file=/tmp/cat.png`,
|
|
63
|
+
meta: { _type: "gradio.FileData" }
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
success: true
|
|
68
|
+
});
|
|
69
|
+
await consumer;
|
|
70
|
+
|
|
71
|
+
const data_event = events.find((event) => event.type === "data");
|
|
72
|
+
expect(data_event.data[0].url).toBe(
|
|
73
|
+
`${direct_space_url}/gradio_api/file=/tmp/cat.png?__sign=jwt_123`
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
40
77
|
test.skipIf(typeof window === "undefined")(
|
|
41
78
|
"sends the selected history bucket with queued submissions",
|
|
42
79
|
async () => {
|
package/src/utils/submit.ts
CHANGED
|
@@ -10,7 +10,12 @@ import type {
|
|
|
10
10
|
SubmitIterable
|
|
11
11
|
} from "../types";
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
skip_queue,
|
|
15
|
+
post_message,
|
|
16
|
+
handle_payload,
|
|
17
|
+
sign_file_urls
|
|
18
|
+
} from "../helpers/data";
|
|
14
19
|
import { get_zerogpu_origin } from "../helpers/zerogpu";
|
|
15
20
|
import {
|
|
16
21
|
handle_message,
|
|
@@ -70,6 +75,7 @@ export function submit(
|
|
|
70
75
|
|
|
71
76
|
if (!api_info) throw new Error(NO_API_INFO_MSG);
|
|
72
77
|
if (!config) throw new Error("Could not resolve app config");
|
|
78
|
+
const root = config.root;
|
|
73
79
|
|
|
74
80
|
let { fn_index, endpoint_info, dependency } = get_endpoint_info(
|
|
75
81
|
api_info,
|
|
@@ -170,6 +176,9 @@ export function submit(
|
|
|
170
176
|
// event subscription methods
|
|
171
177
|
function fire_event(event: GradioEvent): void {
|
|
172
178
|
update_run_history(history_scope, history_run_id, event);
|
|
179
|
+
if (event.type === "data" || event.type === "render") {
|
|
180
|
+
sign_file_urls(event.data, root, api_prefix, that.jwt);
|
|
181
|
+
}
|
|
173
182
|
if (all_events || events_to_publish[event.type]) {
|
|
174
183
|
push_event(event);
|
|
175
184
|
}
|
package/src/utils/view_api.ts
CHANGED
|
@@ -28,7 +28,10 @@ export async function view_api(this: Client): Promise<any> {
|
|
|
28
28
|
if (typeof window !== "undefined" && window.gradio_api_info) {
|
|
29
29
|
api_info = window.gradio_api_info;
|
|
30
30
|
} else {
|
|
31
|
-
const url =
|
|
31
|
+
const url = new URL(
|
|
32
|
+
join_urls(config.root, this.api_prefix, API_INFO_URL)
|
|
33
|
+
);
|
|
34
|
+
if (this.page !== null) url.searchParams.set("page", this.page);
|
|
32
35
|
response = await this.fetch(url, {
|
|
33
36
|
headers,
|
|
34
37
|
credentials: this.options.credentials ?? "same-origin"
|