@webspatial/core-sdk 1.6.0 → 1.7.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 (54) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/iife/index.d.ts +386 -236
  3. package/dist/iife/index.global.js +7 -7
  4. package/dist/iife/index.global.js.map +1 -1
  5. package/dist/index.d.ts +386 -236
  6. package/dist/index.js +1479 -1226
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/JSBCommand.ts +30 -100
  10. package/src/Spatial.ts +0 -21
  11. package/src/SpatialScene.ts +1 -5
  12. package/src/SpatialSession.ts +17 -3
  13. package/src/SpatializedDynamic3DElement.ts +0 -1
  14. package/src/SpatializedElementCreator.ts +6 -4
  15. package/src/SpatializedStatic3DElement.test.ts +99 -0
  16. package/src/SpatializedStatic3DElement.ts +99 -1
  17. package/src/WebMsgCommand.ts +10 -0
  18. package/src/coverage-boost.test.ts +38 -119
  19. package/src/index.ts +3 -1
  20. package/src/jsbcommand.coverage.test.ts +19 -48
  21. package/src/platform-adapter/CommandResultUtils.ts +2 -2
  22. package/src/platform-adapter/createPlatformSync.ts +34 -0
  23. package/src/platform-adapter/index.ts +5 -51
  24. package/src/platform-adapter/interface.ts +35 -23
  25. package/src/platform-adapter/pico-os/PicoOSPlatform.ts +84 -52
  26. package/src/platform-adapter/puppeteer/PuppeteerPlatform.ts +37 -11
  27. package/src/platform-adapter/spatialSceneQuery.ts +17 -0
  28. package/src/platform-adapter/ssr/SSRPlatform.ts +24 -15
  29. package/src/platform-adapter/vision-os/VisionOSPlatform.ts +55 -24
  30. package/src/platform-runtime.ts +13 -0
  31. package/src/reality/Attachment.ts +2 -2
  32. package/src/reality/entity/SpatialEntity.ts +0 -2
  33. package/src/reality/realityCreator.ts +15 -1
  34. package/src/reality/resource/SpatialTextureResource.ts +16 -0
  35. package/src/reality/resource/index.ts +1 -0
  36. package/src/runtime/WebSpatialRuntimeError.ts +16 -0
  37. package/src/runtime/capability-data.ts +113 -0
  38. package/src/runtime/contract-review.test.ts +44 -0
  39. package/src/runtime/index.ts +28 -0
  40. package/src/runtime/jsbAdapterPlatform.test.ts +36 -0
  41. package/src/runtime/jsbAdapterPlatform.ts +51 -0
  42. package/src/runtime/keys.ts +129 -0
  43. package/src/runtime/semver.ts +33 -0
  44. package/src/runtime/supports.test.ts +207 -0
  45. package/src/runtime/supports.ts +110 -0
  46. package/src/runtime/types.ts +11 -0
  47. package/src/runtime/userAgent.ts +64 -0
  48. package/src/scene-polyfill.manifest.test.ts +7 -5
  49. package/src/scene-polyfill.test.ts +60 -0
  50. package/src/scene-polyfill.ts +23 -22
  51. package/src/spatial-host.ts +25 -0
  52. package/src/types/{global.d.ts → global.ts} +10 -5
  53. package/src/types/types.ts +9 -0
  54. package/src/platform-adapter/android/AndroidPlatform.ts +0 -133
@@ -0,0 +1,110 @@
1
+ import { CAPABILITY_TABLE, type CapabilityVersionRow } from './capability-data'
2
+ import { compareSemver, parseSemverOrNull } from './semver'
3
+ import type { WebSpatialRuntimeSnapshot } from './types'
4
+ import { computeRuntimeFromUserAgent } from './userAgent'
5
+ import {
6
+ type CapabilityKey,
7
+ isKnownSubToken,
8
+ isKnownTopLevel,
9
+ normalizeCapabilityName,
10
+ } from './keys'
11
+
12
+ /**
13
+ * Unsubstituted shell version placeholder from visionOS manifest / Xcode template
14
+ * (`manifest.swift`). When still present in `WSAppShell/<version>`, treat as debug
15
+ * build: {@link supports} returns true for every valid capability query.
16
+ */
17
+ export const VISIONOS_DEBUG_SHELL_VERSION_PLACEHOLDER = 'WS_SHELL_VERSION'
18
+
19
+ let runtimeCache: WebSpatialRuntimeSnapshot | undefined
20
+
21
+ /** Test helper: clear cached UA/runtime snapshot between Vitest cases. */
22
+ export function resetRuntimeCacheForTests(): void {
23
+ runtimeCache = undefined
24
+ }
25
+
26
+ /**
27
+ * Internal runtime snapshot (not part of the public `@webspatial/react-sdk` surface).
28
+ */
29
+ export function getRuntime(): WebSpatialRuntimeSnapshot {
30
+ if (runtimeCache !== undefined) return runtimeCache
31
+ if (typeof navigator === 'undefined') {
32
+ runtimeCache = { type: null, shellVersion: null }
33
+ return runtimeCache
34
+ }
35
+ runtimeCache = computeRuntimeFromUserAgent(navigator.userAgent)
36
+ return runtimeCache
37
+ }
38
+
39
+ function selectRow(
40
+ type: 'visionos' | 'picoos',
41
+ shellVersion: string,
42
+ ): CapabilityVersionRow | null {
43
+ const norm = parseSemverOrNull(shellVersion)
44
+ if (!norm) return null
45
+ const rows = CAPABILITY_TABLE[type]
46
+ if (!rows.length) return null
47
+ const sorted = [...rows].sort((a, b) => compareSemver(a.version, b.version))
48
+ const minV = sorted[0].version
49
+ if (compareSemver(norm, minV) < 0) return null
50
+
51
+ let chosen: CapabilityVersionRow | null = null
52
+ for (const row of sorted) {
53
+ if (compareSemver(row.version, norm) <= 0) {
54
+ chosen = row
55
+ } else {
56
+ break
57
+ }
58
+ }
59
+ return chosen
60
+ }
61
+
62
+ /**
63
+ * Public capability probe (`WebSpatialRuntime.supports` re-exports this from React SDK).
64
+ */
65
+ export function supports(
66
+ name: CapabilityKey,
67
+ tokens?: readonly string[],
68
+ ): boolean
69
+ export function supports(name: string, tokens?: readonly string[]): boolean
70
+ export function supports(name: string, tokens?: readonly string[]): boolean {
71
+ if (typeof name !== 'string') return false
72
+ const canonical = normalizeCapabilityName(name)
73
+ if (!isKnownTopLevel(canonical)) return false
74
+
75
+ const tokList =
76
+ tokens === undefined ? [] : Array.isArray(tokens) ? [...tokens] : []
77
+ if (tokList.some(t => typeof t !== 'string')) return false
78
+
79
+ for (const t of tokList) {
80
+ if (!isKnownSubToken(canonical, t)) return false
81
+ }
82
+
83
+ const rt = getRuntime()
84
+ if (rt.type === 'puppeteer') {
85
+ return true
86
+ }
87
+ if (rt.type === null) return false
88
+ if (rt.shellVersion === null) return false
89
+
90
+ if (
91
+ rt.type === 'visionos' &&
92
+ rt.shellVersion === VISIONOS_DEBUG_SHELL_VERSION_PLACEHOLDER
93
+ ) {
94
+ return true
95
+ }
96
+
97
+ const parsedShell = parseSemverOrNull(rt.shellVersion)
98
+ if (!parsedShell) return false
99
+
100
+ if (rt.type !== 'visionos' && rt.type !== 'picoos') return false
101
+
102
+ const row = selectRow(rt.type, parsedShell)
103
+ if (!row) return false
104
+
105
+ if (tokList.length === 0) {
106
+ return row.flags[canonical] === true
107
+ }
108
+ if (row.flags[canonical] !== true) return false
109
+ return tokList.every(t => row.flags[`${canonical}:${t}`] === true)
110
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Internal runtime classification (see OpenSpec `review.md`).
3
+ * `puppeteer`: automation / test harness UA — not a product matrix row; see `supports()`.
4
+ */
5
+ export type WebSpatialRuntimeType = 'visionos' | 'picoos' | 'puppeteer' | null
6
+
7
+ /** Snapshot returned by internal `getRuntime()` (not a public app API). */
8
+ export type WebSpatialRuntimeSnapshot = {
9
+ type: WebSpatialRuntimeType
10
+ shellVersion: string | null
11
+ }
@@ -0,0 +1,64 @@
1
+ import type { WebSpatialRuntimeSnapshot } from './types'
2
+
3
+ /**
4
+ * Parse shell token from UA:
5
+ * - packaged/hybrid runtimes use `WSAppShell/<version>`
6
+ * - browser-mode runtime on Pico OS uses `PicoWebApp/<version>`
7
+ *
8
+ * Keep `WSAppShell` precedence when both appear in one UA.
9
+ */
10
+ export function parseShellToken(ua: string): {
11
+ version: string | null
12
+ source: 'wsapp' | 'picoapp' | null
13
+ } {
14
+ const ws = /\bWSAppShell\/([\w.-]+)/i.exec(ua)
15
+ if (ws?.[1]) return { version: ws[1], source: 'wsapp' }
16
+ const pico = /\bPicoWebApp\/([\w.-]+)/i.exec(ua)
17
+ if (pico?.[1]) return { version: pico[1], source: 'picoapp' }
18
+ return { version: null, source: null }
19
+ }
20
+
21
+ /** Pico runtime: require explicit Pico tokens (do not treat generic `like Quest` VR UAs as picoos). */
22
+ function inferPicoOs(ua: string): boolean {
23
+ return /\bPicoWebApp\//i.test(ua) || /\bPicoBrowser\b/i.test(ua)
24
+ }
25
+
26
+ /** visionOS-class WebView UAs include a Mac OS X platform token; `WSAppShell` alone is not enough. */
27
+ function inferVisionOsFromUa(ua: string): boolean {
28
+ return /Mac OS X/i.test(ua)
29
+ }
30
+
31
+ /**
32
+ * Resolve internal runtime snapshot from `navigator.userAgent`.
33
+ * SSR / no navigator → `{ type: null, shellVersion: null }` (no throw).
34
+ */
35
+ export function computeRuntimeFromUserAgent(
36
+ userAgent: string | undefined,
37
+ ): WebSpatialRuntimeSnapshot {
38
+ if (userAgent === undefined || userAgent === '') {
39
+ return { type: null, shellVersion: null }
40
+ }
41
+ // Must run before the shell-token gate: Puppeteer UAs may omit WSAppShell/PicoWebApp.
42
+ if (userAgent.includes('Puppeteer')) {
43
+ const { version } = parseShellToken(userAgent)
44
+ return { type: 'puppeteer', shellVersion: version }
45
+ }
46
+
47
+ const { version, source } = parseShellToken(userAgent)
48
+ if (!version) {
49
+ return { type: null, shellVersion: null }
50
+ }
51
+
52
+ if (source === 'picoapp' || inferPicoOs(userAgent)) {
53
+ return { type: 'picoos', shellVersion: version }
54
+ }
55
+
56
+ if (source === 'wsapp') {
57
+ if (inferVisionOsFromUa(userAgent)) {
58
+ return { type: 'visionos', shellVersion: version }
59
+ }
60
+ return { type: null, shellVersion: version }
61
+ }
62
+
63
+ return { type: null, shellVersion: version }
64
+ }
@@ -1,12 +1,14 @@
1
1
  import { describe, it, expect, vi } from 'vitest'
2
2
 
3
+ vi.mock('./spatial-host', () => ({
4
+ openSpatialSceneSync: vi.fn().mockReturnValue({
5
+ success: true,
6
+ data: { id: 'scene-1', windowProxy: {} },
7
+ }),
8
+ }))
9
+
3
10
  vi.mock('./JSBCommand', () => {
4
11
  return {
5
- createSpatialSceneCommand: vi.fn().mockImplementation(() => ({
6
- executeSync: vi.fn().mockReturnValue({
7
- data: { id: 'scene-1', windowProxy: {} },
8
- }),
9
- })),
10
12
  FocusScene: vi.fn().mockImplementation(() => ({
11
13
  execute: vi.fn().mockResolvedValue(undefined),
12
14
  })),
@@ -4,6 +4,7 @@ import {
4
4
  initScene,
5
5
  injectSceneHook,
6
6
  __getSceneConfigSnapshotForTest,
7
+ hijackWindowATag,
7
8
  } from './scene-polyfill'
8
9
  import { SpatialSceneCreationOptions } from './types/types'
9
10
  import { pointToPhysical } from './physicalMetrics'
@@ -464,3 +465,62 @@ describe('initScene callback chaining', () => {
464
465
  expect(cb2).toHaveBeenCalledWith(firstReturn)
465
466
  })
466
467
  })
468
+
469
+ describe('hijackWindowATag', () => {
470
+ afterEach(() => {
471
+ document.body.innerHTML = ''
472
+ document.onclick = null
473
+ vi.restoreAllMocks()
474
+ })
475
+
476
+ it('handles clicks on nested elements inside anchor tags', () => {
477
+ const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
478
+ hijackWindowATag(window)
479
+
480
+ const anchor = document.createElement('a')
481
+ anchor.href = 'https://example.com/detail'
482
+ anchor.target = '_blank'
483
+
484
+ const image = document.createElement('img')
485
+ anchor.appendChild(image)
486
+ document.body.appendChild(anchor)
487
+
488
+ image.dispatchEvent(
489
+ new MouseEvent('click', { bubbles: true, cancelable: true }),
490
+ )
491
+
492
+ expect(openSpy).toHaveBeenCalledWith('https://example.com/detail', '_blank')
493
+ })
494
+ })
495
+
496
+ describe('hijackWindowATag – defaultPrevented', () => {
497
+ afterEach(() => {
498
+ document.body.innerHTML = ''
499
+ document.onclick = null
500
+ vi.restoreAllMocks()
501
+ })
502
+
503
+ it('does not open a new window when the click was already preventDefault-ed', () => {
504
+ const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
505
+ hijackWindowATag(window)
506
+
507
+ const anchor = document.createElement('a')
508
+ anchor.href = 'https://example.com/detail'
509
+ anchor.target = '_blank'
510
+
511
+ const span = document.createElement('span')
512
+ anchor.appendChild(span)
513
+ document.body.appendChild(anchor)
514
+
515
+ // Simulate an app-level handler that cancels the click before the polyfill sees it.
516
+ span.addEventListener('click', ev => {
517
+ ev.preventDefault()
518
+ })
519
+
520
+ span.dispatchEvent(
521
+ new MouseEvent('click', { bubbles: true, cancelable: true }),
522
+ )
523
+
524
+ expect(openSpy).not.toHaveBeenCalled()
525
+ })
526
+ })
@@ -1,4 +1,5 @@
1
- import { createSpatialSceneCommand, FocusScene } from './JSBCommand'
1
+ import { FocusScene } from './JSBCommand'
2
+ import { openSpatialSceneSync } from './spatial-host'
2
3
  import { SpatialScene } from './SpatialScene'
3
4
  import {
4
5
  SpatialSceneCreationOptions,
@@ -221,8 +222,12 @@ class SceneManager {
221
222
  cfg = { ...ans, type: 'window' }
222
223
  }
223
224
 
224
- const cmd = new createSpatialSceneCommand(url!, cfg, target, features)
225
- const result = cmd.executeSync()
225
+ const result = openSpatialSceneSync(
226
+ url!,
227
+ cfg as SpatialSceneCreationOptionsInternal,
228
+ target,
229
+ features,
230
+ )
226
231
 
227
232
  const id = result.data?.id
228
233
 
@@ -429,8 +434,6 @@ export function formatSceneConfig(
429
434
  // defaultSize should format into px if window
430
435
  // defaultSize should format into m if volume
431
436
 
432
- const defaultSceneConfig = getSceneDefaultConfig(sceneType)
433
-
434
437
  const errors: string[] = []
435
438
 
436
439
  const isWindow = sceneType === 'window'
@@ -523,23 +526,22 @@ export function hijackWindowOpen(window: WindowProxy) {
523
526
  export function hijackWindowATag(openedWindow: WindowProxy) {
524
527
  openedWindow!.document.onclick = function (e) {
525
528
  let element = e.target as HTMLElement | null
526
- let found = false
527
529
 
528
530
  // Look for <a> element in the clicked elements parents and if found override navigation behavior if needed
529
- while (!found) {
530
- if (element && element.tagName == 'A') {
531
+ while (element) {
532
+ if (element.tagName == 'A') {
531
533
  // When using libraries like react route's <Link> it sets an onclick event, when this happens we should do nothing and let that occur
532
534
 
533
535
  // if onClick is set for the element, the raw onclick will be noop() trapped so the onclick check is no longer trustable
534
536
  // we handle all the scenarios
535
537
 
536
- if (handleATag(e)) {
538
+ if (handleATag(e, element as HTMLAnchorElement)) {
537
539
  return false // prevent default action and stop event propagation
538
540
  }
539
541
 
540
542
  return true
541
543
  }
542
- if (element && element.parentElement) {
544
+ if (element.parentElement) {
543
545
  element = element.parentElement
544
546
  } else {
545
547
  break
@@ -548,18 +550,17 @@ export function hijackWindowATag(openedWindow: WindowProxy) {
548
550
  }
549
551
  }
550
552
 
551
- function handleATag(event: MouseEvent) {
552
- const targetElement = event.target as HTMLElement
553
- if (targetElement.tagName === 'A') {
554
- const link = targetElement as HTMLAnchorElement
555
- const target = link.target
556
- const url = link.href
557
-
558
- if (target && target !== '_self') {
559
- event.preventDefault()
560
- window.open(url, target)
561
- return true
562
- }
553
+ function handleATag(event: MouseEvent, link: HTMLAnchorElement) {
554
+ // Respect clicks that have already been cancelled (e.g. by app/router handlers).
555
+ if (event.defaultPrevented) return false
556
+ // Use the anchor found during bubbling so nested clicks like <a><img /></a> are handled correctly.
557
+ const target = link.target
558
+ const url = link.href
559
+
560
+ if (target && target !== '_self') {
561
+ event.preventDefault()
562
+ window.open(url, target)
563
+ return true
563
564
  }
564
565
  }
565
566
 
@@ -0,0 +1,25 @@
1
+ import type { WebSpatialProtocolResult } from './platform-adapter/interface'
2
+ import { getPlatform, getPlatformSync } from './platform-runtime'
3
+ import type { SpatialSceneCreationOptionsInternal } from './types/internal'
4
+ import type { AttachmentEntityOptions } from './types/types'
5
+
6
+ export async function createNativeSpatialDiv(): Promise<WebSpatialProtocolResult> {
7
+ const platform = await getPlatform()
8
+ return platform.createNativeSpatialDiv()
9
+ }
10
+
11
+ export async function createNativeAttachment(
12
+ options: AttachmentEntityOptions,
13
+ ): Promise<WebSpatialProtocolResult> {
14
+ const platform = await getPlatform()
15
+ return platform.createNativeAttachment(options)
16
+ }
17
+
18
+ export function openSpatialSceneSync(
19
+ url: string,
20
+ config: SpatialSceneCreationOptionsInternal | undefined,
21
+ target?: string,
22
+ features?: string,
23
+ ): WebSpatialProtocolResult {
24
+ return getPlatformSync().openSpatialSceneSync(url, config, target, features)
25
+ }
@@ -1,7 +1,8 @@
1
1
  import type { PhysicalMetricsValueShape } from '../physicalMetrics'
2
+ import type { SpatialSceneCreationOptions, SpatialSceneType } from './types'
2
3
 
3
4
  declare global {
4
- declare const __WEBSPATIAL_CORE_SDK_VERSION__: string
5
+ const __WEBSPATIAL_CORE_SDK_VERSION__: string
5
6
 
6
7
  interface Window {
7
8
  xrCurrentSceneType: SpatialSceneType
@@ -41,13 +42,17 @@ declare global {
41
42
  physicalMetrics?: PhysicalMetricsValueShape
42
43
  }
43
44
 
44
- xrInnerDepth: number
45
- xrOuterDepth: number
45
+ /** Present when `supports('xrInnerDepth')` is true; otherwise `undefined`. */
46
+ xrInnerDepth?: number
47
+ /** Present when `supports('xrOuterDepth')` is true; otherwise `undefined`. */
48
+ xrOuterDepth?: number
46
49
  }
47
50
 
48
51
  interface HTMLElement {
49
- xrOffsetBack: number
50
- xrClientDepth: number
52
+ /** Present when `supports('xrOffsetBack')` is true for element readbacks. */
53
+ xrOffsetBack?: number
54
+ /** Present when `supports('xrClientDepth')` is true for element readbacks. */
55
+ xrClientDepth?: number
51
56
  }
52
57
  }
53
58
 
@@ -100,6 +100,8 @@ export interface ModelSource {
100
100
  type?: string
101
101
  }
102
102
 
103
+ export type ModelLoadingMode = 'eager' | 'lazy'
104
+
103
105
  export interface SpatializedStatic3DElementProperties
104
106
  extends SpatializedElementProperties {
105
107
  modelURL: string
@@ -109,6 +111,9 @@ export interface SpatializedStatic3DElementProperties
109
111
  loop?: boolean
110
112
  animationPaused?: boolean
111
113
  playbackRate?: number
114
+ currentTime?: number
115
+ posterURL?: string
116
+ loading?: ModelLoadingMode
112
117
  }
113
118
 
114
119
  export interface SpatialSceneCreationOptions {
@@ -262,6 +267,10 @@ export interface SpatialUnlitMaterialOptions {
262
267
  opacity?: number
263
268
  }
264
269
 
270
+ export interface SpatialTextureResourceOptions {
271
+ url: string
272
+ }
273
+
265
274
  export interface ModelComponentOptions {
266
275
  mesh: SpatialGeometry
267
276
  materials: SpatialMaterial[]
@@ -1,133 +0,0 @@
1
- import { PlatformAbility, CommandResult } from '../interface'
2
- import {
3
- CommandResultFailure,
4
- CommandResultSuccess,
5
- } from '../CommandResultUtils'
6
- import { CheckWebViewCanCreateCommand } from '../../JSBCommand'
7
- import { SpatialWebEvent } from '../../SpatialWebEvent'
8
-
9
- interface JSBResponse {
10
- success: boolean
11
- data: any
12
- }
13
- type JSBError = {
14
- code: string
15
- message: string
16
- }
17
-
18
- let creatingElementCount = 0
19
-
20
- let requestId = 0
21
-
22
- const MAX_ID = 100000
23
-
24
- function nextRequestId() {
25
- requestId = (requestId + 1) % MAX_ID
26
- return `rId_${requestId}`
27
- }
28
-
29
- export class AndroidPlatform implements PlatformAbility {
30
- async callJSB(cmd: string, msg: string): Promise<CommandResult> {
31
- // android JS Bridge interface only support sync invoking
32
- // in order to implement promise API, register every request by requestId and remove when resolve/reject.
33
- return new Promise((resolve, reject) => {
34
- try {
35
- const rId = nextRequestId()
36
-
37
- SpatialWebEvent.addEventReceiver(rId, (result: JSBResponse) => {
38
- SpatialWebEvent.removeEventReceiver(rId)
39
- if (result.success) {
40
- resolve(CommandResultSuccess(result.data))
41
- } else {
42
- const { code, message } = result.data as JSBError
43
- resolve(CommandResultFailure(code, message))
44
- }
45
- })
46
-
47
- const ans = window.webspatialBridge.postMessage(rId, cmd, msg)
48
- if (ans !== '') {
49
- SpatialWebEvent.removeEventReceiver(rId)
50
- // sync call
51
- const result = JSON.parse(ans) as JSBResponse
52
- if (result.success) {
53
- resolve(CommandResultSuccess(result.data))
54
- } else {
55
- const { code, message } = result.data as JSBError
56
- resolve(CommandResultFailure(code, message))
57
- }
58
- }
59
- } catch (error: unknown) {
60
- console.error(
61
- `AndroidPlatform cmd: ${cmd}, msg: ${msg} error: ${error}`,
62
- )
63
- const { code, message } = error as JSBError
64
- resolve(CommandResultFailure(code, message))
65
- }
66
- })
67
- }
68
-
69
- async callWebSpatialProtocol(
70
- command: string,
71
- query?: string,
72
- target?: string,
73
- features?: string,
74
- ): Promise<CommandResult> {
75
- // Waiting for request to create spatial div
76
- await new Promise(resolve => setTimeout(resolve, 16 * creatingElementCount))
77
- // Count the current total number of created spatial div queues
78
- creatingElementCount++
79
- // Create a spatial div through JSB polling request
80
- let canCreate = await new CheckWebViewCanCreateCommand().execute()
81
- while (!canCreate.data.can) {
82
- await new Promise(resolve => setTimeout(resolve, 16))
83
- canCreate = await new CheckWebViewCanCreateCommand().execute()
84
- }
85
- // Request successful, call window.open
86
- const { windowProxy } = this.openWindow(command, query, target, features)
87
- // Polling waiting for windowProxy to convert into a real window object
88
- while (!windowProxy?.open) {
89
- await new Promise(resolve => setTimeout(resolve, 16))
90
- }
91
- // Make the page renderable through window.open
92
- windowProxy?.open('about:blank', '_self')
93
- // Polling to check if SpatialId injection is successful
94
- while (!windowProxy?.__SpatialId) {
95
- await new Promise(resolve => setTimeout(resolve, 16))
96
- }
97
- let spatialId = windowProxy?.__SpatialId
98
- creatingElementCount--
99
- return Promise.resolve(
100
- CommandResultSuccess({ windowProxy: windowProxy, id: spatialId }),
101
- )
102
- }
103
-
104
- callWebSpatialProtocolSync(
105
- command: string,
106
- query?: string,
107
- target?: string,
108
- features?: string,
109
- ): CommandResult {
110
- const { spatialId: id = '', windowProxy } = this.openWindow(
111
- command,
112
- query,
113
- target,
114
- features,
115
- )
116
-
117
- return CommandResultSuccess({ windowProxy, id })
118
- }
119
-
120
- private openWindow(
121
- command: string,
122
- query?: string,
123
- target?: string,
124
- features?: string,
125
- ) {
126
- const windowProxy = window.open(
127
- `webspatial://${command}?${query || ''}`,
128
- target,
129
- features,
130
- )
131
- return { spatialId: '', windowProxy }
132
- }
133
- }