lecodes-cli 0.18.2 → 0.19.2

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 (224) hide show
  1. package/dist/index.js +1866 -301
  2. package/package.json +14 -12
  3. package/runtime/scene-harness.json +1 -1
  4. package/runtime/sdk-types.json +1 -1
  5. package/src/api.ts +302 -0
  6. package/src/browserAuth.ts +87 -0
  7. package/src/cmgenTool.ts +104 -0
  8. package/src/commands/app.ts +891 -0
  9. package/src/commands/appAndroid.ts +603 -0
  10. package/src/commands/appDesktop.ts +326 -0
  11. package/src/commands/appDesktopMac.ts +470 -0
  12. package/src/commands/appIcon.ts +187 -0
  13. package/src/commands/appShared.ts +448 -0
  14. package/src/commands/appTemplates.ts +883 -0
  15. package/src/commands/appTemplatesAndroid.ts +599 -0
  16. package/src/commands/appTemplatesGradlew.ts +9 -0
  17. package/src/commands/assets.ts +28 -0
  18. package/src/commands/clone.ts +60 -0
  19. package/src/commands/compile.ts +253 -0
  20. package/src/commands/create.ts +59 -0
  21. package/src/commands/design.ts +863 -0
  22. package/src/commands/designTemplates.ts +11 -0
  23. package/src/commands/desktop.ts +122 -0
  24. package/src/commands/dev.ts +214 -0
  25. package/src/commands/diff.ts +59 -0
  26. package/src/commands/init.ts +191 -0
  27. package/src/commands/install.ts +198 -0
  28. package/src/commands/lightmap.ts +290 -0
  29. package/src/commands/link.ts +147 -0
  30. package/src/commands/login.ts +24 -0
  31. package/src/commands/navmesh.ts +225 -0
  32. package/src/commands/pn.ts +305 -0
  33. package/src/commands/projectTemplates.ts +129 -0
  34. package/src/commands/pull.ts +109 -0
  35. package/src/commands/push.ts +173 -0
  36. package/src/commands/render.ts +414 -0
  37. package/src/commands/scene.ts +148 -0
  38. package/src/commands/shaders.ts +157 -0
  39. package/src/commands/status.ts +29 -0
  40. package/src/commands/test.ts +370 -0
  41. package/src/commands/thumbs.ts +178 -0
  42. package/src/commands/types.ts +87 -0
  43. package/src/commands/update.ts +190 -0
  44. package/src/compile/assetIcons.ts +214 -0
  45. package/src/compile/collect.ts +46 -0
  46. package/src/compile/collectLocal.ts +37 -0
  47. package/src/compile/designCompile.ts +104 -0
  48. package/src/compile/fonts.ts +156 -0
  49. package/src/compile/headlessBundle.ts +129 -0
  50. package/src/compile/nativeStack.ts +21 -0
  51. package/src/compile/projectCompile.ts +109 -0
  52. package/src/compile/sceneCompile.ts +113 -0
  53. package/src/compile/screenEntry.ts +127 -0
  54. package/src/compile/shaders.ts +245 -0
  55. package/src/config.ts +42 -0
  56. package/src/designMeta.ts +35 -0
  57. package/src/desktopRenderer.ts +532 -0
  58. package/src/desktopScript.ts +276 -0
  59. package/src/dev/clientTemplates.ts +160 -0
  60. package/src/dev/devServer.ts +292 -0
  61. package/src/dev/wsServer.ts +144 -0
  62. package/src/distRoot.ts +20 -0
  63. package/src/ignore.ts +163 -0
  64. package/src/index.ts +491 -0
  65. package/src/lecodes-3d-editor.d.ts +41 -0
  66. package/src/lecodes-assets.d.ts +7 -0
  67. package/src/lecodes-design.d.ts +191 -0
  68. package/src/lecodes-renderer.d.ts +131 -0
  69. package/src/localFiles.ts +144 -0
  70. package/src/manifest.ts +46 -0
  71. package/src/matcTool.ts +137 -0
  72. package/src/peers.ts +40 -0
  73. package/src/project.ts +61 -0
  74. package/src/projectEnv.ts +94 -0
  75. package/src/qrcode-terminal.d.ts +9 -0
  76. package/src/releases.ts +125 -0
  77. package/src/serverDiff.ts +78 -0
  78. package/src/textDiff.ts +103 -0
  79. package/src/types.ts +0 -0
  80. package/src/util.ts +146 -0
  81. package/runtime/sdk/animate/animate.ts +0 -238
  82. package/runtime/sdk/animate/bezier.ts +0 -138
  83. package/runtime/sdk/animate/easings.ts +0 -126
  84. package/runtime/sdk/canvas/Canvas.ts +0 -305
  85. package/runtime/sdk/core/Aspect.ts +0 -512
  86. package/runtime/sdk/core/InspectorUI.ts +0 -212
  87. package/runtime/sdk/core/color.ts +0 -66
  88. package/runtime/sdk/core/compWrite.ts +0 -42
  89. package/runtime/sdk/core/events.ts +0 -38
  90. package/runtime/sdk/core/fields.ts +0 -120
  91. package/runtime/sdk/core/registry.ts +0 -23
  92. package/runtime/sdk/core/signals.ts +0 -277
  93. package/runtime/sdk/core/time.ts +0 -81
  94. package/runtime/sdk/g2/Camera2D.ts +0 -40
  95. package/runtime/sdk/g2/CharacterController2D.ts +0 -276
  96. package/runtime/sdk/g2/Node2D.ts +0 -267
  97. package/runtime/sdk/g2/OneWay2D.ts +0 -66
  98. package/runtime/sdk/g2/Physics2D.ts +0 -346
  99. package/runtime/sdk/g2/Scene2D.ts +0 -209
  100. package/runtime/sdk/g2/Shape2D.ts +0 -259
  101. package/runtime/sdk/g2/Sprite.ts +0 -89
  102. package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
  103. package/runtime/sdk/g2/SpriteSheet.ts +0 -166
  104. package/runtime/sdk/g2/Texture2D.ts +0 -47
  105. package/runtime/sdk/g2/Tilemap.ts +0 -41
  106. package/runtime/sdk/g2/Tileset.ts +0 -71
  107. package/runtime/sdk/g2/Trigger2D.ts +0 -77
  108. package/runtime/sdk/g2/autotile.ts +0 -433
  109. package/runtime/sdk/g2/cells.ts +0 -91
  110. package/runtime/sdk/g2/defineScene2d.ts +0 -381
  111. package/runtime/sdk/g2/groups2d.ts +0 -106
  112. package/runtime/sdk/g2/loop.ts +0 -50
  113. package/runtime/sdk/g2/scenarios2d.ts +0 -69
  114. package/runtime/sdk/g2/touch.ts +0 -83
  115. package/runtime/sdk/gl/Camera.ts +0 -160
  116. package/runtime/sdk/gl/CameraPlace.ts +0 -52
  117. package/runtime/sdk/gl/CharacterController.ts +0 -238
  118. package/runtime/sdk/gl/Gearbox.ts +0 -212
  119. package/runtime/sdk/gl/Geometry.ts +0 -279
  120. package/runtime/sdk/gl/IK.ts +0 -193
  121. package/runtime/sdk/gl/InstancedMesh.ts +0 -132
  122. package/runtime/sdk/gl/Light.ts +0 -99
  123. package/runtime/sdk/gl/Lightmap.ts +0 -179
  124. package/runtime/sdk/gl/Material.ts +0 -245
  125. package/runtime/sdk/gl/Mesh.ts +0 -83
  126. package/runtime/sdk/gl/Model.ts +0 -64
  127. package/runtime/sdk/gl/Node.ts +0 -350
  128. package/runtime/sdk/gl/Noise.ts +0 -30
  129. package/runtime/sdk/gl/Particles.ts +0 -676
  130. package/runtime/sdk/gl/Physics.ts +0 -222
  131. package/runtime/sdk/gl/Plane.ts +0 -53
  132. package/runtime/sdk/gl/Ray.ts +0 -16
  133. package/runtime/sdk/gl/Scene.ts +0 -479
  134. package/runtime/sdk/gl/Shape.ts +0 -377
  135. package/runtime/sdk/gl/Texture.ts +0 -46
  136. package/runtime/sdk/gl/Trigger.ts +0 -45
  137. package/runtime/sdk/gl/Vehicle.ts +0 -473
  138. package/runtime/sdk/gl/Wheel.ts +0 -240
  139. package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
  140. package/runtime/sdk/gl/animation/Animator.ts +0 -87
  141. package/runtime/sdk/gl/animation/Layer.ts +0 -29
  142. package/runtime/sdk/gl/animation/Loop.ts +0 -25
  143. package/runtime/sdk/gl/animation/Playback.ts +0 -43
  144. package/runtime/sdk/gl/animation/core.ts +0 -294
  145. package/runtime/sdk/gl/controls.ts +0 -95
  146. package/runtime/sdk/gl/physicsEvents.ts +0 -20
  147. package/runtime/sdk/gl/scenarios.ts +0 -291
  148. package/runtime/sdk/gl/state.ts +0 -6
  149. package/runtime/sdk/gl/touch.ts +0 -68
  150. package/runtime/sdk/inject.ts +0 -186
  151. package/runtime/sdk/math/Mathf.ts +0 -118
  152. package/runtime/sdk/math/mat4.ts +0 -278
  153. package/runtime/sdk/math/quat.ts +0 -232
  154. package/runtime/sdk/math/vec.ts +0 -255
  155. package/runtime/sdk/plugins/camera.ts +0 -81
  156. package/runtime/sdk/plugins/geolocation.ts +0 -123
  157. package/runtime/sdk/plugins/oauth.ts +0 -61
  158. package/runtime/sdk/plugins/permission.ts +0 -7
  159. package/runtime/sdk/plugins/push.ts +0 -132
  160. package/runtime/sdk/plugins/qr.ts +0 -73
  161. package/runtime/sdk/plugins/service.ts +0 -47
  162. package/runtime/sdk/runtime/app.ts +0 -101
  163. package/runtime/sdk/runtime/appEvents.ts +0 -54
  164. package/runtime/sdk/runtime/channel.ts +0 -61
  165. package/runtime/sdk/runtime/clipboard.ts +0 -20
  166. package/runtime/sdk/runtime/datetime.ts +0 -329
  167. package/runtime/sdk/runtime/device.ts +0 -293
  168. package/runtime/sdk/runtime/fetch.ts +0 -77
  169. package/runtime/sdk/runtime/files.ts +0 -23
  170. package/runtime/sdk/runtime/input.ts +0 -175
  171. package/runtime/sdk/runtime/media.ts +0 -111
  172. package/runtime/sdk/runtime/misc.ts +0 -16
  173. package/runtime/sdk/runtime/net.ts +0 -36
  174. package/runtime/sdk/runtime/rpc.ts +0 -218
  175. package/runtime/sdk/runtime/service.ts +0 -83
  176. package/runtime/sdk/runtime/share.ts +0 -9
  177. package/runtime/sdk/runtime/storage.ts +0 -13
  178. package/runtime/sdk/runtime/touch.ts +0 -76
  179. package/runtime/sdk/scene/defineScene.ts +0 -1227
  180. package/runtime/sdk/scene/editorPlugins.ts +0 -92
  181. package/runtime/sdk/scene/gizmos.ts +0 -148
  182. package/runtime/sdk/scene/grammar.ts +0 -120
  183. package/runtime/sdk/scene/material.ts +0 -188
  184. package/runtime/sdk/server/auth/appConfig.ts +0 -12
  185. package/runtime/sdk/server/auth/global.ts +0 -80
  186. package/runtime/sdk/server/auth/host.ts +0 -318
  187. package/runtime/sdk/server/auth/models.ts +0 -83
  188. package/runtime/sdk/server/auth/types.ts +0 -50
  189. package/runtime/sdk/server/channel.ts +0 -56
  190. package/runtime/sdk/server/context.ts +0 -36
  191. package/runtime/sdk/server/db/defineDb.ts +0 -237
  192. package/runtime/sdk/server/db/fields.ts +0 -132
  193. package/runtime/sdk/server/db/httpTransport.ts +0 -93
  194. package/runtime/sdk/server/db/index.ts +0 -7
  195. package/runtime/sdk/server/db/marci/query.ts +0 -412
  196. package/runtime/sdk/server/db/types.ts +0 -202
  197. package/runtime/sdk/server/errors.ts +0 -12
  198. package/runtime/sdk/server/host.ts +0 -74
  199. package/runtime/sdk/server/inject.ts +0 -13
  200. package/runtime/sdk/server/runtime.ts +0 -133
  201. package/runtime/sdk/server/validate.ts +0 -87
  202. package/runtime/sdk/ui/NativeView.ts +0 -142
  203. package/runtime/sdk/ui/UI.ts +0 -39
  204. package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
  205. package/runtime/sdk/ui/UIButton.ts +0 -101
  206. package/runtime/sdk/ui/UIContainer.ts +0 -60
  207. package/runtime/sdk/ui/UIImage.ts +0 -83
  208. package/runtime/sdk/ui/UIInput.ts +0 -185
  209. package/runtime/sdk/ui/UIModal.ts +0 -139
  210. package/runtime/sdk/ui/UINode.ts +0 -826
  211. package/runtime/sdk/ui/UIPager.ts +0 -362
  212. package/runtime/sdk/ui/UIPopover.ts +0 -100
  213. package/runtime/sdk/ui/UIScreen.ts +0 -123
  214. package/runtime/sdk/ui/UIScrollable.ts +0 -87
  215. package/runtime/sdk/ui/UISpacer.ts +0 -14
  216. package/runtime/sdk/ui/UITabs.ts +0 -236
  217. package/runtime/sdk/ui/UIText.ts +0 -51
  218. package/runtime/sdk/ui/UIVideo.ts +0 -88
  219. package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
  220. package/runtime/sdk/ui/UIWidget.ts +0 -127
  221. package/runtime/sdk/ui/fonts.ts +0 -13
  222. package/runtime/sdk/ui/presentable.ts +0 -117
  223. package/runtime/sdk/ui/router.ts +0 -132
  224. package/runtime/sdk/ui/theme.ts +0 -84
@@ -1,255 +0,0 @@
1
- // Fluent 2D / 3D vectors — the SDK's user-facing vector math (no gl-matrix, no out-params, no imports).
2
- //
3
- // Design (see PLAN.md): a vector is a *mutable little struct* — `v.x = 3`, `v.set(x, y, z)` mutate in
4
- // place — but every *method* is PURE: `a.add(b)` returns a NEW vector and never touches `a`. So chains
5
- // (`dir.normalize().scale(s)`) can't clobber their source, and the immutable-method style is the most
6
- // amenable to a future escape-analysis / scalar-replacement compiler pass. Backing is plain number
7
- // fields (not a Float32Array), which the optimizer can trivially scalar-replace.
8
- //
9
- // "Never wrap away the metal": vectors expose `.x/.y/.z`, are iterable (`const [x, y] = v`, `[...v]`),
10
- // and every method / constructor accepts a raw tuple just as well as a wrapper. Reads off a `*Like`
11
- // input go through the `cx/cy/cz` helpers below, which work uniformly on a wrapper (`.x`) or an
12
- // array-like (`[0]`), so callers can always pass `[0, 1, 0]` where a vector is expected.
13
-
14
- export type Vec2Like = Vec2 | readonly [number, number] | number[] | Float32Array
15
- export type Vec3Like = Vec3 | readonly [number, number, number] | number[] | Float32Array
16
-
17
- /** Read the x / first component of any vector-like (wrapper, tuple, or typed array). `?? ` is safe on
18
- * 0 and NaN — it only falls through on `undefined` (i.e. a non-wrapper that has no `.x`). */
19
- export const cx = (v: { x?: number } | ArrayLike<number>): number => (v as { x?: number }).x ?? (v as ArrayLike<number>)[0]
20
- export const cy = (v: { y?: number } | ArrayLike<number>): number => (v as { y?: number }).y ?? (v as ArrayLike<number>)[1]
21
- export const cz = (v: { z?: number } | ArrayLike<number>): number => (v as { z?: number }).z ?? (v as ArrayLike<number>)[2]
22
-
23
- /** Relative-epsilon compare (matches gl-matrix's `equals`), so the oracle tests agree to the bit. */
24
- const approx = (a: number, b: number, eps: number): boolean => Math.abs(a - b) <= eps * Math.max(1, Math.abs(a), Math.abs(b))
25
-
26
- // ---------------------------------------------------------------------------------------------------
27
- // Vec2
28
- // ---------------------------------------------------------------------------------------------------
29
-
30
- export class Vec2 {
31
- x: number
32
- y: number
33
-
34
- constructor(x: number | Vec2Like = 0, y = 0) {
35
- if (typeof x === "number") { this.x = x; this.y = y }
36
- else { this.x = cx(x); this.y = cy(x) }
37
- }
38
-
39
- // --- mutation (the only methods that change `this`) ---
40
- /** Mutate the components in place. Returns `this`. */
41
- set(x: number, y: number): this { this.x = x; this.y = y; return this }
42
- /** Copy another vector's components into this one. Returns `this`. */
43
- copy(v: Vec2Like): this { this.x = cx(v); this.y = cy(v); return this }
44
-
45
- // --- pure arithmetic (each returns a NEW Vec2) ---
46
- add(v: Vec2Like): Vec2 { return new Vec2(this.x + cx(v), this.y + cy(v)) }
47
- sub(v: Vec2Like): Vec2 { return new Vec2(this.x - cx(v), this.y - cy(v)) }
48
- mul(v: Vec2Like): Vec2 { return new Vec2(this.x * cx(v), this.y * cy(v)) }
49
- div(v: Vec2Like): Vec2 { return new Vec2(this.x / cx(v), this.y / cy(v)) }
50
- scale(s: number): Vec2 { return new Vec2(this.x * s, this.y * s) }
51
- /** `this + v * s` (fused multiply-add). */
52
- scaleAndAdd(v: Vec2Like, s: number): Vec2 { return new Vec2(this.x + cx(v) * s, this.y + cy(v) * s) }
53
- negate(): Vec2 { return new Vec2(-this.x, -this.y) }
54
-
55
- // --- products / measures ---
56
- dot(v: Vec2Like): number { return this.x * cx(v) + this.y * cy(v) }
57
- /** 2D cross product → the scalar z of the 3D cross (signed parallelogram area). */
58
- cross(v: Vec2Like): number { return this.x * cy(v) - this.y * cx(v) }
59
- length(): number { return Math.hypot(this.x, this.y) }
60
- lengthSq(): number { return this.x * this.x + this.y * this.y }
61
- distanceTo(v: Vec2Like): number { return Math.hypot(this.x - cx(v), this.y - cy(v)) }
62
- distanceSqTo(v: Vec2Like): number { const dx = this.x - cx(v), dy = this.y - cy(v); return dx * dx + dy * dy }
63
-
64
- normalize(): Vec2 {
65
- const l = Math.hypot(this.x, this.y)
66
- return l === 0 ? new Vec2(0, 0) : new Vec2(this.x / l, this.y / l)
67
- }
68
- lerp(v: Vec2Like, t: number): Vec2 { return new Vec2(this.x + (cx(v) - this.x) * t, this.y + (cy(v) - this.y) * t) }
69
- clamp(min: Vec2Like, max: Vec2Like): Vec2 {
70
- return new Vec2(clampN(this.x, cx(min), cx(max)), clampN(this.y, cy(min), cy(max)))
71
- }
72
- min(v: Vec2Like): Vec2 { return new Vec2(Math.min(this.x, cx(v)), Math.min(this.y, cy(v))) }
73
- max(v: Vec2Like): Vec2 { return new Vec2(Math.max(this.x, cx(v)), Math.max(this.y, cy(v))) }
74
- /** Left-hand perpendicular: (-y, x), a 90° CCW turn. */
75
- perp(): Vec2 { return new Vec2(-this.y, this.x) }
76
- /** Rotate by `rad` (CCW) around `origin` (default 0). */
77
- rotate(rad: number, origin: Vec2Like = ZERO2): Vec2 {
78
- const ox = cx(origin), oy = cy(origin)
79
- const c = Math.cos(rad), s = Math.sin(rad)
80
- const dx = this.x - ox, dy = this.y - oy
81
- return new Vec2(ox + dx * c - dy * s, oy + dx * s + dy * c)
82
- }
83
- /** The vector's heading in radians: `atan2(y, x)`. */
84
- heading(): number { return Math.atan2(this.y, this.x) }
85
- /** Angle (radians) between this and `v`. */
86
- angle(v: Vec2Like): number {
87
- const mag = Math.sqrt(this.lengthSq() * (cx(v) * cx(v) + cy(v) * cy(v)))
88
- return mag === 0 ? 0 : Math.acos(clampN(this.dot(v) / mag, -1, 1))
89
- }
90
-
91
- withX(x: number): Vec2 { return new Vec2(x, this.y) }
92
- withY(y: number): Vec2 { return new Vec2(this.x, y) }
93
-
94
- // --- interop ---
95
- equals(v: Vec2Like, eps = 1e-6): boolean { return approx(this.x, cx(v), eps) && approx(this.y, cy(v), eps) }
96
- clone(): Vec2 { return new Vec2(this.x, this.y) }
97
- toArray(): [number, number] { return [ this.x, this.y ] }
98
- *[Symbol.iterator](): Iterator<number> { yield this.x; yield this.y }
99
-
100
- static from(v: Vec2Like): Vec2 { return new Vec2(cx(v), cy(v)) }
101
- static get zero(): Vec2 { return new Vec2(0, 0) }
102
- static get one(): Vec2 { return new Vec2(1, 1) }
103
- static get up(): Vec2 { return new Vec2(0, 1) }
104
- static get down(): Vec2 { return new Vec2(0, -1) }
105
- static get left(): Vec2 { return new Vec2(-1, 0) }
106
- static get right(): Vec2 { return new Vec2(1, 0) }
107
- }
108
-
109
- // ---------------------------------------------------------------------------------------------------
110
- // Vec3
111
- // ---------------------------------------------------------------------------------------------------
112
-
113
- export class Vec3 {
114
- x: number
115
- y: number
116
- z: number
117
-
118
- constructor(x: number | Vec3Like = 0, y = 0, z = 0) {
119
- if (typeof x === "number") { this.x = x; this.y = y; this.z = z }
120
- else { this.x = cx(x); this.y = cy(x); this.z = cz(x) }
121
- }
122
-
123
- // --- mutation ---
124
- set(x: number, y: number, z: number): this { this.x = x; this.y = y; this.z = z; return this }
125
- copy(v: Vec3Like): this { this.x = cx(v); this.y = cy(v); this.z = cz(v); return this }
126
-
127
- // --- pure arithmetic ---
128
- add(v: Vec3Like): Vec3 { return new Vec3(this.x + cx(v), this.y + cy(v), this.z + cz(v)) }
129
- sub(v: Vec3Like): Vec3 { return new Vec3(this.x - cx(v), this.y - cy(v), this.z - cz(v)) }
130
- mul(v: Vec3Like): Vec3 { return new Vec3(this.x * cx(v), this.y * cy(v), this.z * cz(v)) }
131
- div(v: Vec3Like): Vec3 { return new Vec3(this.x / cx(v), this.y / cy(v), this.z / cz(v)) }
132
- scale(s: number): Vec3 { return new Vec3(this.x * s, this.y * s, this.z * s) }
133
- /** `this + v * s` (fused multiply-add). */
134
- scaleAndAdd(v: Vec3Like, s: number): Vec3 { return new Vec3(this.x + cx(v) * s, this.y + cy(v) * s, this.z + cz(v) * s) }
135
- negate(): Vec3 { return new Vec3(-this.x, -this.y, -this.z) }
136
-
137
- // --- products / measures ---
138
- dot(v: Vec3Like): number { return this.x * cx(v) + this.y * cy(v) + this.z * cz(v) }
139
- cross(v: Vec3Like): Vec3 {
140
- const bx = cx(v), by = cy(v), bz = cz(v)
141
- return new Vec3(this.y * bz - this.z * by, this.z * bx - this.x * bz, this.x * by - this.y * bx)
142
- }
143
- length(): number { return Math.hypot(this.x, this.y, this.z) }
144
- lengthSq(): number { return this.x * this.x + this.y * this.y + this.z * this.z }
145
- distanceTo(v: Vec3Like): number { return Math.hypot(this.x - cx(v), this.y - cy(v), this.z - cz(v)) }
146
- distanceSqTo(v: Vec3Like): number {
147
- const dx = this.x - cx(v), dy = this.y - cy(v), dz = this.z - cz(v)
148
- return dx * dx + dy * dy + dz * dz
149
- }
150
-
151
- normalize(): Vec3 {
152
- const l = Math.hypot(this.x, this.y, this.z)
153
- return l === 0 ? new Vec3(0, 0, 0) : new Vec3(this.x / l, this.y / l, this.z / l)
154
- }
155
- lerp(v: Vec3Like, t: number): Vec3 {
156
- return new Vec3(this.x + (cx(v) - this.x) * t, this.y + (cy(v) - this.y) * t, this.z + (cz(v) - this.z) * t)
157
- }
158
- clamp(min: Vec3Like, max: Vec3Like): Vec3 {
159
- return new Vec3(clampN(this.x, cx(min), cx(max)), clampN(this.y, cy(min), cy(max)), clampN(this.z, cz(min), cz(max)))
160
- }
161
- min(v: Vec3Like): Vec3 { return new Vec3(Math.min(this.x, cx(v)), Math.min(this.y, cy(v)), Math.min(this.z, cz(v))) }
162
- max(v: Vec3Like): Vec3 { return new Vec3(Math.max(this.x, cx(v)), Math.max(this.y, cy(v)), Math.max(this.z, cz(v))) }
163
- /** Reflect this vector about a (unit) `normal`: `v - 2·(v·n)·n`. */
164
- reflect(normal: Vec3Like): Vec3 {
165
- const d = 2 * this.dot(normal)
166
- return new Vec3(this.x - d * cx(normal), this.y - d * cy(normal), this.z - d * cz(normal))
167
- }
168
- /** Vector projection of this onto `onto`. */
169
- project(onto: Vec3Like): Vec3 {
170
- const ox = cx(onto), oy = cy(onto), oz = cz(onto)
171
- const denom = ox * ox + oy * oy + oz * oz
172
- if (denom === 0) return new Vec3(0, 0, 0)
173
- const k = this.dot(onto) / denom
174
- return new Vec3(ox * k, oy * k, oz * k)
175
- }
176
- /** Angle (radians) between this and `v`. */
177
- angle(v: Vec3Like): number {
178
- const bx = cx(v), by = cy(v), bz = cz(v)
179
- const mag = Math.sqrt(this.lengthSq() * (bx * bx + by * by + bz * bz))
180
- return mag === 0 ? 0 : Math.acos(clampN(this.dot(v) / mag, -1, 1))
181
- }
182
-
183
- // --- rotation ---
184
- /** Rotate this point around the X axis through `origin` (default 0) by `rad`. */
185
- rotateX(rad: number, origin: Vec3Like = ZERO3): Vec3 {
186
- const ox = cx(origin), oy = cy(origin), oz = cz(origin)
187
- const py = this.y - oy, pz = this.z - oz, c = Math.cos(rad), s = Math.sin(rad)
188
- return new Vec3(this.x, oy + py * c - pz * s, oz + py * s + pz * c)
189
- }
190
- rotateY(rad: number, origin: Vec3Like = ZERO3): Vec3 {
191
- const ox = cx(origin), oy = cy(origin), oz = cz(origin)
192
- const px = this.x - ox, pz = this.z - oz, c = Math.cos(rad), s = Math.sin(rad)
193
- return new Vec3(ox + pz * s + px * c, this.y, oz + pz * c - px * s)
194
- }
195
- rotateZ(rad: number, origin: Vec3Like = ZERO3): Vec3 {
196
- const ox = cx(origin), oy = cy(origin), oz = cz(origin)
197
- const px = this.x - ox, py = this.y - oy, c = Math.cos(rad), s = Math.sin(rad)
198
- return new Vec3(ox + px * c - py * s, oy + px * s + py * c, this.z)
199
- }
200
- /** Rotate this vector by a quaternion (x, y, z, w). Accepts a `Quat` or a raw `[x,y,z,w]`. */
201
- rotate(q: ArrayLike<number> | { x: number, y: number, z: number, w: number }): Vec3 {
202
- const qx = cx(q as Vec3Like), qy = cy(q as Vec3Like), qz = cz(q as Vec3Like)
203
- const qw = (q as { w?: number }).w ?? (q as ArrayLike<number>)[3]
204
- const vx = this.x, vy = this.y, vz = this.z
205
- // t = 2 * (q.xyz × v)
206
- let tx = qy * vz - qz * vy, ty = qz * vx - qx * vz, tz = qx * vy - qy * vx
207
- tx += tx; ty += ty; tz += tz
208
- // v + w·t + q.xyz × t
209
- return new Vec3(
210
- vx + qw * tx + qy * tz - qz * ty,
211
- vy + qw * ty + qz * tx - qx * tz,
212
- vz + qw * tz + qx * ty - qy * tx,
213
- )
214
- }
215
- /** Transform this point by a column-major mat4 (a `Mat4`, its `.m`, or a raw length-16 array),
216
- * applying translation and the perspective divide. */
217
- transform(m: ArrayLike<number> | { m: ArrayLike<number> }): Vec3 {
218
- const a = (m as { m?: ArrayLike<number> }).m ?? (m as ArrayLike<number>)
219
- const x = this.x, y = this.y, z = this.z
220
- let w = a[3] * x + a[7] * y + a[11] * z + a[15]
221
- w = w || 1
222
- return new Vec3(
223
- (a[0] * x + a[4] * y + a[8] * z + a[12]) / w,
224
- (a[1] * x + a[5] * y + a[9] * z + a[13]) / w,
225
- (a[2] * x + a[6] * y + a[10] * z + a[14]) / w,
226
- )
227
- }
228
-
229
- withX(x: number): Vec3 { return new Vec3(x, this.y, this.z) }
230
- withY(y: number): Vec3 { return new Vec3(this.x, y, this.z) }
231
- withZ(z: number): Vec3 { return new Vec3(this.x, this.y, z) }
232
-
233
- // --- interop ---
234
- equals(v: Vec3Like, eps = 1e-6): boolean {
235
- return approx(this.x, cx(v), eps) && approx(this.y, cy(v), eps) && approx(this.z, cz(v), eps)
236
- }
237
- clone(): Vec3 { return new Vec3(this.x, this.y, this.z) }
238
- toArray(): [number, number, number] { return [ this.x, this.y, this.z ] }
239
- *[Symbol.iterator](): Iterator<number> { yield this.x; yield this.y; yield this.z }
240
-
241
- static from(v: Vec3Like): Vec3 { return new Vec3(cx(v), cy(v), cz(v)) }
242
- static get zero(): Vec3 { return new Vec3(0, 0, 0) }
243
- static get one(): Vec3 { return new Vec3(1, 1, 1) }
244
- static get up(): Vec3 { return new Vec3(0, 1, 0) }
245
- static get down(): Vec3 { return new Vec3(0, -1, 0) }
246
- static get left(): Vec3 { return new Vec3(-1, 0, 0) }
247
- static get right(): Vec3 { return new Vec3(1, 0, 0) }
248
- /** Forward is −Z (the engine's cameras look down −Z; `node.forward` is −Z at identity). */
249
- static get forward(): Vec3 { return new Vec3(0, 0, -1) }
250
- static get back(): Vec3 { return new Vec3(0, 0, 1) }
251
- }
252
-
253
- const clampN = (v: number, lo: number, hi: number): number => (v < lo ? lo : v > hi ? hi : v)
254
- const ZERO2 = new Vec2(0, 0)
255
- const ZERO3 = new Vec3(0, 0, 0)
@@ -1,81 +0,0 @@
1
- // CameraView — the device camera (live preview + still capture) as a Presentable. A typed wrapper
2
- // over the `NativeView("camera")` capability (docs/navigation-presentable-plan.md): open it
3
- // fullscreen (`await camera.open()`), push it (`Router.push(camera)`), or embed it among a
4
- // screen's children as a live preview node. `takePhoto()` returns a `File` (a host buffer
5
- // handle) — use it as a texture (`Texture2D.load(file)`), upload it (`FormData`), or `share` it.
6
- //
7
- // Host-OPTIONAL: it exists where the host registers a "camera" view (Android; web can via
8
- // `_creatorUI._registerView`) — gate on `CameraView.isSupported`. Opening prepares first (camera
9
- // permission) while the current destination stays visible — the same contract as ARScene/QRScanner.
10
- //
11
- // The pre-Presentable `_creatorUtils.camera*` bridges are gone from the ABI (2026-07-15): hosts
12
- // keep their implementations only to serve previously compiled bundles.
13
-
14
- import { File } from "../runtime/fetch"
15
- import { _navEpoch, type PresentOptions } from "../ui/presentable"
16
- import { NativeView, NativeViewElement } from "../ui/NativeView"
17
- import { _requestCameraPermission } from "./permission"
18
-
19
- export type CameraFacing = "front" | "back"
20
-
21
- export interface CameraView extends NativeView {
22
- /** Request camera permission, then present the preview. The current destination stays visible
23
- * until the camera is ready; rejects if permission is denied, the host has no camera, or
24
- * another navigation superseded this one. */
25
- open(options?: PresentOptions): Promise<void>
26
- /** Capture a still frame as an image `File` (host-encoded). */
27
- takePhoto(): Promise<File>
28
- /** Switch between the front and back camera while the preview is live. */
29
- setFacingMode(facingMode: CameraFacing): Promise<void>
30
- }
31
-
32
- class CameraViewElement extends NativeViewElement {
33
- constructor(options: { facingMode?: CameraFacing } = {}) {
34
- super("camera", { facingMode: options.facingMode ?? "back" })
35
- }
36
-
37
- /** @internal prepare-then-present: permission before the swap (see Presentable._prepare). */
38
- async _prepare(): Promise<void> {
39
- const epoch = _navEpoch()
40
- await _requestCameraPermission()
41
- if (_navEpoch() !== epoch) {
42
- throw new Error("CameraView.open() superseded: another destination was opened while preparing")
43
- }
44
- }
45
-
46
- override async open(options?: PresentOptions): Promise<void> {
47
- // Fail before prompting for the camera — see QRScanner.
48
- if (!NativeView.isSupported("camera")) {
49
- throw new Error("CameraView isn't available on this host — it needs a registered \"camera\" view.")
50
- }
51
- await this._prepare()
52
- super.open(options)
53
- }
54
-
55
- async takePhoto(): Promise<File> {
56
- const photo = await this.call("takePhoto")
57
- return new File(photo.systemId, photo.name, photo.size)
58
- }
59
-
60
- async setFacingMode(facingMode: CameraFacing): Promise<void> {
61
- await this.call("setFacingMode", facingMode)
62
- }
63
- }
64
-
65
- /**
66
- * Create a camera view (default: the back camera). `CameraView.isSupported` reports whether this
67
- * host has a camera capability — check it before offering the feature.
68
- */
69
- // PURE IIFE so an app that never uses the camera tree-shakes the whole plugin away (see NativeView).
70
- export const CameraView: {
71
- (options?: { facingMode?: CameraFacing }): CameraView
72
- /** Whether this host registered a "camera" view. */
73
- readonly isSupported: boolean
74
- } = /*#__PURE__*/ (() => {
75
- const factory = (options?: { facingMode?: CameraFacing }): CameraView =>
76
- new CameraViewElement(options) as unknown as CameraView
77
- Object.defineProperty(factory, "isSupported", {
78
- get: (): boolean => NativeView.isSupported("camera"),
79
- })
80
- return factory as any
81
- })()
@@ -1,123 +0,0 @@
1
- // Geolocation — the device position as a typed service plugin (docs/service-channel-plan.md).
2
- // A wrapper over the host-registered "geolocation" service, exactly like QRScanner wraps the
3
- // "qrScanner" NativeView — except services have no UI surface, so there is nothing to present:
4
- // just `getCurrent()` and `watch()`.
5
- //
6
- // Host-OPTIONAL, like every plugin: it exists where the host registered a "geolocation" service
7
- // (web hosts do out of the box; Android via MainActivity) — gate on `Geolocation.isSupported`.
8
- // The OS permission prompt happens on the first call that needs it (host-side); denial is a
9
- // rejection. Both methods reject immediately on unsupported hosts, BEFORE any permission path.
10
- //
11
- // Service contract ("geolocation"): calls `getCurrent(options) → GeoPosition`,
12
- // `startWatch(options)` (resolves once watching), `stopWatch()`; event `position` → GeoPosition.
13
-
14
- import { ServiceClient } from "../runtime/service"
15
-
16
- export interface GeoPosition {
17
- latitude: number
18
- longitude: number
19
- /** Horizontal accuracy radius, meters. */
20
- accuracy: number
21
- /** Meters above sea level, or null when the provider doesn't know. */
22
- altitude: number | null
23
- /** Direction of travel in degrees clockwise from north, or null when stationary/unknown. */
24
- heading: number | null
25
- /** Ground speed in m/s, or null when unknown. */
26
- speed: number | null
27
- /** When this fix was taken, ms epoch. */
28
- timestamp: number
29
- }
30
-
31
- export interface GeoOptions {
32
- /** Prefer the precise (GPS) provider — slower first fix, more battery. Default false. */
33
- highAccuracy?: boolean
34
- /** getCurrent only: reject with "timeout" if no fix arrives in this many ms. */
35
- timeout?: number
36
- }
37
-
38
- /** A live `Geolocation.watch()` subscription — `stop()` it when the screen goes away. */
39
- export interface GeoWatch {
40
- stop(): void
41
- }
42
-
43
- /**
44
- * The device position. `Geolocation.isSupported` reports whether this host registered the
45
- * service — check it before offering location features.
46
- */
47
- // PURE IIFE so an app that never uses location tree-shakes the whole plugin away (see NativeView).
48
- export const Geolocation: {
49
- /** Whether this host registered a "geolocation" service. */
50
- readonly isSupported: boolean
51
- /** One position fix. Prompts for permission on first use; rejects on denial ("denied"), no
52
- * provider ("unavailable"), or `options.timeout` elapsing ("timeout"). */
53
- getCurrent(options?: GeoOptions): Promise<GeoPosition>
54
- /** Continuous updates. Resolves once watching (permission granted + provider started) — so a
55
- * denial is a rejection, never a silently dead callback. One host watch serves every
56
- * subscriber; the options of the watch that starts it win. Always `stop()` when done. */
57
- watch(callback: (position: GeoPosition) => void, options?: GeoOptions): Promise<GeoWatch>
58
- } = /*#__PURE__*/ (() => {
59
- let client: ServiceClient | null = null
60
- const service = (): ServiceClient => client ??= new ServiceClient("geolocation")
61
-
62
- // Watch state: one host watch fans out to N subscribers (SDK-side), the last stop() releases it.
63
- const watchers: ((position: GeoPosition) => void)[] = []
64
- let positionHooked = false
65
- let startPromise: Promise<void> | null = null
66
-
67
- const assertSupported = (): void => {
68
- // Fail before the host is touched — a permission dialog for a feature that can't exist
69
- // would be worse than the error (same ordering rule as QRScanner.open).
70
- if (!ServiceClient.isSupported("geolocation")) {
71
- throw new Error('Geolocation isn\'t available on this host — it needs a registered "geolocation" service.')
72
- }
73
- }
74
-
75
- const removeWatcher = (callback: (position: GeoPosition) => void): void => {
76
- const i = watchers.indexOf(callback)
77
- if (i >= 0) watchers.splice(i, 1)
78
- if (watchers.length === 0 && startPromise !== null) {
79
- startPromise = null
80
- service().call("stopWatch").catch(() => {})
81
- }
82
- }
83
-
84
- const geolocation = {
85
- async getCurrent(options?: GeoOptions): Promise<GeoPosition> {
86
- assertSupported()
87
- return await service().call("getCurrent", options ?? {})
88
- },
89
-
90
- async watch(callback: (position: GeoPosition) => void, options?: GeoOptions): Promise<GeoWatch> {
91
- assertSupported()
92
- if (!positionHooked) {
93
- positionHooked = true
94
- service().on("position", (position?: GeoPosition) => {
95
- if (position) for (const cb of [...watchers]) cb(position)
96
- })
97
- }
98
- watchers.push(callback)
99
- startPromise ??= service().call("startWatch", options ?? {})
100
- try {
101
- await startPromise
102
- } catch (e) {
103
- // The watch never started — unwind without the stopWatch a live watch would need.
104
- const i = watchers.indexOf(callback)
105
- if (i >= 0) watchers.splice(i, 1)
106
- if (watchers.length === 0) startPromise = null
107
- throw e
108
- }
109
- let stopped = false
110
- return {
111
- stop(): void {
112
- if (stopped) return
113
- stopped = true
114
- removeWatcher(callback)
115
- },
116
- }
117
- },
118
- }
119
- Object.defineProperty(geolocation, "isSupported", {
120
- get: (): boolean => ServiceClient.isSupported("geolocation"),
121
- })
122
- return geolocation as any
123
- })()
@@ -1,61 +0,0 @@
1
- // OAuth — native provider sign-in as a typed service plugin (docs/backend-plan.md §3.4). The
2
- // browser broker (`auth.oauth.url()` on the server + `openURL` in the app) works everywhere; on
3
- // hosts that ship a provider SDK (Yandex LoginSDK, VK ID, Sign in with Apple, Google Sign-In) the
4
- // user shouldn't leave the app — the host registers an "oauth" service that runs the native sheet
5
- // and hands back the provider's CREDENTIAL. The app then calls its own endpoint, which does
6
- // `auth.oauth.signIn(provider, credential)`: the server verifies the credential with the provider
7
- // (audience = the platform's app ids) and binds the device session. No token is ever trusted
8
- // client-side; the credential is opaque to the app.
9
- //
10
- // Host-OPTIONAL — gate on `OAuth.isSupported(provider)`; fall back to the browser broker otherwise.
11
- //
12
- // Service contract ("oauth"): call `signIn(provider) → OAuthCredential` (rejects "cancelled" /
13
- // "unavailable"), call `providers() → string[]` (which providers this host has SDKs for).
14
-
15
- import { ServiceClient } from "../runtime/service"
16
-
17
- export type OAuthProviderName = "google" | "apple" | "yandex" | "vk"
18
-
19
- /** What a native provider SDK yields — pass it verbatim to your endpoint / `auth.oauth.signIn`. */
20
- export type OAuthCredential = {
21
- provider: OAuthProviderName
22
- /** OAuth access token (Yandex, VK ID). */
23
- accessToken?: string
24
- /** OpenID identity token (Google, Apple). */
25
- idToken?: string
26
- /** Apple only sends the name once, on the first sign-in — forward it so the server can store it. */
27
- name?: string
28
- email?: string
29
- }
30
-
31
- /**
32
- * Native provider sign-in. `OAuth.isSupported` reports whether this host registered the service;
33
- * `OAuth.providers()` which providers it has SDKs for. Otherwise open `auth.oauth.url(provider)` (server)
34
- * with `openURL` — the browser broker.
35
- */
36
- // PURE IIFE so an app that never uses it tree-shakes the plugin away (see Geolocation).
37
- export const OAuth: {
38
- /** Whether this host registered an "oauth" service. */
39
- readonly isSupported: boolean
40
- /** The providers this host can sign in with natively. */
41
- providers(): Promise<OAuthProviderName[]>
42
- /** Run the provider's native sheet; resolves with the credential for `auth.oauth.signIn`. Rejects "cancelled" / "unavailable". */
43
- signIn(provider: OAuthProviderName): Promise<OAuthCredential>
44
- } = /*#__PURE__*/ (() => {
45
- let client: ServiceClient | null = null
46
- const service = (): ServiceClient => client ??= new ServiceClient("oauth")
47
- const assertSupported = (): void => {
48
- if (!ServiceClient.isSupported("oauth")) {
49
- throw new Error("OAuth isn't available on this host — it needs a registered \"oauth\" service; open auth.oauth.url(provider) in the browser instead.")
50
- }
51
- }
52
- return {
53
- get isSupported() { return ServiceClient.isSupported("oauth") },
54
- providers: async () => { assertSupported(); const p = await service().call("providers"); return Array.isArray(p) ? p : [] },
55
- signIn: async (provider) => {
56
- assertSupported()
57
- const r = await service().call("signIn", provider)
58
- return { provider, ...(r ?? {}) }
59
- },
60
- }
61
- })()
@@ -1,7 +0,0 @@
1
- // Camera permission, host-gated: hosts expose it as `_creatorUtils.requestCamera`. Hosts without
2
- // that bridge resolve immediately — their view factory prompts on its own (e.g. getUserMedia on
3
- // the web). Shared by ARScene.prepare, QRScanner and CameraView.
4
- export const _requestCameraPermission = (): Promise<void> =>
5
- typeof _creatorUtils.requestCamera === "function"
6
- ? new Promise<void>((res, rej) => _creatorUtils.requestCamera!(res, rej))
7
- : Promise.resolve()
@@ -1,132 +0,0 @@
1
- // Push — remote notifications as a typed service plugin (docs/push-plan.md). A wrapper over
2
- // the host-registered "push" service, the Geolocation recipe applied to notifications: the
3
- // host owns the OS token, the permission prompt, and the platform registration with the
4
- // le.codes backend; the app only ever sees the opaque project-scoped ADDRESS (never the raw
5
- // APNs/FCM token) plus payloads.
6
- //
7
- // Host-OPTIONAL, like every plugin — gate on `Push.isSupported`. The OS permission prompt
8
- // happens inside `register()` (the first call that needs it, host-side); `getStatus()` and
9
- // `getLaunch()` never prompt. On hosts without the service every method rejects immediately,
10
- // BEFORE any permission path.
11
- //
12
- // The app is not running when a push arrives — display is host/OS work. The JS world sees
13
- // exactly three delivery paths:
14
- // - event "message": arrived while this app was foregrounded (the host suppresses the banner)
15
- // - event "tap": tapped while the world was alive (warm)
16
- // - getLaunch(): the notification that cold-started this world (stable pull, or null)
17
- // A payload's `url` additionally rides the app-lifecycle deep-link path (`app.launchUrl` /
18
- // the "url" event), so apps that already route links get notification routing for free.
19
- //
20
- // Service contract ("push"): calls `getStatus() → PushStatus`, `register({user?}) → {address}`
21
- // (rejects "denied"/"unavailable"), `unregister()`, `getLaunch() → PushPayload|null`;
22
- // events `message`/`tap` → PushPayload. An OPEN SESSION is the host's delivery pipe for the
23
- // events — addEventListener opens one eagerly (cheap by the channel contract: no prompt, no I/O).
24
-
25
- import { ServiceClient } from "../runtime/service"
26
-
27
- /** A delivered notification, as the app sees it. */
28
- export interface PushPayload {
29
- title: string
30
- body?: string
31
- /** Deep link — also delivered through `app.launchUrl` / the app "url" event. */
32
- url?: string
33
- /** The developer's own JSON, ≤ 2 KB serialized. */
34
- data?: Record<string, any>
35
- badge?: number
36
- }
37
-
38
- export interface PushStatus {
39
- /** OS notification permission for the host app. "prompt" = not asked yet. */
40
- permission: "granted" | "denied" | "prompt"
41
- /** Whether this device already holds a registration (an address) for this app. */
42
- registered: boolean
43
- }
44
-
45
- export interface PushRegisterOptions {
46
- /** Your app's OWN user id ("external user id"): lets your server send to `users: ["u42"]`
47
- * instead of storing addresses, fanning out to all the user's devices. CLIENT-ASSERTED and
48
- * unverified — never target sensitive content by user. The registration reflects the latest
49
- * `register()` call, so registering without `user` clears it (logout). */
50
- user?: string
51
- }
52
-
53
- export type PushEvent = "message" | "tap"
54
-
55
- /**
56
- * Remote push notifications. `Push.isSupported` reports whether this host registered the
57
- * service — check it before offering notification features.
58
- */
59
- // PURE IIFE so an app that never uses push tree-shakes the whole plugin away (see Geolocation).
60
- export const Push: {
61
- /** Whether this host registered a "push" service. */
62
- readonly isSupported: boolean
63
- /** Permission + registration state. Never prompts. */
64
- getStatus(): Promise<PushStatus>
65
- /** Ask for permission (first time) and register this device → the opaque address your server
66
- * targets. Idempotent — safe to call every launch; rejects "denied" / "unavailable". */
67
- register(options?: PushRegisterOptions): Promise<{ address: string }>
68
- /** Remove this device's registration (the address stops receiving). */
69
- unregister(): Promise<void>
70
- /** The notification that cold-started this app run, or null. Stable across calls. */
71
- getLaunch(): Promise<PushPayload | null>
72
- /** "message" = received while the app is open; "tap" = the user tapped one while it ran. */
73
- addEventListener(event: PushEvent, callback: (payload: PushPayload) => void): void
74
- removeEventListener(event: PushEvent, callback: (payload: PushPayload) => void): void
75
- } = /*#__PURE__*/ (() => {
76
- let client: ServiceClient | null = null
77
- const service = (): ServiceClient => client ??= new ServiceClient("push")
78
-
79
- const assertSupported = (): void => {
80
- // Fail before the host is touched — a permission dialog for a feature that can't exist
81
- // would be worse than the error (same ordering rule as QRScanner/Geolocation).
82
- if (!ServiceClient.isSupported("push")) {
83
- throw new Error('Push isn\'t available on this host — it needs a registered "push" service.')
84
- }
85
- }
86
-
87
- // ServiceClient.on only records listeners; events need a live session to arrive on. Poke the
88
- // host with the prompt-free getStatus once so a listener-only app (already registered on a
89
- // previous run) still hears warm taps.
90
- let pipeOpened = false
91
- const ensurePipe = (): void => {
92
- if (pipeOpened || !ServiceClient.isSupported("push")) return
93
- pipeOpened = true
94
- service().call("getStatus").catch(() => { pipeOpened = false })
95
- }
96
-
97
- const push = {
98
- async getStatus(): Promise<PushStatus> {
99
- assertSupported()
100
- return await service().call("getStatus")
101
- },
102
-
103
- async register(options?: PushRegisterOptions): Promise<{ address: string }> {
104
- assertSupported()
105
- return await service().call("register", options ?? {})
106
- },
107
-
108
- async unregister(): Promise<void> {
109
- assertSupported()
110
- await service().call("unregister")
111
- },
112
-
113
- async getLaunch(): Promise<PushPayload | null> {
114
- assertSupported()
115
- return (await service().call("getLaunch")) ?? null
116
- },
117
-
118
- addEventListener(event: PushEvent, callback: (payload: PushPayload) => void): void {
119
- // Recording on an unsupported host is a silent no-op (the events just never fire),
120
- // like app/device listeners — only the promise-returning methods reject loudly.
121
- service().on(event, callback as (data?: any) => void)
122
- ensurePipe()
123
- },
124
- removeEventListener(event: PushEvent, callback: (payload: PushPayload) => void): void {
125
- service().off(event, callback as (data?: any) => void)
126
- },
127
- }
128
- Object.defineProperty(push, "isSupported", {
129
- get: (): boolean => ServiceClient.isSupported("push"),
130
- })
131
- return push as any
132
- })()