js-dos 8.1.0 → 8.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-dos",
3
- "version": "8.1.0",
3
+ "version": "8.1.1",
4
4
  "description": "Full-featured DOS player with multiple emulator backends",
5
5
  "type": "module",
6
6
  "keywords": [
package/src/i18n.ts CHANGED
@@ -4,6 +4,8 @@ import { State } from "./store";
4
4
 
5
5
  const translations: {[lang: string]: {[key: string]: string} } = {
6
6
  ru: {
7
+ ws_outdated: "Необходимо обновить DOS Браузер",
8
+ update: "Обновить",
7
9
  preloading_sockdrive: "Загрузка FAT",
8
10
  filter: "Фильтр",
9
11
  add: "Добавить",
@@ -75,6 +77,8 @@ const translations: {[lang: string]: {[key: string]: string} } = {
75
77
  fat_write: "FAT запись",
76
78
  },
77
79
  en: {
80
+ ws_outdated: "Must update DOS Browser App",
81
+ update: "Update",
78
82
  preloading_sockdrive: "Preloading FAT",
79
83
  filter: "Filter",
80
84
  add: "Add",
@@ -1,7 +1,7 @@
1
1
  import { LRUMap } from "./lru";
2
2
 
3
3
  export interface Cache {
4
- read(sector: number): Uint8Array | null;
4
+ read(sector: number, originReadMode: boolean): Uint8Array | null;
5
5
  write(sector: number, buffer: Uint8Array): boolean;
6
6
  create(origin: number, buffer: Uint8Array): void;
7
7
  getOrigin(sector: number): number;
@@ -19,12 +19,16 @@ export class BlockCache implements Cache {
19
19
  this.lru = new LRUMap(Math.floor(memoryLimit / (aheadRange * sectorSize)));
20
20
  }
21
21
 
22
- public read(sector: number): Uint8Array | null {
22
+ public read(sector: number, originReadMode: boolean): Uint8Array | null {
23
23
  const origin = this.getOrigin(sector);
24
24
  const cached = this.lru.get(origin) as Uint8Array;
25
25
  if (cached) {
26
- const offset = sector - origin;
27
- return cached.slice(offset * this.sectorSize, (offset + 1) * this.sectorSize);
26
+ if (originReadMode) {
27
+ return cached;
28
+ } else {
29
+ const offset = sector - origin;
30
+ return cached.slice(offset * this.sectorSize, (offset + 1) * this.sectorSize);
31
+ }
28
32
  }
29
33
  return null;
30
34
  }
@@ -50,84 +54,3 @@ export class BlockCache implements Cache {
50
54
  return this.lru.size * this.aheadRange * this.sectorSize;
51
55
  }
52
56
  }
53
-
54
- export class SimpleCache implements Cache {
55
- aheadRange: number;
56
- sectorSize: number;
57
- lru: LRUMap;
58
-
59
- constructor(sectorSize: number, aheadRange: number, memoryLimit: number) {
60
- this.aheadRange = aheadRange;
61
- this.sectorSize = sectorSize;
62
- this.lru = new LRUMap(Math.floor(memoryLimit / sectorSize));
63
- }
64
-
65
- public read(sector: number): Uint8Array | null {
66
- const cached = this.lru.get(sector) as Uint8Array;
67
- if (cached) {
68
- return cached;
69
- }
70
- return null;
71
- }
72
-
73
- public write(sector: number, buffer: Uint8Array) {
74
- this.lru.set(sector, buffer);
75
- return true;
76
- }
77
-
78
- public create(sector: number, buffer: Uint8Array) {
79
- for (let i = 0; i < this.aheadRange; ++i) {
80
- this.lru.set(sector + i, buffer.slice(i * this.sectorSize, (i + 1) * this.sectorSize));
81
- }
82
- }
83
-
84
- public getOrigin(sector: number) {
85
- return sector;
86
- }
87
-
88
- public memUsed() {
89
- return this.lru.size * this.sectorSize;
90
- }
91
- }
92
-
93
- export class BlockAndWriteCache implements Cache {
94
- aheadRange: number;
95
- sectorSize: number;
96
- blockCache: BlockCache;
97
- writeCache: SimpleCache;
98
-
99
- constructor(sectorSize: number, aheadRange: number, memoryLimit: number) {
100
- this.aheadRange = aheadRange;
101
- this.sectorSize = sectorSize;
102
- this.blockCache = new BlockCache(sectorSize, aheadRange, memoryLimit);
103
- this.writeCache = new SimpleCache(sectorSize, aheadRange, Math.floor(memoryLimit / 10));
104
- }
105
-
106
- read(sector: number) {
107
- return this.blockCache.read(sector) || this.writeCache.read(sector);
108
- }
109
-
110
- write(sector: number, buffer: Uint8Array) {
111
- if (!this.blockCache.write(sector, buffer)) {
112
- this.writeCache.write(sector, buffer);
113
- }
114
- return true;
115
- }
116
-
117
- create(origin: number, buffer: Uint8Array): void {
118
- this.blockCache.create(origin, buffer);
119
- for (let i = 0; i < this.aheadRange; ++i) {
120
- if (this.writeCache.read(origin + i) != null) {
121
- this.writeCache.write(origin + i, buffer.slice(i * this.sectorSize, (i + 1) * this.sectorSize));
122
- }
123
- }
124
- }
125
-
126
- getOrigin(sector: number): number {
127
- return this.blockCache.getOrigin(sector);
128
- }
129
-
130
- memUsed(): number {
131
- return this.blockCache.memUsed() + this.writeCache.memUsed();
132
- }
133
- }
@@ -39,7 +39,8 @@ export class Drive {
39
39
  cache: Cache | null = null;
40
40
  cleanup = () => {/**/};
41
41
 
42
- openFn = (read: boolean, write: boolean, size: number, preloadQueue: number[]) => {/**/};
42
+ openFn = (read: boolean, write: boolean, size: number, preloadQueue: number[],
43
+ aheadRange: number) => {/**/};
43
44
  preloadProgressFn = (restBytes: number) => {/**/};
44
45
  errorFn = (e: Error) => {/**/};
45
46
 
@@ -48,13 +49,16 @@ export class Drive {
48
49
  preloadSectors: boolean;
49
50
  preloadQueue: number[] = [];
50
51
 
52
+ originReadMode: boolean;
53
+
51
54
  public constructor(endpoint: string,
52
55
  owner: string,
53
56
  drive: string,
54
57
  token: string,
55
58
  stats: Stats,
56
59
  module: EmModule,
57
- preloadSectors = true) {
60
+ preloadSectors = true,
61
+ originReadMode = false) {
58
62
  this.sectorSize = 512;
59
63
  this.module = module;
60
64
  this.endpoint = endpoint;
@@ -67,6 +71,7 @@ export class Drive {
67
71
  this.stats = stats;
68
72
  this.retries = 3;
69
73
  this.preloadSectors = preloadSectors;
74
+ this.originReadMode = originReadMode;
70
75
 
71
76
  this.reconnect();
72
77
  }
@@ -75,7 +80,8 @@ export class Drive {
75
80
  this.errorFn = errorFn;
76
81
  }
77
82
 
78
- public onOpen(openFn: (read: boolean, write: boolean, imageSize: number, preloadQueue: number[]) => void) {
83
+ public onOpen(openFn: (read: boolean, write: boolean, imageSize: number,
84
+ preloadQueue: number[], aheadRange: number) => void) {
79
85
  this.openFn = openFn;
80
86
  }
81
87
 
@@ -136,7 +142,8 @@ export class Drive {
136
142
  socket.addEventListener("message", onMessage);
137
143
  this.openFn(true, mode === "write",
138
144
  (Number.parseInt(sizeStr) ?? 2 * 1024 * 1024) * 1024,
139
- this.preloadQueue);
145
+ this.preloadQueue,
146
+ aheadRange);
140
147
  resolve(socket);
141
148
 
142
149
  if (this.preloadQueue.length > 0) {
@@ -216,11 +223,9 @@ export class Drive {
216
223
  }
217
224
 
218
225
  public read(sector: number, buffer: Ptr, sync: boolean): Promise<number> | number {
219
- const cached = this.cache!.read(sector);
226
+ const cached = this.cache!.read(sector, this.originReadMode);
220
227
  if (cached !== null) {
221
- if (sync) {
222
- this.stats.cacheHit++;
223
- }
228
+ this.stats.cacheHit++;
224
229
  this.module.HEAPU8.set(cached, buffer);
225
230
  return sync ? 0 : Promise.resolve(0);
226
231
  } else if (sync) {
@@ -361,10 +366,15 @@ export class Drive {
361
366
  this.cache!.create(origin,
362
367
  this.readAheadBuffer.slice(aheadOffset, aheadOffset + this.aheadSize));
363
368
  if (i == 0 && buffer as number >= 0) {
369
+ if (this.originReadMode && sector !== origin) {
370
+ throw new Error("Sector must be one of origins in originReadMode");
371
+ }
364
372
  const offset = sector - origin;
365
373
  // aheadOffset is zero
366
- this.module.HEAPU8.set(this.readAheadBuffer.slice(offset * this.sectorSize,
367
- (offset + 1) * this.sectorSize), buffer as number);
374
+ const from = offset * this.sectorSize;
375
+ this.module.HEAPU8.set(this.readAheadBuffer
376
+ .slice(from, from + (this.originReadMode ? this.aheadRange : 1) * this.sectorSize),
377
+ buffer as number);
368
378
  }
369
379
  }
370
380
 
@@ -182,14 +182,17 @@ async function runTests() {
182
182
  assert.equal(await drive.read(0, 0, false), 0);
183
183
  assert.deepEqual(module.HEAPU8.slice(0, 10), new Uint8Array([51, 192, 142, 208, 188, 0, 124, 251, 80, 7]));
184
184
  assert.equal(1, stats.cacheMiss, "cache miss");
185
+ assert.equal(0, stats.cacheHit, "cache hit");
185
186
 
186
187
  assert.equal(await drive.read(8192, 0, false), 0);
187
188
  assert.deepEqual(module.HEAPU8.slice(0, 10), new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
188
189
  assert.equal(2, stats.cacheMiss, "cache miss");
190
+ assert.equal(0, stats.cacheHit, "cache hit");
189
191
 
190
192
  assert.equal(await drive.read(8448, 0, false), 0);
191
193
  assert.deepEqual(module.HEAPU8.slice(256, 256 + 10), new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]));
192
- assert.equal(3, stats.cacheMiss, "cache miss");
194
+ assert.equal(2, stats.cacheMiss, "cache miss");
195
+ assert.equal(1, stats.cacheHit, "cache hit");
193
196
  });
194
197
 
195
198
  testDrive("read cache", async (drive, module, stats) => {
@@ -213,16 +216,14 @@ async function runTests() {
213
216
  });
214
217
 
215
218
  testDrive("preload sectors", async (drive, module, stats, preloadQueue) => {
216
- assert.ok(preloadQueue.length >= 3);
219
+ assert.ok(preloadQueue.length >= 2);
217
220
  let _0 = false;
218
- let _8448 = false;
219
221
  let _8192 = false;
220
222
  for (const next of preloadQueue) {
221
223
  _0 = _0 || next === 0;
222
224
  _8192 = _8192 || next === 8192;
223
- _8448 = _8448 || next === 8448;
224
225
  }
225
- assert.ok(_0 && _8192 && _8448);
226
+ assert.ok(_0 && _8192);
226
227
 
227
228
  await new Promise<void>((resolve) => setTimeout(resolve, 1000));
228
229
 
package/src/store/ui.ts CHANGED
@@ -27,6 +27,7 @@ const initialState: {
27
27
  toastTimeoutId: number,
28
28
  background: string | null,
29
29
  readOnlyWarning: boolean,
30
+ updateWsWarning: boolean,
30
31
  cloudSaves: boolean,
31
32
  } = {
32
33
  modal: "none",
@@ -42,6 +43,7 @@ const initialState: {
42
43
  toastTimeoutId: 0,
43
44
  background: null,
44
45
  readOnlyWarning: false,
46
+ updateWsWarning: false,
45
47
  cloudSaves: true,
46
48
  };
47
49
 
@@ -136,6 +138,9 @@ export const uiSlice = createSlice({
136
138
  readOnlyWarning: (state, a: { payload: boolean }) => {
137
139
  state.readOnlyWarning = a.payload;
138
140
  },
141
+ updateWsWarning: (state, a: { payload: boolean }) => {
142
+ state.updateWsWarning = a.payload;
143
+ },
139
144
  cloudSaves: (state, a: { payload: boolean }) => {
140
145
  state.cloudSaves = a.payload;
141
146
  },
package/src/ui.tsx CHANGED
@@ -49,6 +49,7 @@ export function Ui() {
49
49
  <Login />
50
50
  <Toast />
51
51
  <ReadOnlyWarning />
52
+ <UpdateWsWarning />
52
53
  </div>;
53
54
  };
54
55
 
@@ -102,3 +103,38 @@ function ReadOnlyWarning() {
102
103
  </div>
103
104
  </div>;
104
105
  }
106
+
107
+ function UpdateWsWarning() {
108
+ const updateWsWarning = useSelector((state: State) => state.ui.updateWsWarning);
109
+ const t = useT();
110
+ const dispatch = useDispatch();
111
+
112
+ if (!updateWsWarning) {
113
+ return null;
114
+ }
115
+
116
+ function fix() {
117
+ window.open("https://dos.zone/download/", "_blank");
118
+ dispatch(uiSlice.actions.updateWsWarning(false));
119
+ }
120
+
121
+ function close() {
122
+ dispatch(uiSlice.actions.updateWsWarning(false));
123
+ }
124
+
125
+ return <div class="absolute left-20 right-10 top-10 flex justify-center">
126
+ <div class="alert w-auto">
127
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
128
+ stroke-width="1.5" stroke="currentColor" class="size-6 text-error">
129
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217
130
+ 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898
131
+ 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z" />
132
+ </svg>
133
+ <span>{t("ws_outdated")}</span>
134
+ <div>
135
+ <button class="btn btn-sm btn-primary mr-2" onClick={fix}>{t("update")}</button>
136
+ <button class="btn btn-sm" onClick={close}>{t("close")}</button>
137
+ </div>
138
+ </div>
139
+ </div>;
140
+ }
package/src/v8/config.ts CHANGED
@@ -16,3 +16,5 @@ export const cancelSubscriptionPage = {
16
16
  en: "https://v8.js-dos.com/cancel-your-subscription/",
17
17
  ru: "https://v8.js-dos.com/ru/cancel-your-subscription/",
18
18
  };
19
+
20
+ export const actualWsVersion = 1;
@@ -6,6 +6,8 @@ import { State, useNonSerializableStore, postJsDosEvent } from "../../store";
6
6
  import { useDosRuntime } from "./dos-runtime";
7
7
  import { dhry2Bundle, Dhry2Results } from "./dos-dhry2";
8
8
  import { createWsTransportLayer } from "../../ws/ws-transport-layer";
9
+ import { actualWsVersion } from "../../v8/config";
10
+ import { uiSlice } from "../../store/ui";
9
11
 
10
12
  declare const emulators: Emulators;
11
13
 
@@ -35,7 +37,12 @@ export function DosWindow(props: {
35
37
  if (backendHardware && nonSerializableStore.options.backendHardware) {
36
38
  const ws = await nonSerializableStore.options.backendHardware(backend);
37
39
  if (ws !== null) {
38
- return emulators.backend(bundles as any, await createWsTransportLayer(ws), { token });
40
+ return emulators.backend(bundles as any, await createWsTransportLayer(ws, (version) => {
41
+ if (version < actualWsVersion) {
42
+ dispatch(uiSlice.actions.updateWsWarning(true));
43
+ }
44
+ console.log("wsServer:", version, " expected:", actualWsVersion);
45
+ }), { token });
39
46
  }
40
47
  }
41
48
 
@@ -16,8 +16,10 @@ export interface ReadResponse {
16
16
  }
17
17
 
18
18
  export function createSockdrive(
19
- onOpen: (drive: string, read: boolean, write: boolean) => void,
20
- onError: (e: Error) => void) {
19
+ onOpen: (drive: string, read: boolean, write: boolean, imageSize: number, preloadQueue: number[]) => void,
20
+ onError: (e: Error) => void,
21
+ onPreloadProgress: (drive: string, restBytes: number) => void,
22
+ ) {
21
23
  let seq = 0;
22
24
  const mapping: { [handle: Handle]: Drive } = {};
23
25
  const templates: { [handle: number]: Template } = {};
@@ -32,7 +34,10 @@ export function createSockdrive(
32
34
  };
33
35
  const sockdrive = {
34
36
  stats,
35
- open: async (url: string, owner: string, name: string, token: string, aheadRange: number): Promise<Handle> => {
37
+ open: async (url: string, owner: string, name: string, token: string): Promise<{
38
+ handle: Handle,
39
+ aheadRange: number,
40
+ }> => {
36
41
  const response = await fetch(url.replace("wss://", "https://")
37
42
  .replace("ws://", "http://") + "/template/" + owner + "/" + name);
38
43
  const template = await response.json();
@@ -50,22 +55,39 @@ export function createSockdrive(
50
55
  };
51
56
 
52
57
  const sectorSize = templates[seq].sectorSize;
53
- memory[seq] = new Uint8Array(sectorSize + sectorSize * aheadRange);
54
- mapping[seq] = new Drive(url, owner, name, token, stats, { HEAPU8: memory[seq] });
55
- mapping[seq].onOpen((read, write) => {
56
- onOpen(owner + "/" + name, read, write);
58
+ const module = { HEAPU8: new Uint8Array(0) };
59
+ mapping[seq] = new Drive(url, owner, name, token, stats, module, true, true);
60
+ return new Promise<{ handle: Handle, aheadRange: number }>((resolve, reject) => {
61
+ const drive = owner + "/" + name;
62
+ mapping[seq].onOpen((read, write, imageSize, preloadQueue, aheadRange) => {
63
+ memory[seq] = new Uint8Array(sectorSize /* write */ + sectorSize * aheadRange);
64
+ module.HEAPU8 = memory[seq];
65
+ onOpen(drive, read, write, imageSize, preloadQueue);
66
+ resolve({
67
+ handle: seq,
68
+ aheadRange,
69
+ });
70
+ });
71
+ mapping[seq].onPreloadProgress((restBytes) => {
72
+ onPreloadProgress(drive, restBytes);
73
+ });
74
+ mapping[seq].onError((e) => {
75
+ onError(e);
76
+ reject(e);
77
+ });
57
78
  });
58
- mapping[seq].onError(onError);
59
- return seq;
60
79
  },
61
- read: (handle: Handle, sector: number): Promise<ReadResponse> => {
80
+ read: async (handle: Handle, sector: number): Promise<ReadResponse> => {
62
81
  if (mapping[handle]) {
63
- return (mapping[handle].read(sector, 0, false) as Promise<number>).then((code) => {
64
- return {
65
- code,
66
- buffer: memory[handle].slice(templates[seq].sectorSize),
67
- };
68
- });
82
+ const ptr = templates[seq].sectorSize;
83
+ let code = mapping[handle].read(sector, ptr, true) as number;
84
+ if (code = 255) {
85
+ code = await mapping[handle].read(sector, ptr, false);
86
+ }
87
+ return {
88
+ code,
89
+ buffer: memory[handle].slice(ptr),
90
+ };
69
91
  }
70
92
 
71
93
  console.error("ERROR! sockdrive handle", handle, "not found");
@@ -48,9 +48,11 @@ export class WsTransportLayer implements TransportLayer {
48
48
  socket: WebSocket;
49
49
  sessionId: string = Date.now() + "";
50
50
  hardware: Hardware;
51
+ onInit: (version: number) => void = {/**/};
51
52
 
52
53
  private cycles = 0;
53
- private sockdrive = createSockdrive(this.onSockdriveOpen.bind(this), this.onSockdriveError.bind(this));
54
+ private sockdrive = createSockdrive(this.onSockdriveOpen.bind(this),
55
+ this.onSockdriveError.bind(this), this.onSockdrivePreloadProgress.bind(this));
54
56
 
55
57
  private handler: MessageHandler = () => {/**/};
56
58
 
@@ -120,15 +122,17 @@ export class WsTransportLayer implements TransportLayer {
120
122
  const { id, payload } = this.readMessage(msg);
121
123
  const message = serverMessageEnum[id];
122
124
  switch (message) {
123
- case "ws-ready":
125
+ case "ws-ready": {
126
+ this.onInit((payload && payload[0] && payload.length > 0) ? payload[0][0] : 0);
127
+ this.handler(message, {});
128
+ } break;
124
129
  case "ws-server-ready":
125
- case "ws-exit":
126
- {
127
- // delay ws-server-ready until ws-sound-init
128
- if (message !== "ws-server-ready") {
129
- this.handler(message, {});
130
- }
131
- } break;
130
+ case "ws-exit": {
131
+ // delay ws-server-ready until ws-sound-init
132
+ if (message !== "ws-server-ready") {
133
+ this.handler(message, {});
134
+ }
135
+ } break;
132
136
  case "ws-stdout":
133
137
  case "ws-log":
134
138
  case "ws-warn":
@@ -267,17 +271,17 @@ export class WsTransportLayer implements TransportLayer {
267
271
  const owner = textDecoder.decode(payload[1]!);
268
272
  const name = textDecoder.decode(payload[2]!);
269
273
  const token = textDecoder.decode(payload[3]!);
270
- const aheadRange = this.readUint32(payload[4]!, 0);
271
- const handle = await this.sockdrive.open(url, owner, name, token, aheadRange);
274
+ const { handle, aheadRange } = await this.sockdrive.open(url, owner, name, token);
272
275
  const template = this.sockdrive.template(handle);
273
- const packet = new Uint8Array(4 * 6);
276
+ const packet = new Uint8Array(4 * 7);
274
277
  let offset = 0;
275
278
  offset = this.writeUint32(packet, handle, offset);
276
279
  offset = this.writeUint32(packet, template.size, offset);
277
280
  offset = this.writeUint32(packet, template.heads, offset);
278
281
  offset = this.writeUint32(packet, template.cylinders, offset);
279
282
  offset = this.writeUint32(packet, template.sectors, offset);
280
- this.writeUint32(packet, template.sectorSize, offset);
283
+ offset = this.writeUint32(packet, template.sectorSize, offset);
284
+ this.writeUint32(packet, aheadRange, offset);
281
285
  this.sendMessageToSocket(100, packet);
282
286
  } break;
283
287
  case 101/* ws-sockdrive-read */: {
@@ -317,7 +321,7 @@ export class WsTransportLayer implements TransportLayer {
317
321
  }
318
322
  }
319
323
 
320
- constructor(socket: WebSocket) {
324
+ constructor(socket: WebSocket, onInit: (version: number) => void) {
321
325
  this.socket = socket;
322
326
  this.socket.addEventListener("error", (e) => {
323
327
  this.handler("ws-err", { tag: "ws", message: (e as any).message ?? "Unknown transport layer error" });
@@ -330,6 +334,7 @@ export class WsTransportLayer implements TransportLayer {
330
334
  });
331
335
  this.sendMessageToSocket("wc-install");
332
336
  this.hardware = (null) as any;
337
+ this.onInit = onInit;
333
338
  }
334
339
 
335
340
  async sendMessageToServer(name: ClientMessage, props?: { [key: string]: any; }) {
@@ -411,10 +416,11 @@ export class WsTransportLayer implements TransportLayer {
411
416
  this.sendMessageToSocket("wc-exit");
412
417
  }
413
418
 
414
- onSockdriveOpen(drive: string, read: boolean, write: boolean) {
419
+ onSockdriveOpen(drive: string, read: boolean, write: boolean, imageSize: number, preloadQueue: number[]) {
415
420
  this.handler("ws-log", {
416
421
  tag: "worker",
417
- message: "sockdrive: " + drive + ", read=" + read + ", write=" + write,
422
+ message: "sockdrive: " + drive + ", read=" + read + ", write=" + write +
423
+ ", imageSize=" + Math.round(imageSize / 1024 / 1024) + "Mb" + ", preloadQueue=" + preloadQueue.length,
418
424
  });
419
425
  }
420
426
 
@@ -424,9 +430,16 @@ export class WsTransportLayer implements TransportLayer {
424
430
  message: e.message ?? "unable to open sockdrive",
425
431
  });
426
432
  }
433
+
434
+ onSockdrivePreloadProgress(drive: string, restBytes: number) {
435
+ this.handler("ws-log", {
436
+ tag: "worker",
437
+ message: "sockdrive: " + drive + ", preload=" + restBytes,
438
+ });
439
+ }
427
440
  }
428
441
 
429
- export function createWsTransportLayer(url: string): Promise<TransportLayer> {
442
+ export function createWsTransportLayer(url: string, onInit: (version: number) => void): Promise<TransportLayer> {
430
443
  return new Promise<TransportLayer>((resolve) => {
431
444
  let locked = false;
432
445
  const inervalId = setInterval(() => {
@@ -439,7 +452,7 @@ export function createWsTransportLayer(url: string): Promise<TransportLayer> {
439
452
  const onSuccess = () => {
440
453
  clearInterval(inervalId);
441
454
  console.log("Connected to", url);
442
- resolve(new WsTransportLayer(ws));
455
+ resolve(new WsTransportLayer(ws, onInit));
443
456
  };
444
457
 
445
458
  ws.addEventListener("error", (error) => {