@gradio/client 0.16.0 → 0.18.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.
Files changed (84) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/README.md +49 -43
  3. package/dist/client.d.ts +63 -70
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/constants.d.ts +23 -0
  6. package/dist/constants.d.ts.map +1 -0
  7. package/dist/helpers/api_info.d.ts +25 -0
  8. package/dist/helpers/api_info.d.ts.map +1 -0
  9. package/dist/helpers/data.d.ts +8 -0
  10. package/dist/helpers/data.d.ts.map +1 -0
  11. package/dist/{utils.d.ts → helpers/init_helpers.d.ts} +6 -18
  12. package/dist/helpers/init_helpers.d.ts.map +1 -0
  13. package/dist/helpers/spaces.d.ts +7 -0
  14. package/dist/helpers/spaces.d.ts.map +1 -0
  15. package/dist/index.d.ts +8 -4
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1628 -1398
  18. package/dist/test/handlers.d.ts +3 -0
  19. package/dist/test/handlers.d.ts.map +1 -0
  20. package/dist/test/mock_eventsource.d.ts +2 -0
  21. package/dist/test/mock_eventsource.d.ts.map +1 -0
  22. package/dist/test/server.d.ts +2 -0
  23. package/dist/test/server.d.ts.map +1 -0
  24. package/dist/test/test_data.d.ts +76 -0
  25. package/dist/test/test_data.d.ts.map +1 -0
  26. package/dist/types.d.ts +153 -49
  27. package/dist/types.d.ts.map +1 -1
  28. package/dist/upload.d.ts +2 -2
  29. package/dist/upload.d.ts.map +1 -1
  30. package/dist/utils/duplicate.d.ts +4 -0
  31. package/dist/utils/duplicate.d.ts.map +1 -0
  32. package/dist/utils/handle_blob.d.ts +4 -0
  33. package/dist/utils/handle_blob.d.ts.map +1 -0
  34. package/dist/utils/post_data.d.ts +4 -0
  35. package/dist/utils/post_data.d.ts.map +1 -0
  36. package/dist/utils/predict.d.ts +4 -0
  37. package/dist/utils/predict.d.ts.map +1 -0
  38. package/dist/utils/stream.d.ts +8 -0
  39. package/dist/utils/stream.d.ts.map +1 -0
  40. package/dist/utils/submit.d.ts +4 -0
  41. package/dist/utils/submit.d.ts.map +1 -0
  42. package/dist/utils/upload_files.d.ts +4 -0
  43. package/dist/utils/upload_files.d.ts.map +1 -0
  44. package/dist/utils/view_api.d.ts +3 -0
  45. package/dist/utils/view_api.d.ts.map +1 -0
  46. package/dist/{wrapper-6f348d45.js → wrapper-CviSselG.js} +259 -17
  47. package/package.json +10 -3
  48. package/src/client.ts +314 -1691
  49. package/src/constants.ts +27 -0
  50. package/src/globals.d.ts +2 -21
  51. package/src/helpers/api_info.ts +300 -0
  52. package/src/helpers/data.ts +133 -0
  53. package/src/helpers/init_helpers.ts +130 -0
  54. package/src/helpers/spaces.ts +197 -0
  55. package/src/index.ts +16 -10
  56. package/src/test/api_info.test.ts +456 -0
  57. package/src/test/data.test.ts +281 -0
  58. package/src/test/handlers.ts +438 -0
  59. package/src/test/init.test.ts +139 -0
  60. package/src/test/init_helpers.test.ts +94 -0
  61. package/src/test/mock_eventsource.ts +11 -0
  62. package/src/test/post_data.test.ts +45 -0
  63. package/src/test/server.ts +6 -0
  64. package/src/test/spaces.test.ts +145 -0
  65. package/src/test/stream.test.ts +67 -0
  66. package/src/test/test_data.ts +557 -0
  67. package/src/test/upload_files.test.ts +42 -0
  68. package/src/test/view_api.test.ts +53 -0
  69. package/src/types.ts +201 -59
  70. package/src/upload.ts +19 -15
  71. package/src/utils/duplicate.ts +104 -0
  72. package/src/utils/handle_blob.ts +47 -0
  73. package/src/utils/post_data.ts +37 -0
  74. package/src/utils/predict.ts +56 -0
  75. package/src/utils/stream.ts +175 -0
  76. package/src/utils/submit.ts +697 -0
  77. package/src/utils/upload_files.ts +51 -0
  78. package/src/utils/view_api.ts +67 -0
  79. package/src/vite-env.d.ts +1 -0
  80. package/tsconfig.json +15 -2
  81. package/vite.config.js +11 -17
  82. package/dist/utils.d.ts.map +0 -1
  83. package/src/client.node-test.ts +0 -172
  84. package/src/utils.ts +0 -314
package/src/types.ts CHANGED
@@ -1,13 +1,131 @@
1
+ // API Data Types
2
+
3
+ import { hardware_types } from "./helpers/spaces";
4
+
5
+ export interface ApiData {
6
+ label: string;
7
+ type: {
8
+ type: any;
9
+ description: string;
10
+ };
11
+ component: string;
12
+ example_input?: any;
13
+ python_type: { type: string; description: string };
14
+ serializer: string;
15
+ }
16
+
17
+ export interface JsApiData {
18
+ label: string;
19
+ type: string;
20
+ description: string;
21
+ component: string;
22
+ example_input?: any;
23
+ serializer: string;
24
+ python_type: { type: string; description: string };
25
+ }
26
+
27
+ export interface EndpointInfo<T extends ApiData | JsApiData> {
28
+ parameters: T[];
29
+ returns: T[];
30
+ type?: DependencyTypes;
31
+ }
32
+
33
+ export interface ApiInfo<T extends ApiData | JsApiData> {
34
+ named_endpoints: Record<string, EndpointInfo<T>>;
35
+ unnamed_endpoints: Record<string, EndpointInfo<T>>;
36
+ }
37
+
38
+ export interface BlobRef {
39
+ path: string[];
40
+ type: string | undefined;
41
+ blob: Blob | File | false;
42
+ }
43
+
44
+ export type DataType = string | Buffer | Record<string, any> | any[];
45
+
46
+ // Event and Submission Types
47
+
48
+ type event = <K extends EventType>(
49
+ eventType: K,
50
+ listener: EventListener<K>
51
+ ) => SubmitReturn;
52
+
53
+ type predict = (
54
+ endpoint: string | number,
55
+ data?: unknown[],
56
+ event_data?: unknown
57
+ ) => Promise<unknown>;
58
+
59
+ export type client_return = {
60
+ config: Config | undefined;
61
+ predict: predict;
62
+ submit: (
63
+ endpoint: string | number,
64
+ data: unknown[],
65
+ event_data?: unknown,
66
+ trigger_id?: number | null
67
+ ) => SubmitReturn;
68
+ component_server: (
69
+ component_id: number,
70
+ fn_name: string,
71
+ data: unknown[]
72
+ ) => any;
73
+ view_api: (_fetch: typeof fetch) => Promise<ApiInfo<JsApiData>>;
74
+ };
75
+
76
+ export type SubmitReturn = {
77
+ on: event;
78
+ off: event;
79
+ cancel: () => Promise<void>;
80
+ destroy: () => void;
81
+ };
82
+
83
+ // Space Status Types
84
+
85
+ export type SpaceStatus = SpaceStatusNormal | SpaceStatusError;
86
+
87
+ export interface SpaceStatusNormal {
88
+ status: "sleeping" | "running" | "building" | "error" | "stopped";
89
+ detail:
90
+ | "SLEEPING"
91
+ | "RUNNING"
92
+ | "RUNNING_BUILDING"
93
+ | "BUILDING"
94
+ | "NOT_FOUND";
95
+ load_status: "pending" | "error" | "complete" | "generating";
96
+ message: string;
97
+ }
98
+
99
+ export interface SpaceStatusError {
100
+ status: "space_error" | "paused";
101
+ detail:
102
+ | "NO_APP_FILE"
103
+ | "CONFIG_ERROR"
104
+ | "BUILD_ERROR"
105
+ | "RUNTIME_ERROR"
106
+ | "PAUSED";
107
+ load_status: "error";
108
+ message: string;
109
+ discussions_enabled: boolean;
110
+ }
111
+
112
+ export type SpaceStatusCallback = (a: SpaceStatus) => void;
113
+
114
+ // Configuration and Response Types
115
+ // --------------------------------
1
116
  export interface Config {
2
- auth_required: boolean | undefined;
117
+ auth_required: boolean;
118
+ analytics_enabled: boolean;
119
+ connect_heartbeat: boolean;
3
120
  auth_message: string;
4
121
  components: any[];
5
122
  css: string | null;
6
123
  js: string | null;
7
124
  head: string | null;
8
- dependencies: any[];
125
+ dependencies: Dependency[];
9
126
  dev_mode: boolean;
10
127
  enable_queue: boolean;
128
+ show_error: boolean;
11
129
  layout: any;
12
130
  mode: "blocks" | "interface";
13
131
  root: string;
@@ -16,81 +134,91 @@ export interface Config {
16
134
  title: string;
17
135
  version: string;
18
136
  space_id: string | null;
137
+ is_space: boolean;
19
138
  is_colab: boolean;
20
139
  show_api: boolean;
21
140
  stylesheets: string[];
22
141
  path: string;
23
- protocol?: "sse_v2.1" | "sse_v2" | "sse_v1" | "sse" | "ws";
142
+ protocol: "sse_v3" | "sse_v2.1" | "sse_v2" | "sse_v1" | "sse" | "ws";
143
+ max_file_size?: number;
144
+ }
145
+
146
+ export interface Dependency {
147
+ targets: [number, string][];
148
+ inputs: number[];
149
+ outputs: number[];
150
+ backend_fn: boolean;
151
+ js: string | null;
152
+ scroll_to_output: boolean;
153
+ trigger: "click" | "load" | string;
154
+ max_batch_size: number;
155
+ show_progress: "full" | "minimal" | "hidden";
156
+ frontend_fn: ((...args: unknown[]) => Promise<unknown[]>) | null;
157
+ status?: string;
158
+ queue: boolean | null;
159
+ every: number | null;
160
+ batch: boolean;
161
+ api_name: string | null;
162
+ cancels: number[];
163
+ types: DependencyTypes;
164
+ collects_event_data: boolean;
165
+ pending_request?: boolean;
166
+ trigger_after?: number;
167
+ trigger_only_on_success?: boolean;
168
+ trigger_mode: "once" | "multiple" | "always_last";
169
+ final_event: Payload | null;
170
+ show_api: boolean;
171
+ zerogpu?: boolean;
172
+ }
173
+
174
+ export interface DependencyTypes {
175
+ continuous: boolean;
176
+ generator: boolean;
24
177
  }
25
178
 
26
179
  export interface Payload {
180
+ fn_index: number;
27
181
  data: unknown[];
28
- fn_index?: number;
29
- event_data?: unknown;
30
182
  time?: Date;
183
+ event_data?: unknown;
184
+ trigger_id?: number | null;
31
185
  }
32
186
 
33
187
  export interface PostResponse {
34
188
  error?: string;
35
189
  [x: string]: any;
36
190
  }
191
+
37
192
  export interface UploadResponse {
38
193
  error?: string;
39
194
  files?: string[];
40
195
  }
41
196
 
42
- export interface Status {
43
- queue: boolean;
44
- code?: string;
45
- success?: boolean;
46
- stage: "pending" | "error" | "complete" | "generating";
47
- broken?: boolean;
48
- size?: number;
49
- position?: number;
50
- eta?: number;
51
- message?: string;
52
- progress_data?: {
53
- progress: number | null;
54
- index: number | null;
55
- length: number | null;
56
- unit: string | null;
57
- desc: string | null;
58
- }[];
59
- time?: Date;
60
- }
197
+ // Client and File Handling Types
61
198
 
62
- export interface LogMessage {
63
- log: string;
64
- level: "warning" | "info";
199
+ export interface DuplicateOptions extends ClientOptions {
200
+ private?: boolean;
201
+ hardware?: (typeof hardware_types)[number];
202
+ timeout?: number;
65
203
  }
66
204
 
67
- export interface SpaceStatusNormal {
68
- status: "sleeping" | "running" | "building" | "error" | "stopped";
69
- detail:
70
- | "SLEEPING"
71
- | "RUNNING"
72
- | "RUNNING_BUILDING"
73
- | "BUILDING"
74
- | "NOT_FOUND";
75
- load_status: "pending" | "error" | "complete" | "generating";
76
- message: string;
205
+ export interface ClientOptions {
206
+ hf_token?: `hf_${string}`;
207
+ status_callback?: SpaceStatusCallback | null;
77
208
  }
78
- export interface SpaceStatusError {
79
- status: "space_error" | "paused";
80
- detail:
81
- | "NO_APP_FILE"
82
- | "CONFIG_ERROR"
83
- | "BUILD_ERROR"
84
- | "RUNTIME_ERROR"
85
- | "PAUSED";
86
- load_status: "error";
87
- message: string;
88
- discussions_enabled: boolean;
209
+
210
+ export interface FileData {
211
+ name: string;
212
+ orig_name?: string;
213
+ size?: number;
214
+ data: string;
215
+ blob?: File;
216
+ is_file?: boolean;
217
+ mime_type?: string;
218
+ alt_text?: string;
89
219
  }
90
- export type SpaceStatus = SpaceStatusNormal | SpaceStatusError;
91
220
 
92
- export type status_callback_function = (a: Status) => void;
93
- export type SpaceStatusCallback = (a: SpaceStatus) => void;
221
+ // Event and Listener Types
94
222
 
95
223
  export type EventType = "data" | "status" | "log";
96
224
 
@@ -107,13 +235,27 @@ export type EventListener<K extends EventType> = (event: Event<K>) => void;
107
235
  export type ListenerMap<K extends EventType> = {
108
236
  [P in K]?: EventListener<K>[];
109
237
  };
110
- export interface FileData {
111
- name: string;
112
- orig_name?: string;
238
+ export interface LogMessage {
239
+ log: string;
240
+ level: "warning" | "info";
241
+ }
242
+
243
+ export interface Status {
244
+ queue: boolean;
245
+ code?: string;
246
+ success?: boolean;
247
+ stage: "pending" | "error" | "complete" | "generating";
248
+ broken?: boolean;
113
249
  size?: number;
114
- data: string;
115
- blob?: File;
116
- is_file?: boolean;
117
- mime_type?: string;
118
- alt_text?: string;
250
+ position?: number;
251
+ eta?: number;
252
+ message?: string;
253
+ progress_data?: {
254
+ progress: number | null;
255
+ index: number | null;
256
+ length: number | null;
257
+ unit: string | null;
258
+ desc: string | null;
259
+ }[];
260
+ time?: Date;
119
261
  }
package/src/upload.ts CHANGED
@@ -1,26 +1,30 @@
1
- import { upload_files } from "./client";
2
-
3
- function is_url(str: string): boolean {
4
- try {
5
- const url = new URL(str);
6
- return url.protocol === "http:" || url.protocol === "https:";
7
- } catch {
8
- return false;
9
- }
10
- }
1
+ import type { UploadResponse } from "./types";
2
+ import type { Client } from "./client";
11
3
 
12
4
  export async function upload(
5
+ this: Client,
13
6
  file_data: FileData[],
14
- root: string,
7
+ root_url: string,
15
8
  upload_id?: string,
16
- upload_fn: typeof upload_files = upload_files
9
+ max_file_size?: number
17
10
  ): Promise<(FileData | null)[] | null> {
18
11
  let files = (Array.isArray(file_data) ? file_data : [file_data]).map(
19
12
  (file_data) => file_data.blob!
20
13
  );
21
14
 
15
+ const oversized_files = files.filter(
16
+ (f) => f.size > (max_file_size ?? Infinity)
17
+ );
18
+ if (oversized_files.length) {
19
+ throw new Error(
20
+ `File size exceeds the maximum allowed size of ${max_file_size} bytes: ${oversized_files
21
+ .map((f) => f.name)
22
+ .join(", ")}`
23
+ );
24
+ }
25
+
22
26
  return await Promise.all(
23
- await upload_fn(root, files, undefined, upload_id).then(
27
+ await this.upload_files(root_url, files, upload_id).then(
24
28
  async (response: { files?: string[]; error?: string }) => {
25
29
  if (response.error) {
26
30
  throw new Error(response.error);
@@ -30,7 +34,7 @@ export async function upload(
30
34
  const file = new FileData({
31
35
  ...file_data[i],
32
36
  path: f,
33
- url: root + "/file=" + f
37
+ url: root_url + "/file=" + f
34
38
  });
35
39
  return file;
36
40
  });
@@ -48,7 +52,7 @@ export async function prepare_files(
48
52
  is_stream?: boolean
49
53
  ): Promise<FileData[]> {
50
54
  return files.map(
51
- (f, i) =>
55
+ (f) =>
52
56
  new FileData({
53
57
  path: f.name,
54
58
  orig_name: f.name,
@@ -0,0 +1,104 @@
1
+ import {
2
+ get_space_hardware,
3
+ hardware_types,
4
+ set_space_timeout
5
+ } from "../helpers/spaces";
6
+ import type { DuplicateOptions } from "../types";
7
+ import { Client } from "../client";
8
+ import { SPACE_METADATA_ERROR_MSG } from "../constants";
9
+
10
+ export async function duplicate(
11
+ app_reference: string,
12
+ options: DuplicateOptions
13
+ ): Promise<Client> {
14
+ const { hf_token, private: _private, hardware, timeout } = options;
15
+
16
+ if (hardware && !hardware_types.includes(hardware)) {
17
+ throw new Error(
18
+ `Invalid hardware type provided. Valid types are: ${hardware_types
19
+ .map((v) => `"${v}"`)
20
+ .join(",")}.`
21
+ );
22
+ }
23
+ const headers = {
24
+ Authorization: `Bearer ${hf_token}`,
25
+ "Content-Type": "application/json"
26
+ };
27
+
28
+ const user = (
29
+ await (
30
+ await fetch(`https://huggingface.co/api/whoami-v2`, {
31
+ headers
32
+ })
33
+ ).json()
34
+ ).name;
35
+
36
+ const space_name = app_reference.split("/")[1];
37
+ const body: {
38
+ repository: string;
39
+ private?: boolean;
40
+ hardware?: string;
41
+ } = {
42
+ repository: `${user}/${space_name}`
43
+ };
44
+
45
+ if (_private) {
46
+ body.private = true;
47
+ }
48
+
49
+ let original_hardware;
50
+
51
+ try {
52
+ if (!hardware) {
53
+ original_hardware = await get_space_hardware(app_reference, hf_token);
54
+ }
55
+ } catch (e) {
56
+ throw Error(SPACE_METADATA_ERROR_MSG + (e as Error).message);
57
+ }
58
+
59
+ const requested_hardware = hardware || original_hardware || "cpu-basic";
60
+
61
+ body.hardware = requested_hardware;
62
+
63
+ try {
64
+ const response = await fetch(
65
+ `https://huggingface.co/api/spaces/${app_reference}/duplicate`,
66
+ {
67
+ method: "POST",
68
+ headers,
69
+ body: JSON.stringify(body)
70
+ }
71
+ );
72
+
73
+ if (response.status === 409) {
74
+ try {
75
+ const client = await Client.connect(`${user}/${space_name}`, options);
76
+ return client;
77
+ } catch (error) {
78
+ console.error("Failed to connect Client instance:", error);
79
+ throw error;
80
+ }
81
+ } else if (response.status !== 200) {
82
+ throw new Error(response.statusText);
83
+ }
84
+
85
+ const duplicated_space = await response.json();
86
+
87
+ await set_space_timeout(`${user}/${space_name}`, timeout || 300, hf_token);
88
+
89
+ return await Client.connect(
90
+ get_space_reference(duplicated_space.url),
91
+ options
92
+ );
93
+ } catch (e: any) {
94
+ throw new Error(e);
95
+ }
96
+ }
97
+
98
+ function get_space_reference(url: string): any {
99
+ const regex = /https:\/\/huggingface.co\/spaces\/([^/]+\/[^/]+)/;
100
+ const match = url.match(regex);
101
+ if (match) {
102
+ return match[1];
103
+ }
104
+ }
@@ -0,0 +1,47 @@
1
+ import { update_object, walk_and_store_blobs } from "../helpers/data";
2
+ import type { ApiData, EndpointInfo, JsApiData } from "../types";
3
+ import { FileData } from "../upload";
4
+ import type { Client } from "..";
5
+
6
+ export async function handle_blob(
7
+ this: Client,
8
+ endpoint: string,
9
+ data: unknown[],
10
+ api_info: EndpointInfo<JsApiData | ApiData>
11
+ ): Promise<unknown[]> {
12
+ const self = this;
13
+
14
+ const blobRefs = await walk_and_store_blobs(
15
+ data,
16
+ undefined,
17
+ [],
18
+ true,
19
+ api_info
20
+ );
21
+
22
+ const results = await Promise.all(
23
+ blobRefs.map(async ({ path, blob, type }) => {
24
+ if (!blob) return { path, type };
25
+
26
+ const response = await self.upload_files(endpoint, [blob]);
27
+ const file_url = response.files && response.files[0];
28
+ return {
29
+ path,
30
+ file_url,
31
+ type,
32
+ name: blob instanceof File ? blob?.name : undefined
33
+ };
34
+ })
35
+ );
36
+
37
+ results.forEach(({ path, file_url, type, name }) => {
38
+ if (type === "Gallery") {
39
+ update_object(data, file_url, path);
40
+ } else if (file_url) {
41
+ const file = new FileData({ path: file_url, orig_name: name });
42
+ update_object(data, file, path);
43
+ }
44
+ });
45
+
46
+ return data;
47
+ }
@@ -0,0 +1,37 @@
1
+ import { BROKEN_CONNECTION_MSG } from "../constants";
2
+ import type { PostResponse } from "../types";
3
+ import { Client } from "..";
4
+
5
+ export async function post_data(
6
+ this: Client,
7
+ url: string,
8
+ body: unknown,
9
+ additional_headers?: any
10
+ ): Promise<[PostResponse, number]> {
11
+ const headers: {
12
+ Authorization?: string;
13
+ "Content-Type": "application/json";
14
+ } = { "Content-Type": "application/json" };
15
+ if (this.options.hf_token) {
16
+ headers.Authorization = `Bearer ${this.options.hf_token}`;
17
+ }
18
+ try {
19
+ var response = await this.fetch(url, {
20
+ method: "POST",
21
+ body: JSON.stringify(body),
22
+ headers: { ...headers, ...additional_headers }
23
+ });
24
+ } catch (e) {
25
+ return [{ error: BROKEN_CONNECTION_MSG }, 500];
26
+ }
27
+ let output: PostResponse;
28
+ let status: number;
29
+ try {
30
+ output = await response.json();
31
+ status = response.status;
32
+ } catch (e) {
33
+ output = { error: `Could not parse server response: ${e}` };
34
+ status = 500;
35
+ }
36
+ return [output, status];
37
+ }
@@ -0,0 +1,56 @@
1
+ import { Client } from "../client";
2
+ import type { Dependency, SubmitReturn } from "../types";
3
+
4
+ export async function predict(
5
+ this: Client,
6
+ endpoint: string | number,
7
+ data?: unknown[]
8
+ ): Promise<SubmitReturn> {
9
+ let data_returned = false;
10
+ let status_complete = false;
11
+ let dependency: Dependency;
12
+
13
+ if (!this.config) {
14
+ throw new Error("Could not resolve app config");
15
+ }
16
+
17
+ if (typeof endpoint === "number") {
18
+ dependency = this.config.dependencies[endpoint];
19
+ } else {
20
+ const trimmed_endpoint = endpoint.replace(/^\//, "");
21
+ dependency = this.config.dependencies[this.api_map[trimmed_endpoint]];
22
+ }
23
+
24
+ if (dependency?.types.continuous) {
25
+ throw new Error(
26
+ "Cannot call predict on this function as it may run forever. Use submit instead"
27
+ );
28
+ }
29
+
30
+ return new Promise(async (resolve, reject) => {
31
+ const app = this.submit(endpoint, data || []);
32
+ let result: unknown;
33
+
34
+ app
35
+ .on("data", (d: unknown) => {
36
+ // if complete message comes before data, resolve here
37
+ if (status_complete) {
38
+ app.destroy();
39
+ resolve(d as SubmitReturn);
40
+ }
41
+ data_returned = true;
42
+ result = d;
43
+ })
44
+ .on("status", (status) => {
45
+ if (status.stage === "error") reject(status);
46
+ if (status.stage === "complete") {
47
+ status_complete = true;
48
+ // if complete message comes after data, resolve here
49
+ if (data_returned) {
50
+ app.destroy();
51
+ resolve(result as SubmitReturn);
52
+ }
53
+ }
54
+ });
55
+ });
56
+ }