Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add eventsource polyfill for Node.js and browser environments #8118

Merged
merged 53 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
56e50b5
add msw setup and initialisation tests
hannahblair Apr 23, 2024
ad7e4e6
add changeset
gradio-pr-bot Apr 23, 2024
fb4bce0
Merge branch 'main' into js-client-tests
hannahblair Apr 23, 2024
705f9e1
add eventsource polyfill for node and browser envs
hannahblair Apr 24, 2024
9da7cfe
add changeset
gradio-pr-bot Apr 24, 2024
0f0320d
Merge branch 'main' into client-eventsource
hannahblair Apr 24, 2024
8d2ce5b
add changeset
gradio-pr-bot Apr 24, 2024
57d48c3
config tweak
hannahblair Apr 24, 2024
5ee640d
Merge branch 'client-eventsource' of https://github.com/gradio-app/gr…
hannahblair Apr 24, 2024
69e32a3
types
hannahblair Apr 24, 2024
7cd5a53
Merge branch 'main' into js-client-tests
hannahblair Apr 24, 2024
706d1dd
update eventsource usage
hannahblair Apr 24, 2024
2a2a151
add changeset
gradio-pr-bot Apr 24, 2024
d588e8e
add walk_and_store_blobs improvements and add tests
hannahblair Apr 25, 2024
f948e7a
add changeset
gradio-pr-bot Apr 25, 2024
a70b768
api_info tests
hannahblair Apr 25, 2024
2fafb73
Merge branch 'js-client-tests' of https://github.com/gradio-app/gradi…
hannahblair Apr 25, 2024
f0deb1b
add direct space URL link tests
hannahblair Apr 25, 2024
37248c7
Merge branch 'main' into js-client-tests
hannahblair Apr 25, 2024
f3fb49a
fix tests
hannahblair Apr 26, 2024
43f6dce
Merge branch 'js-client-tests' of https://github.com/gradio-app/gradi…
hannahblair Apr 26, 2024
c48766a
add view_api tests
hannahblair Apr 26, 2024
da34593
Merge branch 'main' into js-client-tests
hannahblair Apr 26, 2024
6943549
add post_message test
hannahblair Apr 26, 2024
17ee3fd
Merge branch 'js-client-tests' of https://github.com/gradio-app/gradi…
hannahblair Apr 26, 2024
1f91a72
tweak
hannahblair Apr 26, 2024
7a041ef
Merge branch 'main' into js-client-tests
hannahblair Apr 26, 2024
0463ba5
add spaces tests
hannahblair Apr 26, 2024
9dc5c0b
Merge branch 'js-client-tests' of https://github.com/gradio-app/gradi…
hannahblair Apr 26, 2024
732f788
jwt and protocol tests
hannahblair Apr 26, 2024
643e5bf
add post_data tests
hannahblair Apr 26, 2024
cd12651
test tweaks
hannahblair Apr 29, 2024
f939578
Merge branch 'main' into js-client-tests
hannahblair Apr 29, 2024
b465986
Merge branch 'main' into client-eventsource
hannahblair Apr 29, 2024
443898c
Merge branch 'js-client-tests' into client-eventsource
hannahblair Apr 29, 2024
0338ed1
dynamically import eventsource
hannahblair Apr 29, 2024
e2ca25a
revet eventsource imports
hannahblair Apr 29, 2024
480c847
Merge branch 'main' into client-eventsource
hannahblair Apr 30, 2024
b9a6834
add node test
hannahblair May 1, 2024
dc77a19
Merge branch 'main' into client-eventsource
hannahblair May 1, 2024
a78873a
lockfile
hannahblair May 1, 2024
02cfec5
add client test in root pkg file
hannahblair May 1, 2024
6c44d35
Merge branch 'main' into client-eventsource
hannahblair May 1, 2024
0ddf543
lcokfile
hannahblair May 1, 2024
d8d1c88
remove eventsource from js/app
hannahblair May 2, 2024
14a4273
Merge branch 'main' into client-eventsource
hannahblair May 2, 2024
3d7f8f0
add changeset
gradio-pr-bot May 2, 2024
b145987
remove ts ignore
hannahblair May 2, 2024
8d36bd8
move eventsource polyfill to eventsource factory
hannahblair May 2, 2024
1668324
add changeset
gradio-pr-bot May 2, 2024
1466abb
Merge branch 'main' into client-eventsource
hannahblair May 2, 2024
2cc42ac
tweak
hannahblair May 2, 2024
d3c2e57
Merge branch 'client-eventsource' of https://github.com/gradio-app/gr…
hannahblair May 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/red-colts-burn.md
@@ -0,0 +1,7 @@
---
"@gradio/app": patch
"@gradio/client": patch
"gradio": patch
---

fix:Add eventsource polyfill for Node.js and browser environments
7 changes: 7 additions & 0 deletions .changeset/ripe-shirts-grow.md
@@ -0,0 +1,7 @@
---
"@gradio/app": minor
"@gradio/client": minor
"gradio": minor
---

feat:Implement JS Client tests
3 changes: 3 additions & 0 deletions client/js/package.json
Expand Up @@ -13,7 +13,10 @@
"./package.json": "./package.json"
},
"dependencies": {
"@types/eventsource": "^1.1.15",
"bufferutil": "^4.0.7",
"eventsource": "^2.0.2",
"msw": "^2.2.1",
"semiver": "^1.1.0",
"typescript": "^5.0.0",
"ws": "^8.13.0"
Expand Down
44 changes: 36 additions & 8 deletions client/js/src/client.ts
Expand Up @@ -27,13 +27,24 @@ import {
} from "./helpers/init_helpers";
import { check_space_status } from "./helpers/spaces";
import { open_stream } from "./utils/stream";
import { API_INFO_ERROR_MSG, CONFIG_ERROR_MSG } from "./constants";

export class NodeBlob extends Blob {
constructor(blobParts?: BlobPart[], options?: BlobPropertyBag) {
super(blobParts, options);
}
}

if (typeof window === "undefined") {
import("eventsource")
.then((EventSourceModule) => {
global.EventSource = EventSourceModule.default as any;
})
.catch((error) =>
console.error("Failed to load EventSource module:", error)
);
}

hannahblair marked this conversation as resolved.
Show resolved Hide resolved
export class Client {
app_reference: string;
options: ClientOptions;
Expand Down Expand Up @@ -61,10 +72,12 @@ export class Client {
}

eventSource_factory(url: URL): EventSource | null {
if (typeof window !== undefined && typeof EventSource !== "undefined") {
if (typeof window !== "undefined" && "EventSource" in window) {
return new EventSource(url.toString());
} else if (typeof global !== "undefined" && global.EventSource) {
return new global.EventSource(url.toString());
}
return null; // todo: polyfill eventsource for node envs
return null;
}

view_api: () => Promise<ApiInfo<JsApiData>>;
Expand Down Expand Up @@ -95,7 +108,7 @@ export class Client {
event_data?: unknown
) => Promise<unknown>;
open_stream: () => void;
resolve_config: (endpoint: string) => Promise<Config | undefined>;
private resolve_config: (endpoint: string) => Promise<Config | undefined>;
constructor(app_reference: string, options: ClientOptions = {}) {
this.app_reference = app_reference;
this.options = options;
Expand Down Expand Up @@ -142,7 +155,7 @@ export class Client {
}
});
} catch (e) {
throw Error(`Could not resolve config: ${e}`);
throw Error(CONFIG_ERROR_MSG + (e as Error).message);
}

this.api_info = await this.view_api();
Expand Down Expand Up @@ -182,7 +195,7 @@ export class Client {
config = await this.resolve_config(`${http_protocol}//${host}`);

if (!config) {
throw new Error("Could not resolve app config");
throw new Error(CONFIG_ERROR_MSG);
}

return this.config_success(config);
Expand Down Expand Up @@ -224,7 +237,7 @@ export class Client {
try {
this.api_info = await this.view_api();
} catch (e) {
console.error(`Could not get API details: ${(e as Error).message}`);
console.error(API_INFO_ERROR_MSG + (e as Error).message);
}

return this.prepare_return_obj();
Expand All @@ -237,7 +250,7 @@ export class Client {
try {
this.config = await this._resolve_config();
if (!this.config) {
throw new Error("Could not resolve app config");
throw new Error(CONFIG_ERROR_MSG);
}

const _config = await this.config_success(this.config);
Expand All @@ -263,7 +276,7 @@ export class Client {
data: unknown[] | { binary: boolean; data: Record<string, any> }
): Promise<unknown> {
if (!this.config) {
throw new Error("Could not resolve app config");
throw new Error(CONFIG_ERROR_MSG);
}

const headers: {
Expand Down Expand Up @@ -363,4 +376,19 @@ export async function client(
return await Client.connect(app_reference, options);
}

/**
* @deprecated This method will be removed in v1.0. Use `Client.duplicate()` instead.
* Creates a duplicate of a space and returns a client instance for the duplicated space.
*
* @param {string} app_reference - The reference or URL to a Gradio space or app to duplicate.
* @param {DuplicateOptions} options - Configuration options for the client.
* @returns {Promise<Client>} A promise that resolves to a `Client` instance.
*/
export async function duplicate_space(
app_reference: string,
options: DuplicateOptions
): Promise<Client> {
return await Client.duplicate(app_reference, options);
}

export type ClientInstance = Client;
11 changes: 9 additions & 2 deletions client/js/src/constants.ts
Expand Up @@ -9,12 +9,19 @@ export const UPLOAD_URL = "upload";
export const LOGIN_URL = "login";
export const CONFIG_URL = "config";
export const API_INFO_URL = "info";
export const RUNTIME_URL = "runtime";
export const SLEEPTIME_URL = "sleeptime";
export const RAW_API_INFO_URL = "info?serialize=False";
export const SPACE_FETCHER_URL =
"https://gradio-space-api-fetcher-v2.hf.space/api";
export const RESET_URL = "reset";
export const SPACE_URL = "https://hf.space/{}";

// messages
export const QUEUE_FULL_MSG = "This application is too busy. Keep trying!";
export const BROKEN_CONNECTION_MSG = "Connection errored out.";
export const QUEUE_FULL_MSG =
"This application is currently busy. Please try again. ";
export const BROKEN_CONNECTION_MSG = "Connection errored out. ";
export const CONFIG_ERROR_MSG = "Could not resolve app config. ";
export const SPACE_STATUS_ERROR_MSG = "Could not get space status. ";
export const API_INFO_ERROR_MSG = "Could not get API info. ";
export const SPACE_METADATA_ERROR_MSG = "Space metadata could not be loaded. ";
8 changes: 4 additions & 4 deletions client/js/src/helpers/api_info.ts
Expand Up @@ -29,16 +29,16 @@ export async function process_endpoint(
{ headers }
);

if (res.status !== 200)
throw new Error("Space metadata could not be loaded.");
const _host = (await res.json()).host;

return {
space_id: app_reference,
...determine_protocol(_host)
};
} catch (e: any) {
throw new Error("Space metadata could not be loaded." + e.message);
} catch (e) {
throw new Error(
"Space metadata could not be loaded. " + (e as Error).message
);
}
}

Expand Down
74 changes: 46 additions & 28 deletions client/js/src/helpers/data.ts
Expand Up @@ -5,7 +5,7 @@ import type {
Config,
EndpointInfo,
JsApiData,
ParamType
DataType
} from "../types";

export function update_object(
Expand All @@ -31,22 +31,22 @@ export function update_object(
}

export async function walk_and_store_blobs(
param: ParamType,
data: DataType,
type: string | undefined = undefined,
path: string[] = [],
root = false,
endpoint_info: EndpointInfo<ApiData | JsApiData> | undefined = undefined
): Promise<BlobRef[]> {
if (Array.isArray(param)) {
if (Array.isArray(data)) {
let blob_refs: BlobRef[] = [];

await Promise.all(
param.map(async (item) => {
data.map(async (item) => {
let new_path = path.slice();
new_path.push(item);

const array_refs = await walk_and_store_blobs(
param[item],
data[item],
root ? endpoint_info?.parameters[item]?.component || undefined : type,
new_path,
false,
Expand All @@ -58,44 +58,62 @@ export async function walk_and_store_blobs(
);

return blob_refs;
} else if (globalThis.Buffer && param instanceof globalThis.Buffer) {
} else if (
(globalThis.Buffer && data instanceof globalThis.Buffer) ||
data instanceof Blob
) {
const is_image = type === "Image";
return [
{
path: path,
blob: is_image ? false : new NodeBlob([param]),
blob: is_image ? false : new NodeBlob([data]),
type
}
];
} else if (typeof param === "object") {
} else if (typeof data === "object" && data !== null) {
let blob_refs: BlobRef[] = [];
for (let key in param) {
if (param.hasOwnProperty(key)) {
let new_path = path.slice();
new_path.push(key);
blob_refs = blob_refs.concat(
await walk_and_store_blobs(
// @ts-ignore
param[key],
undefined,
new_path,
false,
endpoint_info
)
);
}
for (const key of Object.keys(data) as (keyof typeof data)[]) {
const new_path = [...path, key];
const value = data[key];

blob_refs = blob_refs.concat(
await walk_and_store_blobs(
value,
undefined,
new_path,
false,
endpoint_info
)
);
}

if (
!blob_refs.length &&
!(
data instanceof Blob ||
data instanceof ArrayBuffer ||
data instanceof Uint8Array
)
) {
return [
{
path: path,
blob: new NodeBlob([JSON.stringify(data)]),
type: typeof data
}
];
}
return blob_refs;
}

return [];
}

export function skip_queue(id: number, config: Config): boolean {
return (
!(config?.dependencies?.[id]?.queue === null
? config.enable_queue
: config?.dependencies?.[id]?.queue) || false
);
if (config?.dependencies?.[id]?.queue !== null) {
return !config.dependencies[id].queue;
}
return !config.enable_queue;
}

// todo: add jsdoc for this function
Expand Down
7 changes: 3 additions & 4 deletions client/js/src/helpers/init_helpers.ts
@@ -1,5 +1,5 @@
import type { Config } from "../types";
import { CONFIG_URL } from "../constants";
import { CONFIG_ERROR_MSG, CONFIG_URL } from "../constants";
import { Client } from "..";

/**
Expand Down Expand Up @@ -38,7 +38,6 @@ export async function get_jwt(

return jwt || false;
} catch (e) {
console.error(e);
return false;
}
}
Expand Down Expand Up @@ -89,10 +88,10 @@ export async function resolve_config(
config.root = endpoint;
return config;
}
throw new Error("Could not get config.");
throw new Error(CONFIG_ERROR_MSG);
}

throw new Error("No config or app endpoint found");
throw new Error(CONFIG_ERROR_MSG);
}

export function determine_protocol(endpoint: string): {
Expand Down
11 changes: 8 additions & 3 deletions client/js/src/helpers/spaces.ts
@@ -1,3 +1,8 @@
import {
RUNTIME_URL,
SLEEPTIME_URL,
SPACE_STATUS_ERROR_MSG
} from "../constants";
import type { SpaceStatusCallback } from "../types";

export async function check_space_status(
Expand All @@ -22,7 +27,7 @@ export async function check_space_status(
status_callback({
status: "error",
load_status: "error",
message: "Could not get space status",
message: SPACE_STATUS_ERROR_MSG,
detail: "NOT_FOUND"
});
return;
Expand Down Expand Up @@ -121,7 +126,7 @@ export async function get_space_hardware(

try {
const res = await fetch(
`https://huggingface.co/api/spaces/${space_id}/runtime`,
`https://huggingface.co/api/spaces/${space_id}/${RUNTIME_URL}`,
{ headers }
);

Expand Down Expand Up @@ -154,7 +159,7 @@ export async function set_space_timeout(

try {
const res = await fetch(
`https://huggingface.co/api/spaces/${space_id}/sleeptime`,
`https://huggingface.co/api/spaces/${space_id}/${SLEEPTIME_URL}`,
{
method: "POST",
headers: { "Content-Type": "application/json", ...headers },
Expand Down
2 changes: 1 addition & 1 deletion client/js/src/index.ts
Expand Up @@ -14,4 +14,4 @@ export type {

// todo: remove in @gradio/client v1.0
export { client } from "./client";
export { duplicate } from "./utils/duplicate";
export { duplicate_space as duplicate } from "./client";