@allwright.dev/core 0.1.7 → 0.1.9

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
@@ -47,6 +47,10 @@ const download = await hook.wait();
47
47
  await download.saveAs(`artifacts/${download.suggestedFilename}`);
48
48
  ```
49
49
 
50
+ Paths passed to `setFiles` and `saveAs` belong to the test process, even when
51
+ the Allwright server is remote. Android apps support the same `fileChooser` and
52
+ `download` hook shape; Android `newPage` hooks are not applicable.
53
+
50
54
  Runnable examples live in [examples/web-basic.ts](./examples/web-basic.ts) and [examples/android-basic.ts](./examples/android-basic.ts).
51
55
 
52
56
  Shared config files are stack-agnostic and can live in `allwright.config.yaml` or `allwright.config.json`.
package/dist/bootstrap.js CHANGED
@@ -12,7 +12,7 @@ const ALLWRIGHT_HOME_ENV_VAR = "ALLWRIGHT_HOME";
12
12
  const ALLWRIGHT_REPOSITORY_ENV_VAR = "ALLWRIGHT_REPOSITORY";
13
13
  const ALLWRIGHT_VERSION_ENV_VAR = "ALLWRIGHT_VERSION";
14
14
  const DEFAULT_RELEASE_REPOSITORY = "allwright-dev/allwright";
15
- const DEFAULT_RELEASE_VERSION = "0.1.7";
15
+ const DEFAULT_RELEASE_VERSION = "0.1.9";
16
16
  const STARTUP_TIMEOUT_MS = 20_000;
17
17
  const PING_TIMEOUT_MS = 1_000;
18
18
  const PROTO_ROOT = fileURLToPath(new URL("../proto/", import.meta.url));
package/dist/mobile.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { createBrowserSessionHandle, createPageHandle, getRuntime } from "./runtime.js";
2
- import { writeFile } from "node:fs/promises";
2
+ import { open, rename, unlink, writeFile } from "node:fs/promises";
3
+ import { randomUUID } from "node:crypto";
4
+ import { basename } from "node:path";
3
5
  import { chainMobileSelectorForTransport, normalizeMobileSelectorForTransport } from "./mobileSelectors.js";
4
6
  function retryOptions(timeoutMs) {
5
7
  return timeoutMs ? { timeoutMs } : undefined;
@@ -17,6 +19,164 @@ class MobileAndroidAppImpl {
17
19
  locator(selector) {
18
20
  return new MobileAndroidLocatorImpl(this, normalizeMobileSelectorForTransport(selector));
19
21
  }
22
+ async registerHook(type) {
23
+ if (type.name !== "fileChooser" && type.name !== "download") {
24
+ throw new Error(`hook type ${type.name} is not supported by Android apps`);
25
+ }
26
+ const handle = await this.#getHandle();
27
+ this.#ensureOpen(handle);
28
+ handle.stream.write({
29
+ surfaceSessionId: this.#surfaceSessionId,
30
+ contextSessionId: this.sessionId,
31
+ registerHook: type.name === "fileChooser"
32
+ ? { mobileFileChooser: {} }
33
+ : { mobileDownload: {} },
34
+ });
35
+ while (true) {
36
+ const event = await handle.queue.next();
37
+ if (event.hookRegistered?.hookId) {
38
+ const hookId = event.hookRegistered.hookId;
39
+ return {
40
+ id: hookId,
41
+ type,
42
+ wait: (options = {}) => this.#waitForHook(hookId, type, options),
43
+ };
44
+ }
45
+ if (event.error?.message)
46
+ throw new Error(event.error.message);
47
+ }
48
+ }
49
+ async #waitForHook(hookId, type, options) {
50
+ const handle = await this.#getHandle();
51
+ this.#ensureOpen(handle);
52
+ handle.stream.write({
53
+ surfaceSessionId: this.#surfaceSessionId,
54
+ contextSessionId: this.sessionId,
55
+ waitForHook: { hookId, retryOptions: retryOptions(options.timeoutMs) },
56
+ });
57
+ while (true) {
58
+ const event = await handle.queue.next();
59
+ if (event.hookCompleted?.hookId === hookId) {
60
+ if (type.name === "fileChooser" && event.hookCompleted.mobileFileChooser?.fileChooserId) {
61
+ return new MobileFileChooserImpl(this, event.hookCompleted.mobileFileChooser.fileChooserId, event.hookCompleted.mobileFileChooser.isMultiple ?? false);
62
+ }
63
+ if (type.name === "download" && event.hookCompleted.mobileDownload?.downloadId) {
64
+ return new MobileDownloadImpl(this, event.hookCompleted.mobileDownload.downloadId, event.hookCompleted.mobileDownload.suggestedFilename ?? "download");
65
+ }
66
+ throw new Error(`Android ${type.name} hook returned an invalid result`);
67
+ }
68
+ if (event.error?.message)
69
+ throw new Error(event.error.message);
70
+ }
71
+ }
72
+ async setChooserFiles(fileChooserId, files, options) {
73
+ const handle = await this.#getHandle();
74
+ this.#ensureOpen(handle);
75
+ const fileIds = [];
76
+ for (const file of files)
77
+ fileIds.push(await this.#uploadLocalFile(handle, file));
78
+ handle.stream.write({
79
+ surfaceSessionId: this.#surfaceSessionId,
80
+ contextSessionId: this.sessionId,
81
+ setMobileFileChooserFiles: {
82
+ fileChooserId,
83
+ fileIds,
84
+ retryOptions: retryOptions(options.timeoutMs),
85
+ },
86
+ });
87
+ while (true) {
88
+ const event = await handle.queue.next();
89
+ if (event.mobileFileChooserFilesSet?.fileChooserId === fileChooserId)
90
+ return;
91
+ if (event.error?.message)
92
+ throw new Error(event.error.message);
93
+ }
94
+ }
95
+ async saveMobileDownload(downloadId, path, options) {
96
+ const handle = await this.#getHandle();
97
+ this.#ensureOpen(handle);
98
+ handle.stream.write({
99
+ surfaceSessionId: this.#surfaceSessionId,
100
+ contextSessionId: this.sessionId,
101
+ saveMobileDownload: { downloadId, retryOptions: retryOptions(options.timeoutMs) },
102
+ });
103
+ while (true) {
104
+ const event = await handle.queue.next();
105
+ if (event.mobileDownloadSaved?.downloadId === downloadId && event.mobileDownloadSaved.fileId) {
106
+ await this.#downloadToLocalFile(handle, event.mobileDownloadSaved.fileId, path);
107
+ return;
108
+ }
109
+ if (event.error?.message)
110
+ throw new Error(event.error.message);
111
+ }
112
+ }
113
+ async #uploadLocalFile(handle, path) {
114
+ const source = await open(path, "r");
115
+ const transferId = randomUUID();
116
+ let offset = 0;
117
+ try {
118
+ const size = (await source.stat()).size;
119
+ do {
120
+ const buffer = Buffer.alloc(Math.min(256 * 1024, Math.max(0, size - offset)) || 1);
121
+ const { bytesRead } = await source.read(buffer, 0, buffer.length, offset);
122
+ const last = offset + bytesRead >= size;
123
+ handle.stream.write({
124
+ surfaceSessionId: this.#surfaceSessionId,
125
+ contextSessionId: this.sessionId,
126
+ uploadFileChunk: {
127
+ transferId,
128
+ name: basename(path),
129
+ offset: String(offset),
130
+ data: buffer.subarray(0, bytesRead),
131
+ last,
132
+ },
133
+ });
134
+ offset += bytesRead;
135
+ } while (offset < size);
136
+ }
137
+ finally {
138
+ await source.close();
139
+ }
140
+ while (true) {
141
+ const event = await handle.queue.next();
142
+ if (event.fileUploaded?.transferId === transferId && event.fileUploaded.fileId) {
143
+ return event.fileUploaded.fileId;
144
+ }
145
+ if (event.error?.message)
146
+ throw new Error(event.error.message);
147
+ }
148
+ }
149
+ async #downloadToLocalFile(handle, fileId, path) {
150
+ const temporaryPath = `${path}.allwright-${randomUUID()}.tmp`;
151
+ const destination = await open(temporaryPath, "wx");
152
+ let offset = 0;
153
+ try {
154
+ while (true) {
155
+ handle.stream.write({
156
+ surfaceSessionId: this.#surfaceSessionId,
157
+ contextSessionId: this.sessionId,
158
+ readFileChunk: { fileId, offset: String(offset), maxBytes: 256 * 1024 },
159
+ });
160
+ const event = await handle.queue.next();
161
+ if (event.error?.message)
162
+ throw new Error(event.error.message);
163
+ if (event.fileChunk?.fileId !== fileId || Number(event.fileChunk.offset ?? -1) !== offset)
164
+ continue;
165
+ const data = event.fileChunk.data ?? new Uint8Array();
166
+ await destination.write(data, 0, data.length, offset);
167
+ offset += data.length;
168
+ if (event.fileChunk.last)
169
+ break;
170
+ }
171
+ await destination.close();
172
+ await rename(temporaryPath, path);
173
+ }
174
+ catch (error) {
175
+ await destination.close().catch(() => undefined);
176
+ await unlink(temporaryPath).catch(() => undefined);
177
+ throw error;
178
+ }
179
+ }
20
180
  async click(selector, options = {}) {
21
181
  const handle = await this.#getHandle();
22
182
  this.#ensureOpen(handle);
@@ -322,6 +482,40 @@ class MobileAndroidAppImpl {
322
482
  }
323
483
  }
324
484
  }
485
+ class MobileFileChooserImpl {
486
+ page;
487
+ id;
488
+ multiple;
489
+ constructor(page, id, multiple) {
490
+ this.page = page;
491
+ this.id = id;
492
+ this.multiple = multiple;
493
+ }
494
+ isMultiple() {
495
+ return this.multiple;
496
+ }
497
+ async setFiles(files, options = {}) {
498
+ const paths = typeof files === "string" ? [files] : files;
499
+ if (!this.multiple && paths.length > 1) {
500
+ throw new Error("Android file chooser does not accept multiple files");
501
+ }
502
+ await this.page.setChooserFiles(this.id, paths, options);
503
+ }
504
+ }
505
+ class MobileDownloadImpl {
506
+ page;
507
+ id;
508
+ suggestedFilename;
509
+ url = "";
510
+ constructor(page, id, suggestedFilename) {
511
+ this.page = page;
512
+ this.id = id;
513
+ this.suggestedFilename = suggestedFilename;
514
+ }
515
+ async saveAs(path, options = {}) {
516
+ await this.page.saveMobileDownload(this.id, path, options);
517
+ }
518
+ }
325
519
  class MobileAndroidLocatorImpl {
326
520
  page;
327
521
  selector;
package/dist/page.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { WebLocatorBuilders } from "./web-locators.js";
2
2
  import { LocatorImpl } from "./locator.js";
3
- import { writeFile } from "node:fs/promises";
3
+ import { open, rename, unlink, writeFile } from "node:fs/promises";
4
+ import { randomUUID } from "node:crypto";
5
+ import { basename } from "node:path";
4
6
  import { formatActionError } from "./errors.js";
5
7
  import { normalizeSelectorForTransport } from "./selectors.js";
6
8
  import { createPageHandle } from "./runtime.js";
@@ -85,12 +87,16 @@ export class PageImpl extends WebLocatorBuilders {
85
87
  async setFileChooserFiles(fileChooserId, files, options = {}) {
86
88
  const handle = await this.#getHandle();
87
89
  this.#ensureOpen(handle);
90
+ const fileIds = [];
91
+ for (const file of files) {
92
+ fileIds.push(await this.#uploadLocalFile(handle, file));
93
+ }
88
94
  handle.stream.write({
89
95
  surfaceSessionId: this.browserSessionId,
90
96
  contextSessionId: this.sessionId,
91
97
  setFileChooserFiles: {
92
98
  fileChooserId,
93
- files,
99
+ fileIds,
94
100
  retryOptions: options.timeoutMs ? { timeoutMs: options.timeoutMs } : undefined,
95
101
  },
96
102
  });
@@ -112,13 +118,13 @@ export class PageImpl extends WebLocatorBuilders {
112
118
  contextSessionId: this.sessionId,
113
119
  saveDownload: {
114
120
  downloadId,
115
- path,
116
121
  retryOptions: options.timeoutMs ? { timeoutMs: options.timeoutMs } : undefined,
117
122
  },
118
123
  });
119
124
  while (true) {
120
125
  const event = await handle.queue.next();
121
- if (event.downloadSaved?.downloadId === downloadId) {
126
+ if (event.downloadSaved?.downloadId === downloadId && event.downloadSaved.fileId) {
127
+ await this.#downloadToLocalFile(handle, event.downloadSaved.fileId, path);
122
128
  return;
123
129
  }
124
130
  if (event.error?.message) {
@@ -126,6 +132,77 @@ export class PageImpl extends WebLocatorBuilders {
126
132
  }
127
133
  }
128
134
  }
135
+ async #uploadLocalFile(handle, path) {
136
+ const source = await open(path, "r");
137
+ const transferId = randomUUID();
138
+ const name = basename(path);
139
+ let offset = 0;
140
+ try {
141
+ const size = (await source.stat()).size;
142
+ do {
143
+ const buffer = Buffer.alloc(Math.min(256 * 1024, Math.max(0, size - offset)) || 1);
144
+ const { bytesRead } = await source.read(buffer, 0, buffer.length, offset);
145
+ const last = offset + bytesRead >= size;
146
+ handle.stream.write({
147
+ surfaceSessionId: this.browserSessionId,
148
+ contextSessionId: this.sessionId,
149
+ uploadFileChunk: {
150
+ transferId,
151
+ name,
152
+ offset: String(offset),
153
+ data: buffer.subarray(0, bytesRead),
154
+ last,
155
+ },
156
+ });
157
+ offset += bytesRead;
158
+ } while (offset < size);
159
+ }
160
+ finally {
161
+ await source.close();
162
+ }
163
+ while (true) {
164
+ const event = await handle.queue.next();
165
+ if (event.fileUploaded?.transferId === transferId && event.fileUploaded.fileId) {
166
+ return event.fileUploaded.fileId;
167
+ }
168
+ if (event.error?.message) {
169
+ throw formatActionError("upload file", event.error.message);
170
+ }
171
+ }
172
+ }
173
+ async #downloadToLocalFile(handle, fileId, path) {
174
+ const temporaryPath = `${path}.allwright-${randomUUID()}.tmp`;
175
+ const destination = await open(temporaryPath, "wx");
176
+ let offset = 0;
177
+ try {
178
+ while (true) {
179
+ handle.stream.write({
180
+ surfaceSessionId: this.browserSessionId,
181
+ contextSessionId: this.sessionId,
182
+ readFileChunk: { fileId, offset: String(offset), maxBytes: 256 * 1024 },
183
+ });
184
+ const event = await handle.queue.next();
185
+ if (event.error?.message) {
186
+ throw formatActionError("download file", event.error.message);
187
+ }
188
+ if (event.fileChunk?.fileId !== fileId || Number(event.fileChunk.offset ?? -1) !== offset) {
189
+ continue;
190
+ }
191
+ const data = event.fileChunk.data ?? new Uint8Array();
192
+ await destination.write(data, 0, data.length, offset);
193
+ offset += data.length;
194
+ if (event.fileChunk.last)
195
+ break;
196
+ }
197
+ await destination.close();
198
+ await rename(temporaryPath, path);
199
+ }
200
+ catch (error) {
201
+ await destination.close().catch(() => undefined);
202
+ await unlink(temporaryPath).catch(() => undefined);
203
+ throw error;
204
+ }
205
+ }
129
206
  locator(selector, options) {
130
207
  const result = new LocatorImpl({ page: this, selector: normalizeSelectorForTransport(selector) });
131
208
  return options ? result.filter(options) : result;
package/dist/types.d.ts CHANGED
@@ -110,13 +110,13 @@ export interface Hook<T> {
110
110
  }
111
111
  export interface FileChooser {
112
112
  readonly id: string;
113
- readonly page: Page;
113
+ readonly page: Page | MobileAndroidApp;
114
114
  isMultiple(): boolean;
115
115
  setFiles(files: string | string[], options?: CommandOptions): Promise<void>;
116
116
  }
117
117
  export interface Download {
118
118
  readonly id: string;
119
- readonly page: Page;
119
+ readonly page: Page | MobileAndroidApp;
120
120
  readonly url: string;
121
121
  readonly suggestedFilename: string;
122
122
  saveAs(path: string, options?: CommandOptions): Promise<void>;
@@ -192,6 +192,7 @@ export interface MobileAndroidApp {
192
192
  innerText(selector: string, options?: CommandOptions): Promise<TextResult>;
193
193
  waitForSelector(selector: string, options?: WaitForSelectorOptions): Promise<WaitForSelectorResult>;
194
194
  screenshot(options?: ScreenshotOptions): Promise<ScreenshotResult>;
195
+ registerHook<T>(type: HookType<T>): Promise<Hook<T>>;
195
196
  }
196
197
  export interface MobileAndroidDevice {
197
198
  readonly sessionId: string;
@@ -411,15 +412,51 @@ export interface ContextSessionEvent {
411
412
  suggestedFilename?: string;
412
413
  note?: string;
413
414
  };
415
+ mobileFileChooser?: {
416
+ fileChooserId?: string;
417
+ isMultiple?: boolean;
418
+ note?: string;
419
+ };
420
+ mobileDownload?: {
421
+ downloadId?: string;
422
+ suggestedFilename?: string;
423
+ note?: string;
424
+ };
414
425
  };
415
426
  fileChooserFilesSet?: {
416
427
  fileChooserId?: string;
417
- files?: string[];
428
+ fileIds?: string[];
418
429
  note?: string;
419
430
  };
420
431
  downloadSaved?: {
421
432
  downloadId?: string;
422
- path?: string;
433
+ fileId?: string;
434
+ suggestedFilename?: string;
435
+ size?: string;
436
+ note?: string;
437
+ };
438
+ fileUploaded?: {
439
+ transferId?: string;
440
+ fileId?: string;
441
+ name?: string;
442
+ size?: string;
443
+ };
444
+ fileChunk?: {
445
+ fileId?: string;
446
+ offset?: string;
447
+ data?: Uint8Array;
448
+ last?: boolean;
449
+ };
450
+ mobileFileChooserFilesSet?: {
451
+ fileChooserId?: string;
452
+ fileIds?: string[];
453
+ note?: string;
454
+ };
455
+ mobileDownloadSaved?: {
456
+ downloadId?: string;
457
+ fileId?: string;
458
+ suggestedFilename?: string;
459
+ size?: string;
423
460
  note?: string;
424
461
  };
425
462
  navigated?: {
@@ -517,6 +554,8 @@ export interface RegisterHookRequest {
517
554
  newPage?: Record<string, never>;
518
555
  fileChooser?: Record<string, never>;
519
556
  download?: Record<string, never>;
557
+ mobileFileChooser?: Record<string, never>;
558
+ mobileDownload?: Record<string, never>;
520
559
  };
521
560
  }
522
561
  export interface SaveDownloadRequest {
@@ -524,7 +563,6 @@ export interface SaveDownloadRequest {
524
563
  contextSessionId: string;
525
564
  saveDownload: {
526
565
  downloadId: string;
527
- path: string;
528
566
  retryOptions?: {
529
567
  timeoutMs?: number;
530
568
  };
@@ -535,7 +573,48 @@ export interface SetFileChooserFilesRequest {
535
573
  contextSessionId: string;
536
574
  setFileChooserFiles: {
537
575
  fileChooserId: string;
538
- files: string[];
576
+ fileIds: string[];
577
+ retryOptions?: {
578
+ timeoutMs?: number;
579
+ };
580
+ };
581
+ }
582
+ export interface UploadFileChunkRequest {
583
+ surfaceSessionId: string;
584
+ contextSessionId: string;
585
+ uploadFileChunk: {
586
+ transferId: string;
587
+ name: string;
588
+ offset: string;
589
+ data: Uint8Array;
590
+ last: boolean;
591
+ };
592
+ }
593
+ export interface ReadFileChunkRequest {
594
+ surfaceSessionId: string;
595
+ contextSessionId: string;
596
+ readFileChunk: {
597
+ fileId: string;
598
+ offset: string;
599
+ maxBytes: number;
600
+ };
601
+ }
602
+ export interface SetMobileFileChooserFilesRequest {
603
+ surfaceSessionId: string;
604
+ contextSessionId: string;
605
+ setMobileFileChooserFiles: {
606
+ fileChooserId: string;
607
+ fileIds: string[];
608
+ retryOptions?: {
609
+ timeoutMs?: number;
610
+ };
611
+ };
612
+ }
613
+ export interface SaveMobileDownloadRequest {
614
+ surfaceSessionId: string;
615
+ contextSessionId: string;
616
+ saveMobileDownload: {
617
+ downloadId: string;
539
618
  retryOptions?: {
540
619
  timeoutMs?: number;
541
620
  };
@@ -730,7 +809,7 @@ export interface CloseContextRequest {
730
809
  close: Record<string, never>;
731
810
  }
732
811
  export type SurfaceSessionRequest = LaunchBrowserRequest | LaunchChromeRequest | OpenContextRequest | ConnectMobileRequest | LaunchAppRequest | SurfacePingRequest | CloseSurfaceRequest;
733
- export type ContextSessionRequest = ContextPingRequest | RegisterHookRequest | WaitForHookRequest | SetFileChooserFilesRequest | SaveDownloadRequest | NavigateRequest | ClickRequest | CountRequest | HighlightRequest | FocusRequest | FillRequest | HoverRequest | PressRequest | TextContentRequest | InnerTextRequest | WaitForSelectorRequest | AccessibilitySnapshotRequest | ScreenshotRequest | CloseContextRequest;
812
+ export type ContextSessionRequest = ContextPingRequest | RegisterHookRequest | WaitForHookRequest | SetFileChooserFilesRequest | SaveDownloadRequest | UploadFileChunkRequest | ReadFileChunkRequest | SetMobileFileChooserFilesRequest | SaveMobileDownloadRequest | NavigateRequest | ClickRequest | CountRequest | HighlightRequest | FocusRequest | FillRequest | HoverRequest | PressRequest | TextContentRequest | InnerTextRequest | WaitForSelectorRequest | AccessibilitySnapshotRequest | ScreenshotRequest | CloseContextRequest;
734
813
  export type SurfaceSessionStream = grpc.ClientDuplexStream<SurfaceSessionRequest, SurfaceSessionEvent>;
735
814
  export type ContextSessionStream = grpc.ClientDuplexStream<ContextSessionRequest, ContextSessionEvent>;
736
815
  export interface EngineServiceClientShape {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allwright.dev/core",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "High-level TypeScript client for the allwright automation engine.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -6,6 +6,7 @@ option java_package = "dev.allwright.engine.v1";
6
6
  option java_multiple_files = true;
7
7
 
8
8
  import "core/v1/common.proto";
9
+ import "surfaces/mobile/v1/mobile.proto";
9
10
  import "surfaces/web/v1/web.proto";
10
11
 
11
12
  message ContextSessionCommand {
@@ -32,6 +33,10 @@ message ContextSessionCommand {
32
33
  WaitForHookCommand wait_for_hook = 19;
33
34
  SetFileChooserFilesCommand set_file_chooser_files = 20;
34
35
  SaveDownloadCommand save_download = 21;
36
+ UploadFileChunkCommand upload_file_chunk = 22;
37
+ ReadFileChunkCommand read_file_chunk = 23;
38
+ SetMobileFileChooserFilesCommand set_mobile_file_chooser_files = 24;
39
+ SaveMobileDownloadCommand save_mobile_download = 25;
35
40
  }
36
41
  }
37
42
 
@@ -67,6 +72,10 @@ message ContextSessionEvent {
67
72
  HookCompletedEvent hook_completed = 21;
68
73
  FileChooserFilesSetEvent file_chooser_files_set = 22;
69
74
  DownloadSavedEvent download_saved = 23;
75
+ FileUploadedEvent file_uploaded = 24;
76
+ FileChunkEvent file_chunk = 25;
77
+ MobileFileChooserFilesSetEvent mobile_file_chooser_files_set = 26;
78
+ MobileDownloadSavedEvent mobile_download_saved = 27;
70
79
  }
71
80
  }
72
81
 
@@ -91,6 +100,8 @@ message RegisterHookCommand {
91
100
  RegisterNewPageHook new_page = 1;
92
101
  RegisterFileChooserHook file_chooser = 2;
93
102
  RegisterDownloadHook download = 3;
103
+ RegisterMobileFileChooserHook mobile_file_chooser = 4;
104
+ RegisterMobileDownloadHook mobile_download = 5;
94
105
  }
95
106
  }
96
107
 
@@ -110,9 +121,39 @@ message HookCompletedEvent {
110
121
  NewPageHookResult new_page = 2;
111
122
  FileChooserHookResult file_chooser = 3;
112
123
  DownloadHookResult download = 4;
124
+ MobileFileChooserHookResult mobile_file_chooser = 5;
125
+ MobileDownloadHookResult mobile_download = 6;
113
126
  }
114
127
  }
115
128
 
129
+ message UploadFileChunkCommand {
130
+ string transfer_id = 1;
131
+ string name = 2;
132
+ uint64 offset = 3;
133
+ bytes data = 4;
134
+ bool last = 5;
135
+ }
136
+
137
+ message FileUploadedEvent {
138
+ string transfer_id = 1;
139
+ string file_id = 2;
140
+ string name = 3;
141
+ uint64 size = 4;
142
+ }
143
+
144
+ message ReadFileChunkCommand {
145
+ string file_id = 1;
146
+ uint64 offset = 2;
147
+ uint32 max_bytes = 3;
148
+ }
149
+
150
+ message FileChunkEvent {
151
+ string file_id = 1;
152
+ uint64 offset = 2;
153
+ bytes data = 3;
154
+ bool last = 4;
155
+ }
156
+
116
157
  message NavigatePageCommand {
117
158
  string url = 1;
118
159
  optional CommandRetryOptions retry_options = 2;
@@ -56,3 +56,12 @@ message AppLaunchedEvent {
56
56
  optional string activity_name = 4;
57
57
  optional string webview_context = 5;
58
58
  }
59
+
60
+ message RegisterMobileFileChooserHook {}
61
+ message MobileFileChooserHookResult { string file_chooser_id = 1; bool is_multiple = 2; string note = 3; }
62
+ message SetMobileFileChooserFilesCommand { string file_chooser_id = 1; repeated string file_ids = 2; optional CommandRetryOptions retry_options = 3; }
63
+ message MobileFileChooserFilesSetEvent { string file_chooser_id = 1; repeated string file_ids = 2; string note = 3; }
64
+ message RegisterMobileDownloadHook {}
65
+ message MobileDownloadHookResult { string download_id = 1; string suggested_filename = 2; string note = 3; }
66
+ message SaveMobileDownloadCommand { string download_id = 1; optional CommandRetryOptions retry_options = 2; }
67
+ message MobileDownloadSavedEvent { string download_id = 1; string file_id = 2; string suggested_filename = 3; uint64 size = 4; string note = 5; }
@@ -57,13 +57,13 @@ message FileChooserHookResult {
57
57
 
58
58
  message SetFileChooserFilesCommand {
59
59
  string file_chooser_id = 1;
60
- repeated string files = 2;
60
+ repeated string file_ids = 2;
61
61
  optional CommandRetryOptions retry_options = 3;
62
62
  }
63
63
 
64
64
  message FileChooserFilesSetEvent {
65
65
  string file_chooser_id = 1;
66
- repeated string files = 2;
66
+ repeated string file_ids = 2;
67
67
  string note = 3;
68
68
  }
69
69
 
@@ -78,12 +78,13 @@ message DownloadHookResult {
78
78
 
79
79
  message SaveDownloadCommand {
80
80
  string download_id = 1;
81
- string path = 2;
82
- optional CommandRetryOptions retry_options = 3;
81
+ optional CommandRetryOptions retry_options = 2;
83
82
  }
84
83
 
85
84
  message DownloadSavedEvent {
86
85
  string download_id = 1;
87
- string path = 2;
88
- string note = 3;
86
+ string file_id = 2;
87
+ string suggested_filename = 3;
88
+ uint64 size = 4;
89
+ string note = 5;
89
90
  }