@webspatial/core-sdk 1.0.5 → 1.2.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.
@@ -0,0 +1,133 @@
1
+ import { PlatformAbility, CommandResult } from '../interface'
2
+ import {
3
+ CommandResultFailure,
4
+ CommandResultSuccess,
5
+ } from '../CommandResultUtils'
6
+ import { SpatialWebEvent } from '../../SpatialWebEvent'
7
+
8
+ interface JSBResponse {
9
+ success: boolean
10
+ data: any
11
+ }
12
+ type JSBError = {
13
+ code: string
14
+ message: string
15
+ }
16
+
17
+ let requestId = 0
18
+
19
+ const MAX_ID = 100000
20
+
21
+ function nextRequestId() {
22
+ requestId = (requestId + 1) % MAX_ID
23
+ return `rId_${requestId}`
24
+ }
25
+
26
+ export class XRPlatform implements PlatformAbility {
27
+ async callJSB(cmd: string, msg: string): Promise<CommandResult> {
28
+ // android JS Bridge interface only support sync invoking
29
+ // in order to implement promise API, register every request by requestId and remove when resolve/reject.
30
+ return new Promise((resolve, reject) => {
31
+ try {
32
+ const rId = nextRequestId()
33
+
34
+ SpatialWebEvent.addEventReceiver(rId, (result: JSBResponse) => {
35
+ SpatialWebEvent.removeEventReceiver(rId)
36
+ if (result.success) {
37
+ resolve(CommandResultSuccess(result.data))
38
+ } else {
39
+ const { code, message } = result.data as JSBError
40
+ resolve(CommandResultFailure(code, message))
41
+ }
42
+ })
43
+
44
+ const ans = window.webspatialBridge.postMessage(rId, cmd, msg)
45
+ if (ans !== '') {
46
+ SpatialWebEvent.removeEventReceiver(rId)
47
+ // sync call
48
+ const result = JSON.parse(ans) as JSBResponse
49
+ if (result.success) {
50
+ resolve(CommandResultSuccess(result.data))
51
+ } else {
52
+ const { code, message } = result.data as JSBError
53
+ resolve(CommandResultFailure(code, message))
54
+ }
55
+ }
56
+ } catch (error: unknown) {
57
+ console.error(`XRPlatform cmd: ${cmd}, msg: ${msg} error: ${error}`)
58
+ const { code, message } = error as JSBError
59
+ resolve(CommandResultFailure(code, message))
60
+ }
61
+ })
62
+ }
63
+
64
+ async callWebSpatialProtocol(
65
+ command: string,
66
+ query?: string,
67
+ target?: string,
68
+ features?: string,
69
+ ): Promise<CommandResult> {
70
+ // Waiting for request to create spatial div
71
+ return new Promise((resolve, reject) => {
72
+ const createdId = nextRequestId()
73
+ try {
74
+ let windowProxy: any = null
75
+ SpatialWebEvent.addEventReceiver(
76
+ createdId,
77
+ (result: { spatialId: string }) => {
78
+ console.log('createdId', createdId, result.spatialId)
79
+ resolve(
80
+ CommandResultSuccess({
81
+ windowProxy: windowProxy,
82
+ id: result.spatialId,
83
+ }),
84
+ )
85
+ SpatialWebEvent.removeEventReceiver(createdId)
86
+ },
87
+ )
88
+ windowProxy = this.openWindow(
89
+ command,
90
+ query,
91
+ target,
92
+ features,
93
+ ).windowProxy
94
+ windowProxy?.open(`about:blank?rid=${createdId}`, '_self')
95
+ } catch (error: unknown) {
96
+ console.error(`open window error: ${error}`)
97
+ const { code, message } = error as JSBError
98
+ SpatialWebEvent.removeEventReceiver(createdId)
99
+ resolve(CommandResultFailure(code, message))
100
+ }
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
+ }
@@ -1,2 +1,2 @@
1
1
  export * from './SpatialComponent'
2
- export * from './ModelComponent'
2
+ export * from './ModelComponent'
@@ -164,13 +164,7 @@ export class SpatialEntity extends SpatialObject {
164
164
  this.dispatchEvent(evt)
165
165
  }
166
166
  // rotate
167
- else if (type === SpatialWebMsgType.spatialrotatestart) {
168
- const evt = createSpatialEvent(
169
- SpatialWebMsgType.spatialrotatestart,
170
- (data as SpatialRotateMsg).detail,
171
- )
172
- this.dispatchEvent(evt)
173
- } else if (type === SpatialWebMsgType.spatialrotate) {
167
+ else if (type === SpatialWebMsgType.spatialrotate) {
174
168
  const evt = createSpatialEvent(
175
169
  SpatialWebMsgType.spatialrotate,
176
170
  (data as SpatialRotateMsg).detail,
@@ -184,13 +178,7 @@ export class SpatialEntity extends SpatialObject {
184
178
  this.dispatchEvent(evt)
185
179
  }
186
180
  // magnify
187
- else if (type === SpatialWebMsgType.spatialmagnifystart) {
188
- const evt = createSpatialEvent(
189
- SpatialWebMsgType.spatialmagnifystart,
190
- (data as SpatialMagnifyMsg).detail,
191
- )
192
- this.dispatchEvent(evt)
193
- } else if (type === SpatialWebMsgType.spatialmagnify) {
181
+ else if (type === SpatialWebMsgType.spatialmagnify) {
194
182
  const evt = createSpatialEvent(
195
183
  SpatialWebMsgType.spatialmagnify,
196
184
  (data as SpatialMagnifyMsg).detail,
@@ -206,6 +194,10 @@ export class SpatialEntity extends SpatialObject {
206
194
  }
207
195
 
208
196
  dispatchEvent(evt: CustomEvent) {
197
+ // Set origin once at the first dispatch in the bubbling chain
198
+ if (!(evt as any).__origin) {
199
+ Object.defineProperty(evt, '__origin', { value: this, enumerable: false })
200
+ }
209
201
  this.events[evt.type]?.(evt)
210
202
  if (evt.bubbles && !evt.cancelBubble) {
211
203
  if (this.parent && this.parent instanceof SpatialEntity) {
@@ -1,2 +1,2 @@
1
1
  export * from './SpatialEntity'
2
- export * from './SpatialModelEntity'
2
+ export * from './SpatialModelEntity'
@@ -1,4 +1,7 @@
1
- import { SpatialBoxGeometryOptions, SpatialGeometryType } from '../../types/types'
1
+ import {
2
+ SpatialBoxGeometryOptions,
3
+ SpatialGeometryType,
4
+ } from '../../types/types'
2
5
  import { SpatialGeometry } from './SpatialGeometry'
3
6
 
4
7
  export class SpatialBoxGeometry extends SpatialGeometry {
@@ -3,4 +3,4 @@ export * from './SpatialBoxGeometry'
3
3
  export * from './SpatialSphereGeometry'
4
4
  export * from './SpatialCylinderGeometry'
5
5
  export * from './SpatialPlaneGeometry'
6
- export * from './SpatialConeGeometry'
6
+ export * from './SpatialConeGeometry'
@@ -1,2 +1,2 @@
1
1
  export * from './SpatialUnlitMaterial'
2
- export * from './SpatialMaterial'
2
+ export * from './SpatialMaterial'
@@ -1 +1 @@
1
- export * from './SpatialModelAsset';
1
+ export * from './SpatialModelAsset'
@@ -50,17 +50,37 @@ class SceneManager {
50
50
  return this.configMap[name]
51
51
  }
52
52
 
53
+ // Ensure URL is absolute; only convert when a relative path is provided
54
+ // - Keep external and special schemes untouched (http, https, data, blob, about, file, mailto, etc.)
55
+ // - Handle protocol-relative URLs (//example.com/path)
56
+ // - Resolve relative paths against document.baseURI (respects <base href>)
57
+ private ensureAbsoluteUrl(raw?: string): string | undefined {
58
+ if (!raw) return raw
59
+ // Already has a scheme (includes internal webspatial:// which is handled earlier)
60
+ if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(raw)) {
61
+ return raw
62
+ }
63
+ // Protocol-relative URL
64
+ if (raw.startsWith('//')) {
65
+ return `${window.location.protocol}${raw}`
66
+ }
67
+ // Resolve against base URI
68
+ try {
69
+ return new URL(raw, document.baseURI).toString()
70
+ } catch {
71
+ // Fallback: leave unchanged
72
+ return raw
73
+ }
74
+ }
75
+
53
76
  private open = (url?: string, target?: string, features?: string) => {
54
77
  // bypass internal
55
78
  if (url?.startsWith(INTERNAL_SCHEMA_PREFIX)) {
56
79
  return this.originalOpen(url, target, features)
57
80
  }
58
81
 
59
- // absolute url
60
- const prefix = `${window.location.protocol}//${window.location.host}`
61
- if (!url?.startsWith(prefix)) {
62
- url = prefix + url
63
- }
82
+ // Normalize only relative URLs to absolute for platform handling
83
+ url = this.ensureAbsoluteUrl(url)
64
84
 
65
85
  // if target is special
66
86
  if (target === '_self' || target === '_parent' || target === '_top') {
@@ -72,10 +92,6 @@ class SceneManager {
72
92
  const cmd = new createSpatialSceneCommand(url!, cfg, target, features)
73
93
  const result = cmd.executeSync()
74
94
 
75
- if (typeof target === 'string' && this.configMap[target]) {
76
- delete this.configMap[target]
77
- }
78
-
79
95
  const id = result.data?.id
80
96
 
81
97
  if (id) {
@@ -1,9 +1,9 @@
1
- import { SpatialSceneCreationOptions, SpatialSceneType } from "./types";
1
+ import { SpatialSceneCreationOptions, SpatialSceneType } from './types'
2
2
 
3
-
4
- export type SpatialSceneCreationOptionsInternal = SpatialSceneCreationOptions & {
5
- type: SpatialSceneType;
6
- };
3
+ export type SpatialSceneCreationOptionsInternal =
4
+ SpatialSceneCreationOptions & {
5
+ type: SpatialSceneType
6
+ }
7
7
  export enum SpatialSceneState {
8
8
  idle = 'idle',
9
9
  pending = 'pending',
@@ -11,6 +11,13 @@ export interface Vec3 {
11
11
 
12
12
  export type Point3D = Vec3
13
13
 
14
+ export interface Quaternion {
15
+ x: number
16
+ y: number
17
+ z: number
18
+ w: number
19
+ }
20
+
14
21
  /**
15
22
  * Material type for SpatialDiv or HTML document.
16
23
  *
@@ -69,10 +76,8 @@ export interface SpatializedElementProperties {
69
76
  enableDragStartGesture: boolean
70
77
  enableDragGesture: boolean
71
78
  enableDragEndGesture: boolean
72
- enableRotateStartGesture: boolean
73
79
  enableRotateGesture: boolean
74
80
  enableRotateEndGesture: boolean
75
- enableMagnifyStartGesture: boolean
76
81
  enableMagnifyGesture: boolean
77
82
  enableMagnifyEndGesture: boolean
78
83
  }
@@ -343,38 +348,39 @@ export interface SpatialTapEventDetail {
343
348
 
344
349
  export type SpatialTapEvent = CustomEvent<SpatialTapEventDetail>
345
350
 
346
- export interface SpatialDragEventDetail {
347
- location3D: Point3D
351
+ export interface SpatialDragStartEventDetail {
348
352
  startLocation3D: Point3D
353
+ }
354
+
355
+ export interface SpatialDragEventDetail {
349
356
  translation3D: Vec3
350
- predictedEndTranslation3D: Vec3
351
- predictedEndLocation3D: Point3D
352
- velocity: Size
353
357
  }
354
358
 
355
- export type SpatialDragEvent = CustomEvent<SpatialDragEventDetail>
359
+ export interface SpatialDragEndEventDetail {}
356
360
 
357
- export type SpatialDragEndEvent = SpatialDragEvent
361
+ export type SpatialDragStartEvent = CustomEvent<SpatialDragStartEventDetail>
362
+
363
+ export type SpatialDragEvent = CustomEvent<SpatialDragEventDetail>
358
364
 
365
+ export type SpatialDragEndEvent = CustomEvent<SpatialDragEndEventDetail>
359
366
  export interface SpatialRotateEventDetail {
360
- rotation: { vector: [number, number, number, number] }
361
- startAnchor3D: Vec3
362
- startLocation3D: Point3D
367
+ quaternion: Quaternion
363
368
  }
364
369
 
370
+ export interface SpatialRotateEndEventDetail {}
371
+
365
372
  export type SpatialRotateEvent = CustomEvent<SpatialRotateEventDetail>
366
373
 
367
- export type SpatialRotateEndEvent = SpatialRotateEvent
374
+ export type SpatialRotateEndEvent = CustomEvent<SpatialRotateEndEventDetail>
368
375
 
369
376
  export interface SpatialMagnifyEventDetail {
370
377
  magnification: number
371
- velocity: number
372
- startAnchor3D: Vec3
373
- startLocation3D: Point3D
374
378
  }
375
379
 
380
+ export interface SpatialMagnifyEndEventDetail {}
381
+
376
382
  export type SpatialMagnifyEvent = CustomEvent<SpatialMagnifyEventDetail>
377
383
 
378
- export type SpatialMagnifyEndEvent = SpatialMagnifyEvent
384
+ export type SpatialMagnifyEndEvent = CustomEvent<SpatialMagnifyEndEventDetail>
379
385
 
380
386
  export type SpatialEntityOrReality = SpatialEntity | SpatializedDynamic3DElement