@webspatial/core-sdk 1.6.1 → 2.0.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 (320) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +10 -0
  3. package/dist/JSBCommand.d.ts +398 -0
  4. package/dist/JSBCommand.js +668 -0
  5. package/dist/JSBCommand.js.map +1 -0
  6. package/dist/Spatial.d.ts +63 -0
  7. package/dist/Spatial.js +60 -0
  8. package/dist/Spatial.js.map +1 -0
  9. package/dist/SpatialObject.d.ts +19 -0
  10. package/dist/SpatialObject.js +44 -0
  11. package/dist/SpatialObject.js.map +1 -0
  12. package/dist/SpatialScene.d.ts +51 -0
  13. package/dist/SpatialScene.js +74 -0
  14. package/dist/SpatialScene.js.map +1 -0
  15. package/dist/SpatialSession.d.ts +143 -0
  16. package/dist/SpatialSession.js +173 -0
  17. package/dist/SpatialSession.js.map +1 -0
  18. package/dist/SpatialWebEvent.d.ts +8 -0
  19. package/dist/SpatialWebEvent.js +18 -0
  20. package/dist/SpatialWebEvent.js.map +1 -0
  21. package/dist/SpatialWebEventCreator.d.ts +11 -0
  22. package/dist/SpatialWebEventCreator.js +11 -0
  23. package/dist/SpatialWebEventCreator.js.map +1 -0
  24. package/dist/Spatialized2DElement.d.ts +42 -0
  25. package/dist/Spatialized2DElement.js +47 -0
  26. package/dist/Spatialized2DElement.js.map +1 -0
  27. package/dist/SpatializedDynamic3DElement.d.ts +7 -0
  28. package/dist/SpatializedDynamic3DElement.js +43 -0
  29. package/dist/SpatializedDynamic3DElement.js.map +1 -0
  30. package/dist/SpatializedElement.d.ts +7 -0
  31. package/dist/SpatializedElement.js +206 -0
  32. package/dist/SpatializedElement.js.map +1 -0
  33. package/dist/SpatializedElementCreator.d.ts +15 -0
  34. package/dist/SpatializedElementCreator.js +47 -0
  35. package/dist/SpatializedElementCreator.js.map +1 -0
  36. package/dist/SpatializedStatic3DElement.d.ts +195 -0
  37. package/dist/SpatializedStatic3DElement.js +327 -0
  38. package/dist/SpatializedStatic3DElement.js.map +1 -0
  39. package/dist/WebMsgCommand.d.ts +7 -0
  40. package/dist/WebMsgCommand.js +23 -0
  41. package/dist/WebMsgCommand.js.map +1 -0
  42. package/dist/index.d.ts +35 -1101
  43. package/dist/index.js +21 -3393
  44. package/dist/index.js.map +1 -1
  45. package/dist/install-polyfills.d.ts +2 -0
  46. package/dist/install-polyfills.js +13 -0
  47. package/dist/install-polyfills.js.map +1 -0
  48. package/dist/isSSREnv.d.ts +3 -0
  49. package/dist/isSSREnv.js +6 -0
  50. package/dist/isSSREnv.js.map +1 -0
  51. package/dist/motion/AnimationObject.d.ts +7 -0
  52. package/dist/motion/AnimationObject.js +140 -0
  53. package/dist/motion/AnimationObject.js.map +1 -0
  54. package/dist/motion/compute/index.d.ts +10 -0
  55. package/dist/motion/compute/index.js +19 -0
  56. package/dist/motion/compute/index.js.map +1 -0
  57. package/dist/motion/compute/motionPropertyValues.d.ts +19 -0
  58. package/dist/motion/compute/motionPropertyValues.js +50 -0
  59. package/dist/motion/compute/motionPropertyValues.js.map +1 -0
  60. package/dist/motion/compute/normalize.d.ts +31 -0
  61. package/dist/motion/compute/normalize.js +201 -0
  62. package/dist/motion/compute/normalize.js.map +1 -0
  63. package/dist/motion/compute/validate.d.ts +22 -0
  64. package/dist/motion/compute/validate.js +103 -0
  65. package/dist/motion/compute/validate.js.map +1 -0
  66. package/dist/motion/index.d.ts +8 -0
  67. package/dist/motion/index.js +6 -0
  68. package/dist/motion/index.js.map +1 -0
  69. package/dist/motion/native/serializeMotionTimeline.d.ts +12 -0
  70. package/dist/motion/native/serializeMotionTimeline.js +21 -0
  71. package/dist/motion/native/serializeMotionTimeline.js.map +1 -0
  72. package/dist/physicalMetrics-W5uZ5xKb.d.ts +48 -0
  73. package/dist/physicalMetrics.d.ts +1 -0
  74. package/dist/physicalMetrics.js +58 -0
  75. package/dist/physicalMetrics.js.map +1 -0
  76. package/dist/platform-adapter/CommandResultUtils.d.ts +12 -0
  77. package/dist/platform-adapter/CommandResultUtils.js +21 -0
  78. package/dist/platform-adapter/CommandResultUtils.js.map +1 -0
  79. package/dist/platform-adapter/createPlatformSync.d.ts +16 -0
  80. package/dist/platform-adapter/createPlatformSync.js +30 -0
  81. package/dist/platform-adapter/createPlatformSync.js.map +1 -0
  82. package/dist/platform-adapter/index.d.ts +12 -0
  83. package/dist/platform-adapter/index.js +10 -0
  84. package/dist/platform-adapter/index.js.map +1 -0
  85. package/dist/platform-adapter/interface.d.ts +7 -0
  86. package/dist/platform-adapter/interface.js +1 -0
  87. package/dist/platform-adapter/interface.js.map +1 -0
  88. package/dist/platform-adapter/pico-os/PicoOSPlatform.d.ts +25 -0
  89. package/dist/platform-adapter/pico-os/PicoOSPlatform.js +168 -0
  90. package/dist/platform-adapter/pico-os/PicoOSPlatform.js.map +1 -0
  91. package/dist/platform-adapter/puppeteer/PuppeteerPlatform.d.ts +66 -0
  92. package/dist/platform-adapter/puppeteer/PuppeteerPlatform.js +372 -0
  93. package/dist/platform-adapter/puppeteer/PuppeteerPlatform.js.map +1 -0
  94. package/dist/platform-adapter/spatialRequestMetadata.d.ts +6 -0
  95. package/dist/platform-adapter/spatialRequestMetadata.js +39 -0
  96. package/dist/platform-adapter/spatialRequestMetadata.js.map +1 -0
  97. package/dist/platform-adapter/spatialSceneQuery.d.ts +12 -0
  98. package/dist/platform-adapter/spatialSceneQuery.js +12 -0
  99. package/dist/platform-adapter/spatialSceneQuery.js.map +1 -0
  100. package/dist/platform-adapter/vision-os/VisionOSPlatform.d.ts +19 -0
  101. package/dist/platform-adapter/vision-os/VisionOSPlatform.js +77 -0
  102. package/dist/platform-adapter/vision-os/VisionOSPlatform.js.map +1 -0
  103. package/dist/platform-runtime.d.ts +12 -0
  104. package/dist/platform-runtime.js +14 -0
  105. package/dist/platform-runtime.js.map +1 -0
  106. package/dist/reality/Attachment.d.ts +19 -0
  107. package/dist/reality/Attachment.js +39 -0
  108. package/dist/reality/Attachment.js.map +1 -0
  109. package/dist/reality/component/ModelComponent.d.ts +14 -0
  110. package/dist/reality/component/ModelComponent.js +11 -0
  111. package/dist/reality/component/ModelComponent.js.map +1 -0
  112. package/dist/reality/component/SpatialComponent.d.ts +8 -0
  113. package/dist/reality/component/SpatialComponent.js +19 -0
  114. package/dist/reality/component/SpatialComponent.js.map +1 -0
  115. package/dist/reality/component/index.d.ts +8 -0
  116. package/dist/reality/component/index.js +3 -0
  117. package/dist/reality/component/index.js.map +1 -0
  118. package/dist/reality/entity/SpatialEntity.d.ts +7 -0
  119. package/dist/reality/entity/SpatialEntity.js +291 -0
  120. package/dist/reality/entity/SpatialEntity.js.map +1 -0
  121. package/dist/reality/entity/SpatialModelEntity.d.ts +17 -0
  122. package/dist/reality/entity/SpatialModelEntity.js +17 -0
  123. package/dist/reality/entity/SpatialModelEntity.js.map +1 -0
  124. package/dist/reality/entity/index.d.ts +8 -0
  125. package/dist/reality/entity/index.js +3 -0
  126. package/dist/reality/entity/index.js.map +1 -0
  127. package/dist/reality/geometry/SpatialBoxGeometry.d.ts +16 -0
  128. package/dist/reality/geometry/SpatialBoxGeometry.js +13 -0
  129. package/dist/reality/geometry/SpatialBoxGeometry.js.map +1 -0
  130. package/dist/reality/geometry/SpatialConeGeometry.d.ts +16 -0
  131. package/dist/reality/geometry/SpatialConeGeometry.js +13 -0
  132. package/dist/reality/geometry/SpatialConeGeometry.js.map +1 -0
  133. package/dist/reality/geometry/SpatialCylinderGeometry.d.ts +16 -0
  134. package/dist/reality/geometry/SpatialCylinderGeometry.js +13 -0
  135. package/dist/reality/geometry/SpatialCylinderGeometry.js.map +1 -0
  136. package/dist/reality/geometry/SpatialGeometry.d.ts +7 -0
  137. package/dist/reality/geometry/SpatialGeometry.js +13 -0
  138. package/dist/reality/geometry/SpatialGeometry.js.map +1 -0
  139. package/dist/reality/geometry/SpatialPlaneGeometry.d.ts +16 -0
  140. package/dist/reality/geometry/SpatialPlaneGeometry.js +13 -0
  141. package/dist/reality/geometry/SpatialPlaneGeometry.js.map +1 -0
  142. package/dist/reality/geometry/SpatialSphereGeometry.d.ts +16 -0
  143. package/dist/reality/geometry/SpatialSphereGeometry.js +13 -0
  144. package/dist/reality/geometry/SpatialSphereGeometry.js.map +1 -0
  145. package/dist/reality/geometry/index.d.ts +12 -0
  146. package/dist/reality/geometry/index.js +7 -0
  147. package/dist/reality/geometry/index.js.map +1 -0
  148. package/dist/reality/index.d.ts +18 -0
  149. package/dist/reality/index.js +7 -0
  150. package/dist/reality/index.js.map +1 -0
  151. package/dist/reality/material/SpatialMaterial.d.ts +7 -0
  152. package/dist/reality/material/SpatialMaterial.js +13 -0
  153. package/dist/reality/material/SpatialMaterial.js.map +1 -0
  154. package/dist/reality/material/SpatialUnlitMaterial.d.ts +16 -0
  155. package/dist/reality/material/SpatialUnlitMaterial.js +16 -0
  156. package/dist/reality/material/SpatialUnlitMaterial.js.map +1 -0
  157. package/dist/reality/material/index.d.ts +8 -0
  158. package/dist/reality/material/index.js +3 -0
  159. package/dist/reality/material/index.js.map +1 -0
  160. package/dist/reality/realityCreator.d.ts +22 -0
  161. package/dist/reality/realityCreator.js +89 -0
  162. package/dist/reality/realityCreator.js.map +1 -0
  163. package/dist/reality/resource/SpatialModelAsset.d.ts +15 -0
  164. package/dist/reality/resource/SpatialModelAsset.js +12 -0
  165. package/dist/reality/resource/SpatialModelAsset.js.map +1 -0
  166. package/dist/reality/resource/SpatialTextureResource.d.ts +16 -0
  167. package/dist/reality/resource/SpatialTextureResource.js +16 -0
  168. package/dist/reality/resource/SpatialTextureResource.js.map +1 -0
  169. package/dist/reality/resource/index.d.ts +9 -0
  170. package/dist/reality/resource/index.js +3 -0
  171. package/dist/reality/resource/index.js.map +1 -0
  172. package/dist/runtime/WebSpatialRuntimeError.d.ts +10 -0
  173. package/dist/runtime/WebSpatialRuntimeError.js +14 -0
  174. package/dist/runtime/WebSpatialRuntimeError.js.map +1 -0
  175. package/dist/runtime/capability-data.d.ts +12 -0
  176. package/dist/runtime/capability-data.js +134 -0
  177. package/dist/runtime/capability-data.js.map +1 -0
  178. package/dist/runtime/index.d.ts +8 -0
  179. package/dist/runtime/index.js +27 -0
  180. package/dist/runtime/index.js.map +1 -0
  181. package/dist/runtime/jsbAdapterPlatform.d.ts +9 -0
  182. package/dist/runtime/jsbAdapterPlatform.js +38 -0
  183. package/dist/runtime/jsbAdapterPlatform.js.map +1 -0
  184. package/dist/runtime/keys.d.ts +34 -0
  185. package/dist/runtime/keys.js +122 -0
  186. package/dist/runtime/keys.js.map +1 -0
  187. package/dist/runtime/semver.d.ts +8 -0
  188. package/dist/runtime/semver.js +32 -0
  189. package/dist/runtime/semver.js.map +1 -0
  190. package/dist/runtime/supports.d.ts +22 -0
  191. package/dist/runtime/supports.js +78 -0
  192. package/dist/runtime/supports.js.map +1 -0
  193. package/dist/runtime/types.d.ts +12 -0
  194. package/dist/runtime/types.js +1 -0
  195. package/dist/runtime/types.js.map +1 -0
  196. package/dist/runtime/userAgent.d.ts +20 -0
  197. package/dist/runtime/userAgent.js +41 -0
  198. package/dist/runtime/userAgent.js.map +1 -0
  199. package/dist/scene-polyfill.d.ts +18 -0
  200. package/dist/scene-polyfill.js +480 -0
  201. package/dist/scene-polyfill.js.map +1 -0
  202. package/dist/spatial-host.d.ts +13 -0
  203. package/dist/spatial-host.js +18 -0
  204. package/dist/spatial-host.js.map +1 -0
  205. package/dist/spatial-window-polyfill.d.ts +3 -0
  206. package/dist/spatial-window-polyfill.js +155 -0
  207. package/dist/spatial-window-polyfill.js.map +1 -0
  208. package/dist/spatializedElementMotion-_0XWPQCE.d.ts +1009 -0
  209. package/dist/types/animation.d.ts +7 -0
  210. package/dist/types/animation.js +1 -0
  211. package/dist/types/animation.js.map +1 -0
  212. package/dist/types/global.d.ts +38 -0
  213. package/dist/types/global.js +1 -0
  214. package/dist/types/global.js.map +1 -0
  215. package/dist/types/internal.d.ts +7 -0
  216. package/dist/types/internal.js +12 -0
  217. package/dist/types/internal.js.map +1 -0
  218. package/dist/types/motion/index.d.ts +7 -0
  219. package/dist/types/motion/index.js +3 -0
  220. package/dist/types/motion/index.js.map +1 -0
  221. package/dist/types/motion/spatializedElementMotion.d.ts +7 -0
  222. package/dist/types/motion/spatializedElementMotion.js +1 -0
  223. package/dist/types/motion/spatializedElementMotion.js.map +1 -0
  224. package/dist/types/motion/spatializedMotion.d.ts +7 -0
  225. package/dist/types/motion/spatializedMotion.js +16 -0
  226. package/dist/types/motion/spatializedMotion.js.map +1 -0
  227. package/dist/types/motion/spatializedPlayback.d.ts +11 -0
  228. package/dist/types/motion/spatializedPlayback.js +1 -0
  229. package/dist/types/motion/spatializedPlayback.js.map +1 -0
  230. package/dist/types/motion/spatializedVisual.d.ts +35 -0
  231. package/dist/types/motion/spatializedVisual.js +1 -0
  232. package/dist/types/motion/spatializedVisual.js.map +1 -0
  233. package/dist/types/types.d.ts +7 -0
  234. package/dist/types/types.js +117 -0
  235. package/dist/types/types.js.map +1 -0
  236. package/dist/utils.d.ts +44 -0
  237. package/dist/utils.js +86 -0
  238. package/dist/utils.js.map +1 -0
  239. package/package.json +20 -4
  240. package/src/JSBCommand.ts +148 -91
  241. package/src/Spatial.ts +0 -21
  242. package/src/SpatialObject.ts +10 -0
  243. package/src/SpatialScene.ts +1 -5
  244. package/src/SpatialSession.ts +17 -3
  245. package/src/Spatialized2DElement.ts +5 -0
  246. package/src/SpatializedDynamic3DElement.ts +5 -1
  247. package/src/SpatializedElement.ts +29 -0
  248. package/src/SpatializedElementCreator.ts +6 -4
  249. package/src/SpatializedStatic3DElement.test.ts +99 -0
  250. package/src/SpatializedStatic3DElement.ts +141 -4
  251. package/src/WebMsgCommand.ts +50 -0
  252. package/src/coverage-boost.test.ts +258 -316
  253. package/src/index.ts +28 -12
  254. package/src/install-polyfills.ts +38 -0
  255. package/src/jsbcommand.coverage.test.ts +42 -48
  256. package/src/motion/AnimationObject.test.ts +421 -0
  257. package/src/motion/AnimationObject.ts +208 -0
  258. package/src/motion/compute/index.ts +10 -0
  259. package/src/motion/compute/motionPropertyValues.test.ts +55 -0
  260. package/src/motion/compute/motionPropertyValues.ts +70 -0
  261. package/src/motion/compute/normalize.test.ts +99 -0
  262. package/src/motion/compute/normalize.ts +289 -0
  263. package/src/motion/compute/validate.test.ts +229 -0
  264. package/src/motion/compute/validate.ts +156 -0
  265. package/src/motion/index.ts +2 -0
  266. package/src/motion/native/serializeMotionTimeline.test.ts +61 -0
  267. package/src/motion/native/serializeMotionTimeline.ts +27 -0
  268. package/src/platform-adapter/CommandResultUtils.ts +2 -2
  269. package/src/platform-adapter/createPlatformSync.ts +36 -0
  270. package/src/platform-adapter/index.ts +5 -51
  271. package/src/platform-adapter/interface.ts +35 -23
  272. package/src/platform-adapter/pico-os/PicoOSPlatform.ts +156 -59
  273. package/src/platform-adapter/puppeteer/PuppeteerPlatform.ts +37 -11
  274. package/src/platform-adapter/spatialRequestMetadata.test.ts +175 -0
  275. package/src/platform-adapter/spatialRequestMetadata.ts +48 -0
  276. package/src/platform-adapter/spatialSceneQuery.ts +17 -0
  277. package/src/platform-adapter/vision-os/VisionOSPlatform.ts +60 -24
  278. package/src/platform-runtime.ts +13 -0
  279. package/src/reality/Attachment.ts +2 -2
  280. package/src/reality/entity/SpatialEntity.ts +127 -2
  281. package/src/reality/realityCreator.ts +15 -1
  282. package/src/reality/resource/SpatialTextureResource.ts +16 -0
  283. package/src/reality/resource/index.ts +1 -0
  284. package/src/runtime/WebSpatialRuntimeError.ts +16 -0
  285. package/src/runtime/capability-data.ts +213 -0
  286. package/src/runtime/contract-review.test.ts +44 -0
  287. package/src/runtime/index.ts +17 -0
  288. package/src/runtime/jsbAdapterPlatform.test.ts +36 -0
  289. package/src/runtime/jsbAdapterPlatform.ts +51 -0
  290. package/src/runtime/keys.ts +140 -0
  291. package/src/runtime/semver.ts +33 -0
  292. package/src/runtime/supports.test.ts +346 -0
  293. package/src/runtime/supports.ts +112 -0
  294. package/src/runtime/types.ts +11 -0
  295. package/src/runtime/useAnimation.capability.test.ts +275 -0
  296. package/src/runtime/userAgent.ts +64 -0
  297. package/src/scene-polyfill.manifest.test.ts +312 -9
  298. package/src/scene-polyfill.request-metadata.test.ts +39 -0
  299. package/src/scene-polyfill.test.ts +33 -94
  300. package/src/scene-polyfill.ts +125 -92
  301. package/src/spatial-host.ts +25 -0
  302. package/src/spatial-window-polyfill.ts +18 -2
  303. package/src/transform-roundtrip.test.ts +90 -0
  304. package/src/types/animation.ts +224 -0
  305. package/src/types/{global.d.ts → global.ts} +10 -12
  306. package/src/types/motion/index.ts +10 -0
  307. package/src/types/motion/spatializedElementMotion.ts +49 -0
  308. package/src/types/motion/spatializedMotion.ts +147 -0
  309. package/src/types/motion/spatializedPlayback.ts +17 -0
  310. package/src/types/motion/spatializedVisual.ts +24 -0
  311. package/src/types/types.ts +12 -0
  312. package/src/utils.ts +54 -0
  313. package/tsup.config.ts +61 -36
  314. package/vitest.config.ts +5 -1
  315. package/dist/iife/index.d.ts +0 -1101
  316. package/dist/iife/index.global.js +0 -75
  317. package/dist/iife/index.global.js.map +0 -1
  318. package/src/platform-adapter/android/AndroidPlatform.ts +0 -133
  319. package/src/platform-adapter/ssr/SSRPlatform.ts +0 -43
  320. /package/src/{ssr-polyfill.ts → isSSREnv.ts} +0 -0
@@ -0,0 +1,175 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest'
2
+
3
+ describe('spatial request metadata', () => {
4
+ afterEach(() => {
5
+ vi.useRealTimers()
6
+ vi.restoreAllMocks()
7
+ vi.unstubAllGlobals()
8
+ vi.resetModules()
9
+ delete window.__webspatialsdk__
10
+ })
11
+
12
+ it('builds opaque request ids and includes page epoch when available', async () => {
13
+ window.__webspatialsdk__ = { pageEpoch: 7 }
14
+ const { buildSpatialRequestQuery, createSpatialRequestId } = await import(
15
+ './spatialRequestMetadata'
16
+ )
17
+
18
+ const first = createSpatialRequestId()
19
+ const second = createSpatialRequestId()
20
+ expect(first).not.toBe(second)
21
+
22
+ const params = new URLSearchParams(buildSpatialRequestQuery(first))
23
+ expect(params.get('rid')).toBe(first)
24
+ expect(params.get('wsepoch')).toBe('7')
25
+ })
26
+
27
+ it('emits request id without epoch when host state is unavailable', async () => {
28
+ const { buildSpatialRequestQuery, createSpatialRequestId } = await import(
29
+ './spatialRequestMetadata'
30
+ )
31
+
32
+ const requestId = createSpatialRequestId()
33
+ const params = new URLSearchParams(buildSpatialRequestQuery(requestId))
34
+ expect(params.get('rid')).toBe(requestId)
35
+ expect(params.get('wsepoch')).toBeNull()
36
+ })
37
+
38
+ it('builds refresh-safe request ids across module reinitialization', async () => {
39
+ const nonceValues = [
40
+ [1, 2],
41
+ [3, 4],
42
+ ]
43
+ const getRandomValues = vi.fn((values: Uint32Array) => {
44
+ const next = nonceValues.shift() ?? [5, 6]
45
+ values[0] = next[0]
46
+ values[1] = next[1]
47
+ return values
48
+ })
49
+
50
+ vi.stubGlobal('crypto', { getRandomValues })
51
+
52
+ const firstModule = await import('./spatialRequestMetadata')
53
+ const first = firstModule.createSpatialRequestId()
54
+
55
+ vi.resetModules()
56
+
57
+ const secondModule = await import('./spatialRequestMetadata')
58
+ const second = secondModule.createSpatialRequestId()
59
+
60
+ expect(first).toMatch(/^wsreq_/)
61
+ expect(second).toMatch(/^wsreq_/)
62
+ expect(first).not.toBe(second)
63
+ })
64
+ })
65
+
66
+ describe('PicoOSPlatform SpatialDiv request correlation', () => {
67
+ afterEach(() => {
68
+ vi.useRealTimers()
69
+ vi.restoreAllMocks()
70
+ vi.resetModules()
71
+ delete window.__webspatialsdk__
72
+ delete (window as any).__SpatialWebEvent
73
+ })
74
+
75
+ it('opens SpatialDiv protocol with rid and wsepoch', async () => {
76
+ window.__webspatialsdk__ = { pageEpoch: '3' }
77
+ const { SpatialWebEvent } = await import('../SpatialWebEvent')
78
+ SpatialWebEvent.init()
79
+ const open = vi.fn(
80
+ () =>
81
+ ({
82
+ document: {
83
+ readyState: 'complete',
84
+ addEventListener: vi.fn(),
85
+ removeEventListener: vi.fn(),
86
+ },
87
+ }) as unknown as Window,
88
+ )
89
+ vi.spyOn(window, 'open').mockImplementation(open)
90
+
91
+ const { PicoOSPlatform } = await import('./pico-os/PicoOSPlatform')
92
+ const platform = new PicoOSPlatform()
93
+ const pending = platform.createNativeSpatialDiv()
94
+
95
+ const openedUrl = (open.mock.calls[0] as unknown[])[0] as string
96
+ const url = new URL(openedUrl)
97
+ const requestId = url.searchParams.get('rid')
98
+ expect(url.protocol).toBe('webspatial:')
99
+ expect(url.host).toBe('createSpatialized2DElement')
100
+ expect(requestId).toBeTruthy()
101
+ expect(url.searchParams.get('wsepoch')).toBe('3')
102
+
103
+ window.__SpatialWebEvent({
104
+ id: requestId!,
105
+ data: { spatialId: 'spatial-1' },
106
+ })
107
+
108
+ await expect(pending).resolves.toMatchObject({
109
+ success: true,
110
+ data: { id: 'spatial-1' },
111
+ })
112
+ expect(SpatialWebEvent.eventReceiver[requestId!]).toBeUndefined()
113
+ })
114
+
115
+ it('opens attachment protocol with rid and wsepoch', async () => {
116
+ window.__webspatialsdk__ = { pageEpoch: '4' }
117
+ const { SpatialWebEvent } = await import('../SpatialWebEvent')
118
+ SpatialWebEvent.init()
119
+ const open = vi.fn(
120
+ () =>
121
+ ({
122
+ document: {
123
+ readyState: 'complete',
124
+ addEventListener: vi.fn(),
125
+ removeEventListener: vi.fn(),
126
+ },
127
+ }) as unknown as Window,
128
+ )
129
+ vi.spyOn(window, 'open').mockImplementation(open)
130
+
131
+ const { PicoOSPlatform } = await import('./pico-os/PicoOSPlatform')
132
+ const platform = new PicoOSPlatform()
133
+ const pending = platform.createNativeAttachment()
134
+
135
+ const openedUrl = (open.mock.calls[0] as unknown[])[0] as string
136
+ const url = new URL(openedUrl)
137
+ const requestId = url.searchParams.get('rid')
138
+ expect(url.protocol).toBe('webspatial:')
139
+ expect(url.host).toBe('createAttachment')
140
+ expect(requestId).toBeTruthy()
141
+ expect(url.searchParams.get('wsepoch')).toBe('4')
142
+
143
+ window.__SpatialWebEvent({
144
+ id: requestId!,
145
+ data: { spatialId: 'attachment-1' },
146
+ })
147
+
148
+ await expect(pending).resolves.toMatchObject({
149
+ success: true,
150
+ data: { id: 'attachment-1' },
151
+ })
152
+ expect(SpatialWebEvent.eventReceiver[requestId!]).toBeUndefined()
153
+ })
154
+
155
+ it('times out pending SpatialDiv requests', async () => {
156
+ vi.useFakeTimers()
157
+ vi.spyOn(window, 'open').mockImplementation(() => null)
158
+ const { SpatialWebEvent } = await import('../SpatialWebEvent')
159
+
160
+ const { PicoOSPlatform } = await import('./pico-os/PicoOSPlatform')
161
+ const { DEFAULT_SPATIAL_REQUEST_TIMEOUT_MS } = await import(
162
+ './spatialRequestMetadata'
163
+ )
164
+
165
+ const platform = new PicoOSPlatform()
166
+ const pending = platform.createNativeSpatialDiv()
167
+ await vi.advanceTimersByTimeAsync(DEFAULT_SPATIAL_REQUEST_TIMEOUT_MS)
168
+
169
+ await expect(pending).resolves.toMatchObject({
170
+ success: false,
171
+ errorCode: 'E_SPATIAL_REQUEST_TIMEOUT',
172
+ })
173
+ expect(Object.keys(SpatialWebEvent.eventReceiver)).toHaveLength(0)
174
+ })
175
+ })
@@ -0,0 +1,48 @@
1
+ const REQUEST_ID_PREFIX = 'wsreq'
2
+
3
+ export const DEFAULT_SPATIAL_REQUEST_TIMEOUT_MS = 30_000
4
+
5
+ let requestSequence = 0
6
+
7
+ // One nonce per JS execution context keeps request ids refresh-safe without
8
+ // exposing host-specific lifecycle details in the protocol surface.
9
+ const contextNonce = createContextNonce()
10
+
11
+ function createContextNonce(): string {
12
+ const cryptoApi = globalThis.crypto
13
+ if (cryptoApi?.getRandomValues) {
14
+ const values = new Uint32Array(2)
15
+ cryptoApi.getRandomValues(values)
16
+ return Array.from(values, value => value.toString(36)).join('')
17
+ }
18
+
19
+ return Math.random().toString(36).slice(2, 12)
20
+ }
21
+
22
+ export function createSpatialRequestId(): string {
23
+ requestSequence += 1
24
+ return `${REQUEST_ID_PREFIX}_${contextNonce}_${requestSequence}`
25
+ }
26
+
27
+ export function getCurrentPageEpoch(): string | undefined {
28
+ const pageEpoch = window.__webspatialsdk__?.pageEpoch
29
+ if (pageEpoch === undefined || pageEpoch === null || pageEpoch === '') {
30
+ return undefined
31
+ }
32
+
33
+ return String(pageEpoch)
34
+ }
35
+
36
+ export function buildSpatialRequestQuery(
37
+ requestId: string,
38
+ pageEpoch = getCurrentPageEpoch(),
39
+ ): string {
40
+ const params = new URLSearchParams()
41
+ params.set('rid', requestId)
42
+
43
+ if (pageEpoch !== undefined) {
44
+ params.set('wsepoch', pageEpoch)
45
+ }
46
+
47
+ return params.toString()
48
+ }
@@ -0,0 +1,17 @@
1
+ import type { SpatialSceneCreationOptionsInternal } from '../types/internal'
2
+
3
+ /** Build query string for webspatial://createSpatialScene (url + config). */
4
+ export function buildSpatialSceneQuery(
5
+ url: string,
6
+ config: SpatialSceneCreationOptionsInternal | undefined,
7
+ ): string {
8
+ const params: Record<string, any> = { url, config }
9
+ return Object.keys(params)
10
+ .map(key => {
11
+ const value = params[key]
12
+ const finalValue =
13
+ typeof value === 'object' ? JSON.stringify(value) : value
14
+ return `${key}=${encodeURIComponent(finalValue)}`
15
+ })
16
+ .join('&')
17
+ }
@@ -1,13 +1,28 @@
1
- import { PlatformAbility, CommandResult } from '../interface'
1
+ import {
2
+ PlatformAbility,
3
+ CommandResult,
4
+ WebSpatialProtocolResult,
5
+ } from '../interface'
2
6
  import {
3
7
  CommandResultFailure,
4
8
  CommandResultSuccess,
5
9
  } from '../CommandResultUtils'
10
+ import { buildSpatialSceneQuery } from '../spatialSceneQuery'
11
+ import type { SpatialSceneCreationOptionsInternal } from '../../types/internal'
12
+ import type { AttachmentEntityOptions } from '../../types/types'
13
+ import {
14
+ buildSpatialRequestQuery,
15
+ createSpatialRequestId,
16
+ } from '../spatialRequestMetadata'
6
17
 
7
18
  type JSBError = {
19
+ code?: string
8
20
  message: string
9
21
  }
10
22
 
23
+ const UUID_RE =
24
+ /\b([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})\b/gi
25
+
11
26
  export class VisionOSPlatform implements PlatformAbility {
12
27
  async callJSB(cmd: string, msg: string): Promise<CommandResult> {
13
28
  try {
@@ -17,41 +32,48 @@ export class VisionOSPlatform implements PlatformAbility {
17
32
  return CommandResultSuccess(result)
18
33
  } catch (error: unknown) {
19
34
  // console.error(`VisionOSPlatform cmd: ${cmd}, msg: ${msg} error: ${error}`)
20
- const { code, message } = JSON.parse((error as JSBError).message)
35
+ const { code, message } = this.parseJSBError(error)
21
36
  return CommandResultFailure(code, message)
22
37
  }
23
38
  }
24
39
 
25
- callWebSpatialProtocol(
26
- command: string,
27
- query?: string,
40
+ openSpatialSceneSync(
41
+ url: string,
42
+ config: SpatialSceneCreationOptionsInternal | undefined,
28
43
  target?: string,
29
44
  features?: string,
30
- ): Promise<CommandResult> {
31
- const { spatialId: id, windowProxy } = this.openWindow(
32
- command,
45
+ ): WebSpatialProtocolResult {
46
+ const query = buildSpatialSceneQuery(url, config)
47
+ const { spatialId: id = '', windowProxy } = this.openWindow(
48
+ 'createSpatialScene',
33
49
  query,
34
50
  target,
35
51
  features,
36
52
  )
37
- return Promise.resolve(
38
- CommandResultSuccess({ windowProxy: windowProxy, id }),
39
- )
53
+
54
+ return CommandResultSuccess({ windowProxy, id })
40
55
  }
41
56
 
42
- callWebSpatialProtocolSync(
57
+ createNativeSpatialDiv(): Promise<WebSpatialProtocolResult> {
58
+ return this.openProtocolAsync('createSpatialized2DElement')
59
+ }
60
+
61
+ createNativeAttachment(
62
+ _options?: AttachmentEntityOptions,
63
+ ): Promise<WebSpatialProtocolResult> {
64
+ return this.openProtocolAsync('createAttachment')
65
+ }
66
+
67
+ private async openProtocolAsync(
43
68
  command: string,
44
- query?: string,
45
- target?: string,
46
- features?: string,
47
- ): CommandResult {
69
+ ): Promise<WebSpatialProtocolResult> {
70
+ const requestId = createSpatialRequestId()
48
71
  const { spatialId: id = '', windowProxy } = this.openWindow(
49
72
  command,
50
- query,
51
- target,
52
- features,
73
+ buildSpatialRequestQuery(requestId),
74
+ undefined,
75
+ undefined,
53
76
  )
54
-
55
77
  return CommandResultSuccess({ windowProxy, id })
56
78
  }
57
79
 
@@ -60,17 +82,31 @@ export class VisionOSPlatform implements PlatformAbility {
60
82
  query?: string,
61
83
  target?: string,
62
84
  features?: string,
63
- ) {
85
+ ): { spatialId?: string; windowProxy: WindowProxy | null } {
64
86
  const windowProxy = window.open(
65
87
  `webspatial://${command}?${query || ''}`,
66
88
  target,
67
89
  features,
68
90
  )
69
91
  const ua = windowProxy?.navigator.userAgent
70
- const spatialId = ua?.match(
71
- /\b([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})\b/gi,
72
- )?.[0]
92
+ const spatialId = ua?.match(UUID_RE)?.[0]
73
93
 
74
94
  return { spatialId, windowProxy }
75
95
  }
96
+
97
+ private parseJSBError(error: unknown): { code: string; message: string } {
98
+ try {
99
+ const parsed = JSON.parse((error as JSBError).message ?? '')
100
+ return {
101
+ code: parsed.code ?? 'E_VISIONOS_JSB',
102
+ message: parsed.message ?? 'VisionOS JSB execution failed',
103
+ }
104
+ } catch {
105
+ return {
106
+ code: 'E_VISIONOS_JSB',
107
+ message:
108
+ (error as JSBError)?.message ?? 'VisionOS JSB execution failed',
109
+ }
110
+ }
111
+ }
76
112
  }
@@ -0,0 +1,13 @@
1
+ import { createPlatformSync } from './platform-adapter'
2
+ import type { PlatformAbility } from './platform-adapter/interface'
3
+
4
+ let platformResolved: PlatformAbility | undefined
5
+
6
+ export function getPlatformSync(): PlatformAbility {
7
+ platformResolved ??= createPlatformSync()
8
+ return platformResolved
9
+ }
10
+
11
+ export async function getPlatform(): Promise<PlatformAbility> {
12
+ return getPlatformSync()
13
+ }
@@ -1,9 +1,9 @@
1
1
  import { SpatialObject } from '../SpatialObject'
2
2
  import {
3
- CreateAttachmentEntityCommand,
4
3
  UpdateAttachmentEntityCommand,
5
4
  InitializeAttachmentCommand,
6
5
  } from '../JSBCommand'
6
+ import { createNativeAttachment } from '../spatial-host'
7
7
  import {
8
8
  AttachmentEntityOptions,
9
9
  AttachmentEntityUpdateOptions,
@@ -37,7 +37,7 @@ export class Attachment extends SpatialObject {
37
37
  export async function createAttachmentEntity(
38
38
  options: AttachmentEntityOptions,
39
39
  ): Promise<Attachment> {
40
- const result = await new CreateAttachmentEntityCommand(options).execute()
40
+ const result = await createNativeAttachment(options)
41
41
  if (!result.success) {
42
42
  throw new Error('createAttachmentEntity failed: ' + result?.errorMessage)
43
43
  }
@@ -3,6 +3,7 @@ import {
3
3
  ConvertFromEntityToSceneCommand,
4
4
  ConvertFromSceneToEntityCommand,
5
5
  SetParentForEntityCommand,
6
+ AnimateTransformJSBCommand,
6
7
  } from './../../JSBCommand'
7
8
  import {
8
9
  SpatialEntityEventType,
@@ -10,11 +11,15 @@ import {
10
11
  SpatialEntityUserData,
11
12
  Vec3,
12
13
  } from '../../types/types'
14
+ import type {
15
+ AnimateTransformCommand,
16
+ AnimateTransformResult,
17
+ TransformValues,
18
+ AnimationError,
19
+ } from '../../types/animation'
13
20
  import {
14
21
  AddComponentToEntityCommand,
15
- AddEntityToEntityCommand,
16
22
  RemoveComponentFromEntityCommand,
17
- RemoveEntityFromParentCommand,
18
23
  UpdateEntityEventCommand,
19
24
  UpdateEntityPropertiesCommand,
20
25
  } from '../../JSBCommand'
@@ -23,6 +28,7 @@ import { SpatialEntityProperties } from '../../types/types'
23
28
  import { SpatialComponent } from '../component/SpatialComponent'
24
29
  import { SpatialWebEvent } from '../../SpatialWebEvent'
25
30
  import { createSpatialEvent } from '../../SpatialWebEventCreator'
31
+ import { decomposeSRT } from '../../utils'
26
32
  import {
27
33
  ObjectDestroyMsg,
28
34
  SpatialDragEndMsg,
@@ -34,6 +40,9 @@ import {
34
40
  SpatialRotateMsg,
35
41
  SpatialTapMsg,
36
42
  SpatialWebMsgType,
43
+ AnimationCompletedEventPayload,
44
+ AnimationCanceledEventPayload,
45
+ AnimationFailedEventPayload,
37
46
  } from '../../WebMsgCommand'
38
47
 
39
48
  export class SpatialEntity extends SpatialObject {
@@ -120,6 +129,122 @@ export class SpatialEntity extends SpatialObject {
120
129
  return new UpdateEntityPropertiesCommand(this, properties).execute()
121
130
  }
122
131
 
132
+ /**
133
+ * Send an animation command to the native layer.
134
+ *
135
+ * For `play`: registers event listeners for completion / stop / failure,
136
+ * sends the JSB command, and returns an `AnimateTransformResult` with
137
+ * `finished` and `stopped` promises.
138
+ *
139
+ * For `pause`, `resume`, `cancel``: sends the command and resolves when
140
+ * the bridge acknowledges it.
141
+ */
142
+ animateTransform(
143
+ command: AnimateTransformCommand & { type: 'play' },
144
+ ): Promise<AnimateTransformResult>
145
+ animateTransform(command: AnimateTransformCommand): Promise<void>
146
+ async animateTransform(
147
+ command: AnimateTransformCommand,
148
+ ): Promise<AnimateTransformResult | void> {
149
+ const { animationId, type } = command
150
+
151
+ if (type === 'play') {
152
+ // Register event listeners BEFORE sending the play command
153
+ // to avoid race conditions where native fires before we listen.
154
+ let resolveFinished!: (val: TransformValues) => void
155
+ let resolveCancel!: (val: TransformValues) => void
156
+ let resolveFailed!: (err: AnimationError) => void
157
+
158
+ const finished = new Promise<TransformValues>(r => {
159
+ resolveFinished = r
160
+ })
161
+ const canceled = new Promise<TransformValues>(r => {
162
+ resolveCancel = r
163
+ })
164
+ const failed = new Promise<AnimationError>(r => {
165
+ resolveFailed = r
166
+ })
167
+
168
+ const cleanup = () => {
169
+ SpatialWebEvent.removeEventReceiver(`${animationId}_completed`)
170
+ SpatialWebEvent.removeEventReceiver(`${animationId}_canceled`)
171
+ SpatialWebEvent.removeEventReceiver(`${animationId}_failed`)
172
+ }
173
+
174
+ SpatialWebEvent.addEventReceiver(
175
+ `${animationId}_completed`,
176
+ (data: AnimationCompletedEventPayload) => {
177
+ cleanup()
178
+ const values = decomposeSRT(data.transform)
179
+ // Update local entity state to match the native final transform
180
+ if (values.position) this.position = values.position
181
+ if (values.rotation) this.rotation = values.rotation
182
+ if (values.scale) this.scale = values.scale
183
+ resolveFinished(values)
184
+ },
185
+ )
186
+
187
+ SpatialWebEvent.addEventReceiver(
188
+ `${animationId}_canceled`,
189
+ (data: AnimationCanceledEventPayload) => {
190
+ cleanup()
191
+ const values = decomposeSRT(data.transform)
192
+ if (values.position) this.position = values.position
193
+ if (values.rotation) this.rotation = values.rotation
194
+ if (values.scale) this.scale = values.scale
195
+ resolveCancel(values)
196
+ },
197
+ )
198
+
199
+ SpatialWebEvent.addEventReceiver(
200
+ `${animationId}_failed`,
201
+ (data: AnimationFailedEventPayload) => {
202
+ cleanup()
203
+ resolveFailed({
204
+ animationId: data.animationId,
205
+ command: data.command,
206
+ code: data.code,
207
+ reason: data.reason,
208
+ })
209
+ },
210
+ )
211
+
212
+ // Send the play command
213
+ const result = await new AnimateTransformJSBCommand(command).execute()
214
+ if (!result.success) {
215
+ cleanup()
216
+ throw new Error(
217
+ result.errorMessage ?? 'AnimateTransform play command failed',
218
+ )
219
+ }
220
+
221
+ return {
222
+ animationId,
223
+ finished,
224
+ canceled,
225
+ failed,
226
+ } as AnimateTransformResult
227
+ } else {
228
+ // pause / resume / cancel — fire and wait for bridge ack
229
+ const result = await new AnimateTransformJSBCommand(command).execute()
230
+ if (!result.success) {
231
+ throw new Error(
232
+ result.errorMessage ?? `AnimateTransform ${type} command failed`,
233
+ )
234
+ }
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Remove all animation event receivers for a given animationId.
240
+ * Called on entity unmount to prevent leaked listeners.
241
+ */
242
+ cleanupAnimationListeners(animationId: string) {
243
+ SpatialWebEvent.removeEventReceiver(`${animationId}_completed`)
244
+ SpatialWebEvent.removeEventReceiver(`${animationId}_canceled`)
245
+ SpatialWebEvent.removeEventReceiver(`${animationId}_failed`)
246
+ }
247
+
123
248
  async addEvent(type: SpatialEntityEventType, callback: (data: any) => void) {
124
249
  if (this.events[type]) {
125
250
  // replace if exist
@@ -5,6 +5,7 @@ import {
5
5
  CreateSpatialGeometryCommand,
6
6
  CreateSpatialModelEntityCommand,
7
7
  CreateSpatialUnlitMaterialCommand,
8
+ CreateTextureCommand,
8
9
  } from '../JSBCommand'
9
10
  import {
10
11
  ModelComponentOptions,
@@ -13,12 +14,13 @@ import {
13
14
  SpatialModelEntityCreationOptions,
14
15
  SpatialUnlitMaterialOptions,
15
16
  SpatialEntityUserData,
17
+ SpatialTextureResourceOptions,
16
18
  } from '../types/types'
17
19
  import { SpatialEntity, SpatialModelEntity } from './entity'
18
20
  import { ModelComponent } from './component'
19
21
  import { SpatialGeometry } from './geometry'
20
22
  import { SpatialUnlitMaterial } from './material'
21
- import { SpatialModelAsset } from './resource'
23
+ import { SpatialModelAsset, SpatialTextureResource } from './resource'
22
24
 
23
25
  export async function createSpatialEntity(
24
26
  userData?: SpatialEntityUserData,
@@ -92,3 +94,15 @@ export async function createModelAsset(options: ModelAssetOptions) {
92
94
  return new SpatialModelAsset(id, options)
93
95
  }
94
96
  }
97
+
98
+ export async function createSpatialTexture(
99
+ options: SpatialTextureResourceOptions,
100
+ ) {
101
+ const result = await new CreateTextureCommand(options.url).execute()
102
+ if (!result.success) {
103
+ throw new Error('createSpatialTexture failed:' + result?.errorMessage)
104
+ } else {
105
+ const { id } = result.data
106
+ return new SpatialTextureResource(id, options)
107
+ }
108
+ }
@@ -0,0 +1,16 @@
1
+ import { SpatialObject } from '../../SpatialObject'
2
+ import { SpatialTextureResourceOptions } from '../../types/types'
3
+ import { UpdateTexturePropertiesCommand } from '../../JSBCommand'
4
+
5
+ export class SpatialTextureResource extends SpatialObject {
6
+ constructor(
7
+ public id: string,
8
+ public options: SpatialTextureResourceOptions,
9
+ ) {
10
+ super(id)
11
+ }
12
+
13
+ updateProperties(properties: Partial<SpatialTextureResourceOptions>) {
14
+ return new UpdateTexturePropertiesCommand(this, properties).execute()
15
+ }
16
+ }
@@ -1 +1,2 @@
1
1
  export * from './SpatialModelAsset'
2
+ export * from './SpatialTextureResource'
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Thrown when a runtime-gated JS API is invoked but `supports('<name>')` is false.
3
+ * See OpenSpec `runtime-capabilities/spec.md` (Unsupported behavior contracts).
4
+ */
5
+ export class WebSpatialRuntimeError extends Error {
6
+ public readonly capability: string
7
+
8
+ constructor(capability: string, message?: string) {
9
+ super(
10
+ message ??
11
+ `Capability "${capability}" is not supported in this WebSpatial runtime`,
12
+ )
13
+ this.name = 'WebSpatialRuntimeError'
14
+ this.capability = capability
15
+ }
16
+ }