@netless/app-slide 0.2.106-beta.5 → 0.2.106

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": "@netless/app-slide",
3
- "version": "0.2.106-beta.5",
3
+ "version": "0.2.106",
4
4
  "main": "dist/main.cjs.js",
5
5
  "module": "dist/main.es.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,7 +18,7 @@
18
18
  ],
19
19
  "devDependencies": {
20
20
  "@juggle/resize-observer": "^3.3.1",
21
- "@netless/slide": "1.4.62-beta.1",
21
+ "@netless/slide": "1.4.62",
22
22
  "@types/color-string": "^1.5.2",
23
23
  "color-string": "^1.9.1",
24
24
  "jspdf": "2.5.1",
@@ -2,13 +2,12 @@
2
2
  // 1. map slide events to ui
3
3
  // 2. make sure to init correctly
4
4
  // - the one with (context.isAddApp === true) should call renderSlide(1)
5
- // - others wait for first sync event and restore from sync state
6
- // - if none of above happen, force doing renderSlide(1) after timeout
5
+ // - others restore from sync state, or render page 1 locally if no state exists
7
6
  // 3. send/receive slide sync events
8
7
  // 4. automatically re-create scenes to sync strokes, a view must be existing
9
8
  // 5. pages information are loaded dynamically by the slide package
10
9
 
11
- import type { AppContext, Player, Room, Displayer } from "@netless/window-manager";
10
+ import type { AppContext, Player, Room } from "@netless/window-manager";
12
11
  import type { ISlideConfig, SyncEvent } from "@netless/slide";
13
12
  import type { Attributes, MagixEvents, MagixPayload, SlideState } from "../typings";
14
13
  import type { AppOptions } from "..";
@@ -73,16 +72,27 @@ function runNextSlideWebGLTask(): void {
73
72
  slideWebGLRunning = true;
74
73
  const startedAt = performance.now();
75
74
  log("[Slide][player-queue] start", task.label, "pending", slideWebGLTasks.length);
76
- void Promise.resolve().then(task.run).then(() => {
77
- log("[Slide][player-queue] done", task.label, "ms", Math.round(performance.now() - startedAt));
78
- task.resolve();
79
- }, error => {
80
- log("[Slide][player-queue] failed", task.label, error);
81
- task.reject(error);
82
- }).finally(() => {
83
- slideWebGLRunning = false;
84
- runNextSlideWebGLTask();
85
- });
75
+ void Promise.resolve()
76
+ .then(task.run)
77
+ .then(
78
+ () => {
79
+ log(
80
+ "[Slide][player-queue] done",
81
+ task.label,
82
+ "ms",
83
+ Math.round(performance.now() - startedAt)
84
+ );
85
+ task.resolve();
86
+ },
87
+ error => {
88
+ log("[Slide][player-queue] failed", task.label, error);
89
+ task.reject(error);
90
+ }
91
+ )
92
+ .finally(() => {
93
+ slideWebGLRunning = false;
94
+ runNextSlideWebGLTask();
95
+ });
86
96
  }
87
97
 
88
98
  export function enqueueSlideWebGLTransition(
@@ -156,7 +166,7 @@ export class SlideControllerBase {
156
166
  this.context = context;
157
167
  this.room = context.getRoom();
158
168
  this.player = this.room ? undefined : (context.getDisplayer() as Player);
159
- setRoomLogger((this.room || this.player) as Displayer);
169
+ setRoomLogger(context);
160
170
  // this.slide = this.createSlide(anchor, {
161
171
  // whiteTracker: getRoomTracker(context.getDisplayer()),
162
172
  // });
@@ -225,10 +235,14 @@ export class SlideControllerBase {
225
235
  // otherwise, maybe this slide is just added, let the adder kick start first render
226
236
  log("[Slide] init by renderSlide", 1);
227
237
  slide.renderSlide(1);
238
+ } else if (taskId) {
239
+ // A previous add may have persisted the App before its first render.
240
+ // Render locally so a restored or read-only client can recover it.
241
+ void slide.doRenderSlide(1).catch(error => {
242
+ logger.error("[Slide] initial local render failed", context.appId, error);
243
+ });
228
244
  }
229
- // there's still some risk that the adder is left and no first render
230
- // so anyway, we start polling the slide's "ready state"
231
- // if in the next 20 seconds the slide is not ready, start render first page
245
+ // Keep tracking the first render for setup diagnostics.
232
246
  this.pollReadyState();
233
247
  }
234
248
 
@@ -333,9 +347,13 @@ export class SlideControllerBase {
333
347
  return;
334
348
  }
335
349
  if (this._toFreeze === 1) {
336
- this.freeze();
350
+ void this.freeze().catch(error =>
351
+ logger.error("[Slide] deferred freeze failed", this.context.appId, error)
352
+ );
337
353
  } else if (this._toFreeze === -1) {
338
- this.unfreeze();
354
+ void this.unfreeze().catch(error =>
355
+ logger.error("[Slide] deferred unfreeze failed", this.context.appId, error)
356
+ );
339
357
  }
340
358
  } else if (this.pollCount < MaxPollCount) {
341
359
  this.pollCount++;
@@ -360,7 +378,7 @@ export class SlideControllerBase {
360
378
  }
361
379
 
362
380
  protected createSlide(anchor: HTMLDivElement, defaults: Partial<ISlideConfig> = {}) {
363
- const options = this.context.getAppOptions() || {};
381
+ const options = (this.context.getAppOptions() || {}) as AppOptions;
364
382
  const attribute = this.context.storage.state;
365
383
  const slide = new Slide({
366
384
  anchor,
@@ -387,7 +405,29 @@ export class SlideControllerBase {
387
405
  resourceTimeout: options.resourceTimeout,
388
406
  rtcAudio: options.rtcAudio,
389
407
  useLocalCache: options.useLocalCache,
390
- logger: options.logger,
408
+ logger: {
409
+ ...options.logger,
410
+ error: (message, ...details) => {
411
+ logger.error(message, ...details);
412
+ if (options.logger !== logger.roomLogger) {
413
+ try {
414
+ options.logger?.error?.(message, ...details);
415
+ } catch {
416
+ // Keep the App error even if a custom logger fails.
417
+ }
418
+ }
419
+ },
420
+ warn: (message, ...details) => {
421
+ logger.warn(message, ...details);
422
+ if (options.logger !== logger.roomLogger) {
423
+ try {
424
+ options.logger?.warn?.(message, ...details);
425
+ } catch {
426
+ // Keep the App warning even if a custom logger fails.
427
+ }
428
+ }
429
+ },
430
+ },
391
431
  whiteTracker: defaults.whiteTracker,
392
432
  timestamp: this.timestamp,
393
433
  customLinks: attribute.customLinks,
@@ -416,19 +456,29 @@ export class SlideControllerBase {
416
456
  if (!this.destroyed) {
417
457
  log("[Slide] destroy slide (once)");
418
458
  this.destroyed = true;
419
- this.destroyPromise = enqueueSlideWebGLTransition(() => new Promise<void>((resolve, reject) => {
420
- try {
421
- (this.slide.destroy as (
422
- onPlayerDestroyed?: () => void,
423
- onError?: (error: unknown) => void
424
- ) => void).call(this.slide, () => {
425
- log("[Slide][player] destroy callback", this.context.appId);
426
- resolve();
427
- }, reject);
428
- } catch (error) {
429
- reject(error);
430
- }
431
- }), true, `destroy:${this.context.appId}`);
459
+ this.destroyPromise = enqueueSlideWebGLTransition(
460
+ () =>
461
+ new Promise<void>((resolve, reject) => {
462
+ try {
463
+ (
464
+ this.slide.destroy as (
465
+ onPlayerDestroyed?: () => void,
466
+ onError?: (error: unknown) => void
467
+ ) => void
468
+ ).call(
469
+ this.slide,
470
+ () => {
471
+ resolve();
472
+ },
473
+ reject
474
+ );
475
+ } catch (error) {
476
+ reject(error);
477
+ }
478
+ }),
479
+ true,
480
+ `destroy:${this.context.appId}`
481
+ );
432
482
  this.resolveDestroyedSignal();
433
483
  }
434
484
  return this.destroyPromise ?? Promise.resolve();
@@ -462,7 +512,7 @@ export class SlideControllerBase {
462
512
  if (active && this.shouldBeActive()) await this.unfreeze();
463
513
  else await this.freeze();
464
514
  },
465
- error => logger.warn("[Slide] resource transition failed", this.context.appId, error)
515
+ error => logger.error("[Slide] resource transition failed", this.context.appId, error)
466
516
  );
467
517
 
468
518
  private shouldBeActive = (): boolean => {
@@ -504,20 +554,29 @@ export class SlideControllerBase {
504
554
  if (this.ready) {
505
555
  log("[Slide] freeze", this.context.appId);
506
556
  if (this.invisibleBehavior === "frozen") {
507
- await enqueueSlideWebGLTransition(() => {
508
- if (this.destroyed) return Promise.resolve();
509
- const destroyed = new Promise<void>((resolve, reject) => {
510
- const result = (this.slide.frozen as (
511
- onPlayerDestroyed?: () => void,
512
- onError?: (error: unknown) => void
513
- ) => void | Promise<void>).call(this.slide, () => {
514
- log("[Slide][player] frozen callback", this.context.appId);
515
- resolve();
516
- }, reject);
517
- if (result) void result.then(resolve, reject);
518
- });
519
- return Promise.race([destroyed, this.destroyedSignal]);
520
- }, false, `freeze:${this.context.appId}`);
557
+ await enqueueSlideWebGLTransition(
558
+ () => {
559
+ if (this.destroyed) return Promise.resolve();
560
+ const destroyed = new Promise<void>((resolve, reject) => {
561
+ const result = (
562
+ this.slide.frozen as (
563
+ onPlayerDestroyed?: () => void,
564
+ onError?: (error: unknown) => void
565
+ ) => void | Promise<void>
566
+ ).call(
567
+ this.slide,
568
+ () => {
569
+ resolve();
570
+ },
571
+ reject
572
+ );
573
+ if (result) void result.then(resolve, reject);
574
+ });
575
+ return Promise.race([destroyed, this.destroyedSignal]);
576
+ },
577
+ false,
578
+ `freeze:${this.context.appId}`
579
+ );
521
580
  } else {
522
581
  this.slide.pause();
523
582
  }
@@ -543,33 +602,46 @@ export class SlideControllerBase {
543
602
  if (this.ready) {
544
603
  log("[Slide] unfreeze", this.context.appId);
545
604
  if (this.invisibleBehavior === "frozen") {
546
- await enqueueSlideWebGLTransition(() => {
547
- if (this.destroyed) return Promise.resolve();
548
- const created = new Promise<void>((resolve, reject) => {
549
- const result = (this.slide.release as (
550
- onRestored?: () => void,
551
- onPlayerCreated?: () => void,
552
- onError?: (error: unknown) => void
553
- ) => void | Promise<void>).call(this.slide, () => {
554
- log("[Slide][player] restored callback", this.context.appId);
555
- if (this.isLazySetupMode() && !this.shouldBeActive()) return;
556
- this.slide.notifyFrameResize();
557
- const state = this.context.storage.state.state;
558
- if (state) {
559
- log("[Slide] sync storage", JSON.stringify(state));
560
- void this.slide.setSlideState(state).catch(error => {
561
- this.resourceStateUnknown = true;
562
- logger.warn("[Slide] storage sync after release failed", this.context.appId, error);
563
- });
564
- }
565
- }, () => {
566
- log("[Slide][player] created callback", this.context.appId);
567
- resolve();
568
- }, reject);
569
- if (result) void result.then(resolve, reject);
570
- });
571
- return Promise.race([created, this.destroyedSignal]);
572
- }, false, `release:${this.context.appId}`);
605
+ await enqueueSlideWebGLTransition(
606
+ () => {
607
+ if (this.destroyed) return Promise.resolve();
608
+ const created = new Promise<void>((resolve, reject) => {
609
+ const result = (
610
+ this.slide.release as (
611
+ onRestored?: () => void,
612
+ onPlayerCreated?: () => void,
613
+ onError?: (error: unknown) => void
614
+ ) => void | Promise<void>
615
+ ).call(
616
+ this.slide,
617
+ () => {
618
+ if (this.isLazySetupMode() && !this.shouldBeActive()) return;
619
+ this.slide.notifyFrameResize();
620
+ const state = this.context.storage.state.state;
621
+ if (state) {
622
+ log("[Slide] sync storage", JSON.stringify(state));
623
+ void this.slide.setSlideState(state).catch(error => {
624
+ this.resourceStateUnknown = true;
625
+ logger.error(
626
+ "[Slide] storage sync after release failed",
627
+ this.context.appId,
628
+ error
629
+ );
630
+ });
631
+ }
632
+ },
633
+ () => {
634
+ resolve();
635
+ },
636
+ reject
637
+ );
638
+ if (result) void result.then(resolve, reject);
639
+ });
640
+ return Promise.race([created, this.destroyedSignal]);
641
+ },
642
+ false,
643
+ `release:${this.context.appId}`
644
+ );
573
645
  } else {
574
646
  this.slide.resume();
575
647
  this.slide.notifyFrameResize();
@@ -614,7 +686,9 @@ export class SlideControllerBase {
614
686
  }
615
687
  if (state === "minimized") {
616
688
  log("[Slide] freeze because app state is minimized");
617
- this.freeze();
689
+ void this.freeze().catch(error =>
690
+ logger.error("[Slide] minimized freeze failed", this.context.appId, error)
691
+ );
618
692
  }
619
693
  };
620
694
 
@@ -629,7 +703,9 @@ export class SlideControllerBase {
629
703
  }
630
704
  if (appId === this.context.appId && status === "minimized") {
631
705
  log("[Slide] freeze because app status is minimized");
632
- this.freeze();
706
+ void this.freeze().catch(error =>
707
+ logger.error("[Slide] minimized freeze failed", this.context.appId, error)
708
+ );
633
709
  }
634
710
  };
635
711
 
@@ -658,26 +734,30 @@ export class SlideControllerBase {
658
734
  };
659
735
 
660
736
  protected onVisibilityChange = async () => {
661
- this.visible = document.visibilityState === "visible";
662
- if (this.isLazySetupMode() || this.managedActivity) {
663
- await this.reconcileActivity().catch(() => undefined);
664
- return;
665
- }
666
- const appStatus = this.getAppStatus();
667
- if (!this.visible) {
668
- this.savedIsFrozen = this.isFrozen;
669
- log("[Slide] freeze because tab becomes invisible");
670
- if (!this.isFrozen) await this.freeze();
671
- return;
672
- }
737
+ try {
738
+ this.visible = document.visibilityState === "visible";
739
+ if (this.isLazySetupMode() || this.managedActivity) {
740
+ await this.reconcileActivity().catch(() => undefined);
741
+ return;
742
+ }
743
+ const appStatus = this.getAppStatus();
744
+ if (!this.visible) {
745
+ this.savedIsFrozen = this.isFrozen;
746
+ log("[Slide] freeze because tab becomes invisible");
747
+ if (!this.isFrozen) await this.freeze();
748
+ return;
749
+ }
673
750
 
674
- if (appStatus === "minimized") {
675
- log("[Slide] do nothing because app state is minimized");
676
- return;
677
- }
678
- if (!this.savedIsFrozen) {
679
- log("[Slide] unfreeze because tab becomes visible", { savedIsFrozen: this.savedIsFrozen });
680
- await this.unfreeze();
751
+ if (appStatus === "minimized") {
752
+ log("[Slide] do nothing because app state is minimized");
753
+ return;
754
+ }
755
+ if (!this.savedIsFrozen) {
756
+ log("[Slide] unfreeze because tab becomes visible", { savedIsFrozen: this.savedIsFrozen });
757
+ await this.unfreeze();
758
+ }
759
+ } catch (error) {
760
+ logger.error("[Slide] visibility transition failed", this.context.appId, error);
681
761
  }
682
762
  };
683
763
  }
@@ -431,12 +431,12 @@ export class SlideDocsViewer {
431
431
  if (this.slideController?.onRenderError) {
432
432
  this.slideController.onRenderError(error, index);
433
433
  }
434
- logger.warn("[Slide] render error", error);
434
+ logger.error("[Slide] render error", this.appId, error);
435
435
  };
436
436
 
437
437
  protected onRenderStart = () => {
438
- logger.log("[Slide][notes] hide", this.appId, "scene", this.whiteboardView.focusScenePath);
439
438
  this.$whiteboardView.classList.add(this.wrapClassName("wb-view-hidden"));
439
+ logger.log("[Slide][notes] hide", this.appId, "scene", this.whiteboardView.focusScenePath);
440
440
  this.viewer.setPlaying();
441
441
  };
442
442
 
@@ -448,15 +448,14 @@ export class SlideDocsViewer {
448
448
 
449
449
  public onPageChanged = () => {
450
450
  clearTimeout(this._onPageChangedTimer);
451
- logger.log("[Slide][notes] show scheduled", this.appId, "scene", this.whiteboardView.focusScenePath);
452
451
  this._onPageChangedTimer = setTimeout(this._onPageChanged, 200) as unknown as number;
453
452
  };
454
453
 
455
454
  protected _onPageChangedTimer = 0;
456
455
  protected _onPageChanged = () => {
457
- logger.log("[Slide][notes] show", this.appId, "scene", this.whiteboardView.focusScenePath);
458
456
  this.$overlay.style.opacity = "";
459
457
  this.$whiteboardView.classList.remove(this.wrapClassName("wb-view-hidden"));
458
+ logger.log("[Slide][notes] show", this.appId, "scene", this.whiteboardView.focusScenePath);
460
459
  };
461
460
 
462
461
  protected refreshPages = () => {
@@ -484,13 +483,19 @@ export class SlideDocsViewer {
484
483
  this.offBoxSizeChange = undefined;
485
484
  this.contentResizeObserver?.disconnect();
486
485
  this.contentResizeObserver = undefined;
487
- if (this.slideController) {
488
- const controller = this.slideController;
489
- this.slideController = null;
490
- await controller.destroy();
486
+ try {
487
+ if (this.slideController) {
488
+ const controller = this.slideController;
489
+ this.slideController = null;
490
+ await controller.destroy();
491
+ }
492
+ } finally {
493
+ try {
494
+ this.viewer.unmount();
495
+ } finally {
496
+ this.resizableContainer.destroy(this.box);
497
+ }
491
498
  }
492
- this.viewer.unmount();
493
- this.resizableContainer.destroy(this.box);
494
499
  return this;
495
500
  });
496
501
  }
@@ -505,8 +510,11 @@ export class SlideDocsViewer {
505
510
  if (!this.destroyPromise) {
506
511
  this.destroyPromise = Promise.resolve().then(async () => {
507
512
  this.sideEffect.flushAll();
508
- await this.unmount();
509
- this.viewer.destroy();
513
+ try {
514
+ await this.unmount();
515
+ } finally {
516
+ this.viewer.destroy();
517
+ }
510
518
  });
511
519
  }
512
520
  return this.destroyPromise;
package/src/index.ts CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  } from "./SlideController";
18
18
  import { SlideDocsViewer } from "./SlideDocsViewer";
19
19
  import { apps, FreezerLength, addHooks, useFreezer } from "./utils/freezer";
20
- import { log, logger } from "./utils/logger";
20
+ import { log, logger, setRoomLogger } from "./utils/logger";
21
21
  import styles from "./style.scss?inline";
22
22
 
23
23
  export type { PreviewParams } from "./SlidePreviewer";
@@ -130,6 +130,7 @@ export interface AppResult {
130
130
  const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
131
131
  kind: "Slide",
132
132
  setup(context) {
133
+ setRoomLogger(context);
133
134
  console.log("[Slide] setup @ " + version);
134
135
 
135
136
  if (context.getIsWritable()) {
@@ -185,7 +186,7 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
185
186
  };
186
187
 
187
188
  const mountSlideController = (options: MountSlideOptions): SlideController => {
188
- const appOptions = context.getAppOptions() || {};
189
+ const appOptions = (context.getAppOptions() || {}) as AppOptions;
189
190
 
190
191
  const slideController = new SlideController({
191
192
  context,
@@ -211,22 +212,25 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
211
212
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
212
213
  (window as any).slideController = slideController;
213
214
  }
214
- slideController.readyPromise.then(options.onReady).then(() => {
215
- // Teardown may have destroyed the box while this deferred callback
216
- // was pending; syncing scenes against a destroyed app throws.
217
- if (disposed) return;
218
- const room = context.getRoom();
219
- let synced = false;
220
- if (room && context.getIsWritable()) {
221
- syncSceneWithSlide(room, context, slideController.slide, baseScenePath);
222
- synced = true;
223
- }
224
- const page = slideController.slide.slideState.currentSlideIndex;
225
- log("[Slide] page to", page, synced ? "(synced)" : "", "(on ready)");
226
- slideController.slide.on("renderEnd", options.onRenderEnd);
227
- }).catch(error => {
228
- if (!disposed) logger.warn("[Slide] ready callback failed", context.appId, error);
229
- });
215
+ slideController.readyPromise
216
+ .then(options.onReady)
217
+ .then(() => {
218
+ // Teardown may have destroyed the box while this deferred callback
219
+ // was pending; syncing scenes against a destroyed app throws.
220
+ if (disposed) return;
221
+ const room = context.getRoom();
222
+ let synced = false;
223
+ if (room && context.getIsWritable()) {
224
+ syncSceneWithSlide(room, context, slideController.slide, baseScenePath);
225
+ synced = true;
226
+ }
227
+ const page = slideController.slide.slideState.currentSlideIndex;
228
+ log("[Slide] page to", page, synced ? "(synced)" : "", "(on ready)");
229
+ slideController.slide.on("renderEnd", options.onRenderEnd);
230
+ })
231
+ .catch(error => {
232
+ if (!disposed) logger.warn("[Slide] ready callback failed", context.appId, error);
233
+ });
230
234
  return slideController;
231
235
  };
232
236
 
@@ -330,9 +334,13 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
330
334
  };
331
335
  offDestroy = context.emitter.on("destroy", teardown);
332
336
 
333
- const mounting = enqueueSlideWebGLTransition(() => {
334
- if (!disposed) docsViewer?.mount();
335
- }, false, `setup:${context.appId}`);
337
+ const mounting = enqueueSlideWebGLTransition(
338
+ () => {
339
+ if (!disposed) docsViewer?.mount();
340
+ },
341
+ false,
342
+ `setup:${context.appId}`
343
+ );
336
344
 
337
345
  // Resolve once the SlideController finished its first render (renderEnd).
338
346
  // New lazy hosts retain the real completion after the warning threshold.
@@ -505,22 +513,25 @@ const SlideApp: NetlessApp<Attributes, MagixEvents, AppOptions, AppResult> = {
505
513
  };
506
514
 
507
515
  const setupReadyTimeout = appOptions?.setupReadyTimeout ?? DEFAULT_SLIDE_SETUP_READY_TIMEOUT;
508
- return mounting.then(() => {
509
- if (disposed) throw new Error("[Slide] disposed before first render");
510
- return waitForFirstRender(setupReadyTimeout);
511
- }).then(async () => {
512
- if (disposed) throw new Error("[Slide] disposed before first render");
513
- if (firstRenderFailed) {
514
- throw new Error("[Slide] first render failed before ready");
515
- }
516
- if (!disposed && !slideControllerRef?.ready) {
517
- log("[Slide] setup ready wait timed out, slide keeps loading in background");
518
- }
519
- return appResult;
520
- }).catch(async error => {
521
- if (!disposed) await teardown();
522
- throw error;
523
- }) as unknown as AppResult;
516
+ return mounting
517
+ .then(() => {
518
+ if (disposed) throw new Error("[Slide] disposed before first render");
519
+ return waitForFirstRender(setupReadyTimeout);
520
+ })
521
+ .then(async () => {
522
+ if (disposed) throw new Error("[Slide] disposed before first render");
523
+ if (firstRenderFailed) {
524
+ throw new Error("[Slide] first render failed before ready");
525
+ }
526
+ if (!disposed && !slideControllerRef?.ready) {
527
+ log("[Slide] setup ready wait timed out, slide keeps loading in background");
528
+ }
529
+ return appResult;
530
+ })
531
+ .catch(async error => {
532
+ if (!disposed) await teardown();
533
+ throw error;
534
+ }) as unknown as AppResult;
524
535
  },
525
536
  };
526
537