@webspatial/core-sdk 1.7.0 → 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 (296) hide show
  1. package/CHANGELOG.md +61 -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 -1251
  43. package/dist/index.js +21 -3650
  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 +132 -5
  241. package/src/SpatialObject.ts +10 -0
  242. package/src/Spatialized2DElement.ts +5 -0
  243. package/src/SpatializedDynamic3DElement.ts +5 -0
  244. package/src/SpatializedElement.ts +29 -0
  245. package/src/SpatializedStatic3DElement.ts +42 -3
  246. package/src/WebMsgCommand.ts +40 -0
  247. package/src/coverage-boost.test.ts +239 -216
  248. package/src/index.ts +25 -11
  249. package/src/install-polyfills.ts +38 -0
  250. package/src/jsbcommand.coverage.test.ts +23 -0
  251. package/src/motion/AnimationObject.test.ts +421 -0
  252. package/src/motion/AnimationObject.ts +208 -0
  253. package/src/motion/compute/index.ts +10 -0
  254. package/src/motion/compute/motionPropertyValues.test.ts +55 -0
  255. package/src/motion/compute/motionPropertyValues.ts +70 -0
  256. package/src/motion/compute/normalize.test.ts +99 -0
  257. package/src/motion/compute/normalize.ts +289 -0
  258. package/src/motion/compute/validate.test.ts +229 -0
  259. package/src/motion/compute/validate.ts +156 -0
  260. package/src/motion/index.ts +2 -0
  261. package/src/motion/native/serializeMotionTimeline.test.ts +61 -0
  262. package/src/motion/native/serializeMotionTimeline.ts +27 -0
  263. package/src/platform-adapter/createPlatformSync.ts +5 -3
  264. package/src/platform-adapter/pico-os/PicoOSPlatform.ts +76 -11
  265. package/src/platform-adapter/spatialRequestMetadata.test.ts +175 -0
  266. package/src/platform-adapter/spatialRequestMetadata.ts +48 -0
  267. package/src/platform-adapter/vision-os/VisionOSPlatform.ts +6 -1
  268. package/src/reality/entity/SpatialEntity.ts +127 -0
  269. package/src/runtime/capability-data.ts +104 -4
  270. package/src/runtime/index.ts +0 -11
  271. package/src/runtime/keys.ts +12 -1
  272. package/src/runtime/supports.test.ts +143 -4
  273. package/src/runtime/supports.ts +3 -1
  274. package/src/runtime/useAnimation.capability.test.ts +275 -0
  275. package/src/scene-polyfill.manifest.test.ts +306 -5
  276. package/src/scene-polyfill.request-metadata.test.ts +39 -0
  277. package/src/scene-polyfill.test.ts +33 -94
  278. package/src/scene-polyfill.ts +117 -87
  279. package/src/spatial-window-polyfill.ts +18 -2
  280. package/src/transform-roundtrip.test.ts +90 -0
  281. package/src/types/animation.ts +224 -0
  282. package/src/types/global.ts +1 -8
  283. package/src/types/motion/index.ts +10 -0
  284. package/src/types/motion/spatializedElementMotion.ts +49 -0
  285. package/src/types/motion/spatializedMotion.ts +147 -0
  286. package/src/types/motion/spatializedPlayback.ts +17 -0
  287. package/src/types/motion/spatializedVisual.ts +24 -0
  288. package/src/types/types.ts +3 -0
  289. package/src/utils.ts +54 -0
  290. package/tsup.config.ts +61 -36
  291. package/vitest.config.ts +5 -1
  292. package/dist/iife/index.d.ts +0 -1251
  293. package/dist/iife/index.global.js +0 -75
  294. package/dist/iife/index.global.js.map +0 -1
  295. package/src/platform-adapter/ssr/SSRPlatform.ts +0 -52
  296. /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
+ }
@@ -10,6 +10,10 @@ import {
10
10
  import { buildSpatialSceneQuery } from '../spatialSceneQuery'
11
11
  import type { SpatialSceneCreationOptionsInternal } from '../../types/internal'
12
12
  import type { AttachmentEntityOptions } from '../../types/types'
13
+ import {
14
+ buildSpatialRequestQuery,
15
+ createSpatialRequestId,
16
+ } from '../spatialRequestMetadata'
13
17
 
14
18
  type JSBError = {
15
19
  code?: string
@@ -63,9 +67,10 @@ export class VisionOSPlatform implements PlatformAbility {
63
67
  private async openProtocolAsync(
64
68
  command: string,
65
69
  ): Promise<WebSpatialProtocolResult> {
70
+ const requestId = createSpatialRequestId()
66
71
  const { spatialId: id = '', windowProxy } = this.openWindow(
67
72
  command,
68
- '',
73
+ buildSpatialRequestQuery(requestId),
69
74
  undefined,
70
75
  undefined,
71
76
  )
@@ -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,6 +11,12 @@ 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
22
  RemoveComponentFromEntityCommand,
@@ -21,6 +28,7 @@ import { SpatialEntityProperties } from '../../types/types'
21
28
  import { SpatialComponent } from '../component/SpatialComponent'
22
29
  import { SpatialWebEvent } from '../../SpatialWebEvent'
23
30
  import { createSpatialEvent } from '../../SpatialWebEventCreator'
31
+ import { decomposeSRT } from '../../utils'
24
32
  import {
25
33
  ObjectDestroyMsg,
26
34
  SpatialDragEndMsg,
@@ -32,6 +40,9 @@ import {
32
40
  SpatialRotateMsg,
33
41
  SpatialTapMsg,
34
42
  SpatialWebMsgType,
43
+ AnimationCompletedEventPayload,
44
+ AnimationCanceledEventPayload,
45
+ AnimationFailedEventPayload,
35
46
  } from '../../WebMsgCommand'
36
47
 
37
48
  export class SpatialEntity extends SpatialObject {
@@ -118,6 +129,122 @@ export class SpatialEntity extends SpatialObject {
118
129
  return new UpdateEntityPropertiesCommand(this, properties).execute()
119
130
  }
120
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
+
121
248
  async addEvent(type: SpatialEntityEventType, callback: (data: any) => void) {
122
249
  if (this.events[type]) {
123
250
  // replace if exist
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Versioned capability rows transcribed from the product matrix (`capability-matrix.template.md`).
3
- * visionOS **WSAppShell/1.5.0** & **1.6.0**; picoOS **PicoWebApp/0.1.1** & **0.1.2** — see matrix in OpenSpec / product docs.
3
+ * visionOS **WSAppShell/1.5.0** & **1.6.0** & **1.7.0** & **1.8.0**; picoOS **PicoWebApp/0.1.1** & **0.1.2** & **0.2.2** & **0.3.1** & **0.4.90** — see matrix in OpenSpec / product docs.
4
4
  *
5
5
  * **picoOS** rows use dedicated builders (alpha2.0 / alpha2.1 subtokens); visionOS rows are separate.
6
6
  */
@@ -50,6 +50,11 @@ function matrixVision_1_5_0_Flags(): Record<string, boolean> {
50
50
  flags[`Model:${t}`] = false
51
51
  }
52
52
  flags['SpatialRotateEvent:constrainedToAxis'] = true
53
+ // Container useAnimation is not supported until WSAppShell/1.8.0.
54
+ flags['useAnimation'] = false
55
+ // Placement-shaped attachment protocol lands in picoOS PicoWebApp/0.4.90
56
+ // (OTA0); no visionOS shell decodes it yet.
57
+ flags['AttachmentEntity:placement'] = false
53
58
  return flags
54
59
  }
55
60
 
@@ -63,6 +68,32 @@ function matrixVision_1_6_0_Flags(): Record<string, boolean> {
63
68
  flags[`Model:${t}`] = false
64
69
  }
65
70
  flags['SpatialRotateEvent:constrainedToAxis'] = true
71
+ // Container useAnimation is not supported until WSAppShell/1.8.0.
72
+ flags['useAnimation'] = false
73
+ // Placement-shaped attachment protocol lands in picoOS PicoWebApp/0.4.90
74
+ // (OTA0); no visionOS shell decodes it yet.
75
+ flags['AttachmentEntity:placement'] = false
76
+ return flags
77
+ }
78
+
79
+ /**
80
+ * visionOS **WSAppShell/1.7.0** — entity transform animation support.
81
+ * Container `useAnimation` remains unavailable in this row.
82
+ */
83
+ function matrixVision_1_7_0_Flags(): Record<string, boolean> {
84
+ const flags = matrixVision_1_6_0_Flags()
85
+ flags['Model:currentTime'] = true
86
+ flags['Model:loading'] = true
87
+ flags['Model:poster'] = true
88
+ return flags
89
+ }
90
+
91
+ /**
92
+ * visionOS **WSAppShell/1.8.0**.
93
+ */
94
+ function matrixVision_1_8_0_Flags(): Record<string, boolean> {
95
+ const flags = matrixVision_1_7_0_Flags()
96
+ flags['useAnimation'] = true
66
97
  return flags
67
98
  }
68
99
 
@@ -78,13 +109,43 @@ function matrixPico_0_1_1_Flags(): Record<string, boolean> {
78
109
  }
79
110
 
80
111
  /**
81
- * picoOS **PicoWebApp/0.1.2** — alpha2.1 playback expansion (product matrix).
112
+ * picoOS **PicoWebApp/0.1.2** — beta2.0 playback expansion (product matrix).
82
113
  * **`xrInnerDepth` / `xrOuterDepth` N** (same as 0.1.1).
83
114
  */
84
115
  function matrixPico_0_1_2_Flags(): Record<string, boolean> {
85
116
  const flags = matrixVision_1_6_0_Flags()
86
117
  flags.xrInnerDepth = false
87
118
  flags.xrOuterDepth = false
119
+ flags['Model:currentTime'] = true
120
+ flags['Model:poster'] = true
121
+ return flags
122
+ }
123
+
124
+ /**
125
+ * picoOS **PicoWebApp/0.2.2** — entity transform animation support.
126
+ * Container `useAnimation` remains unavailable in this row.
127
+ */
128
+ function matrixPico_0_2_2_Flags(): Record<string, boolean> {
129
+ const flags = matrixPico_0_1_2_Flags()
130
+ return flags
131
+ }
132
+
133
+ /**
134
+ * picoOS **PicoWebApp/0.3.1** — beta2.1
135
+ */
136
+ function matrixPico_0_3_1_Flags(): Record<string, boolean> {
137
+ const flags = matrixPico_0_2_2_Flags()
138
+ flags['Model:loading'] = true
139
+ return flags
140
+ }
141
+
142
+ /**
143
+ * picoOS **PicoWebApp/0.4.90** — OTA0 container animation support.
144
+ */
145
+ function matrixPico_0_4_90_Flags(): Record<string, boolean> {
146
+ const flags = matrixPico_0_3_1_Flags()
147
+ flags['useAnimation'] = true
148
+ flags['AttachmentEntity:placement'] = true
88
149
  return flags
89
150
  }
90
151
 
@@ -96,18 +157,57 @@ function visionOsRow_1_6_0(): CapabilityVersionRow {
96
157
  return { version: '1.6.0', flags: matrixVision_1_6_0_Flags() }
97
158
  }
98
159
 
160
+ function visionOsRow_1_7_0(): CapabilityVersionRow {
161
+ return { version: '1.7.0', flags: matrixVision_1_7_0_Flags() }
162
+ }
163
+
164
+ /**
165
+ * Build the capability row for visionOS **WSAppShell/1.8.0**.
166
+ */
167
+ function visionOsRow_1_8_0(): CapabilityVersionRow {
168
+ return { version: '1.8.0', flags: matrixVision_1_8_0_Flags() }
169
+ }
170
+
99
171
  function picoOsRow_0_1_1(): CapabilityVersionRow {
100
172
  return { version: '0.1.1', flags: matrixPico_0_1_1_Flags() }
101
173
  }
102
174
 
175
+ // Pico OS 6.0 Beta 2.0
103
176
  function picoOsRow_0_1_2(): CapabilityVersionRow {
104
177
  return { version: '0.1.2', flags: matrixPico_0_1_2_Flags() }
105
178
  }
106
179
 
180
+ function picoOsRow_0_2_2(): CapabilityVersionRow {
181
+ return { version: '0.2.2', flags: matrixPico_0_2_2_Flags() }
182
+ }
183
+
184
+ // Pico OS 6.0 Beta 2.1
185
+ function picoOsRow_0_3_1(): CapabilityVersionRow {
186
+ return { version: '0.3.1', flags: matrixPico_0_3_1_Flags() }
187
+ }
188
+
189
+ /**
190
+ * Build the capability row for picoOS **PicoWebApp/0.4.90** OTA0.
191
+ */
192
+ function picoOsRow_0_4_90(): CapabilityVersionRow {
193
+ return { version: '0.4.90', flags: matrixPico_0_4_90_Flags() }
194
+ }
195
+
107
196
  export const CAPABILITY_TABLE: {
108
197
  visionos: CapabilityVersionRow[]
109
198
  picoos: CapabilityVersionRow[]
110
199
  } = {
111
- visionos: [visionOsRow_1_5_0(), visionOsRow_1_6_0()],
112
- picoos: [picoOsRow_0_1_1(), picoOsRow_0_1_2()],
200
+ visionos: [
201
+ visionOsRow_1_5_0(),
202
+ visionOsRow_1_6_0(),
203
+ visionOsRow_1_7_0(),
204
+ visionOsRow_1_8_0(),
205
+ ],
206
+ picoos: [
207
+ picoOsRow_0_1_1(),
208
+ picoOsRow_0_1_2(),
209
+ picoOsRow_0_2_2(),
210
+ picoOsRow_0_3_1(),
211
+ picoOsRow_0_4_90(),
212
+ ],
113
213
  }
@@ -15,14 +15,3 @@ export {
15
15
  export type { CapabilityVersionRow } from './capability-data'
16
16
  export { CAPABILITY_TABLE } from './capability-data'
17
17
  export type { CapabilityKey } from './keys'
18
- export {
19
- TOP_LEVEL_KEYS,
20
- SUB_TOKENS_BY_NAME,
21
- COMPONENT_KEYS,
22
- CSS_KEYS,
23
- GESTURE_KEYS,
24
- JS_SCENE_KEYS,
25
- DOM_DEPTH_KEYS,
26
- ELEMENT_DOM_DEPTH_KEYS,
27
- WINDOW_DOM_DEPTH_KEYS,
28
- } from './keys'
@@ -1,6 +1,10 @@
1
1
  /**
2
- * Canonical `supports(name)` keys (OpenSpec `review.md` §3).
2
+ * Canonical `supports(name)` key registry (OpenSpec `review.md` §3).
3
3
  * Aliases are normalized before lookup (see `normalizeCapabilityName`).
4
+ *
5
+ * @internal SDK contract data — not part of the public `@webspatial/react-sdk`
6
+ * surface. Import via `@webspatial/core-sdk/runtime/keys` only for SDK demos,
7
+ * contract tests, and capability-matrix tooling.
4
8
  */
5
9
 
6
10
  export const COMPONENT_KEYS = [
@@ -39,6 +43,8 @@ export const GESTURE_KEYS = [
39
43
  'SpatialMagnifyEndEvent',
40
44
  ] as const
41
45
 
46
+ export const ANIMATION_KEYS = ['useAnimation'] as const
47
+
42
48
  export const JS_SCENE_KEYS = [
43
49
  'useMetrics',
44
50
  'convertCoordinate',
@@ -68,6 +74,7 @@ export const TOP_LEVEL_KEYS = [
68
74
  ...COMPONENT_KEYS,
69
75
  ...CSS_KEYS,
70
76
  ...GESTURE_KEYS,
77
+ ...ANIMATION_KEYS,
71
78
  ...JS_SCENE_KEYS,
72
79
  ...DOM_DEPTH_KEYS,
73
80
  ] as const
@@ -100,6 +107,10 @@ export const SUB_TOKENS_BY_NAME: Readonly<Record<string, readonly string[]>> = {
100
107
  'baseplateVisibility',
101
108
  ],
102
109
  SpatialRotateEvent: ['constrainedToAxis'],
110
+ // Placement-shaped attachment JSB protocol (placementId + Vec3 transform +
111
+ // meter width/height). Runtimes without it take the legacy payload
112
+ // (parentEntityId + tuple position + point size).
113
+ AttachmentEntity: ['placement'],
103
114
  Model: [
104
115
  'autoplay',
105
116
  'loop',