lecodes-sdk 1.1.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 (257) hide show
  1. package/README.md +5 -2
  2. package/dist/editor.d.ts +12 -0
  3. package/dist/global.d.ts +4 -8
  4. package/dist/host.d.ts +2 -3
  5. package/dist/types/animate/tween/Animation.d.ts +0 -3
  6. package/dist/types/animate/tween/animateValue.d.ts +4 -2
  7. package/dist/types/animate/tween/easing.d.ts +8 -0
  8. package/dist/types/animate/tween/spec.d.ts +15 -7
  9. package/dist/types/audio/audio.d.ts +2 -1
  10. package/dist/types/canvas/Canvas.d.ts +40 -108
  11. package/dist/types/canvas/gen/cssColor.d.ts +17 -0
  12. package/dist/types/canvas/gen/recorder.d.ts +118 -0
  13. package/dist/types/canvas/gen/spec.d.ts +144 -0
  14. package/dist/types/core/color.d.ts +3 -1
  15. package/dist/types/core/pins.d.ts +18 -0
  16. package/dist/types/g2/Node2D.d.ts +5 -8
  17. package/dist/types/g2/Scene2D.d.ts +6 -2
  18. package/dist/types/gl/Foliage.d.ts +30 -7
  19. package/dist/types/gl/Light.d.ts +11 -0
  20. package/dist/types/gl/Lightmap.d.ts +13 -2
  21. package/dist/types/gl/Material.d.ts +17 -3
  22. package/dist/types/gl/Model.d.ts +6 -2
  23. package/dist/types/gl/Node.d.ts +3 -6
  24. package/dist/types/gl/Particles.d.ts +15 -0
  25. package/dist/types/gl/Scene.d.ts +41 -13
  26. package/dist/types/gl/Texture.d.ts +1 -1
  27. package/dist/types/gl/animation/Locomotion.d.ts +59 -3
  28. package/dist/types/gl/terrain/Terrain.d.ts +8 -0
  29. package/dist/types/inject.d.ts +11 -11
  30. package/dist/types/inject.editor.d.ts +1 -0
  31. package/dist/types/net/core.d.ts +7 -0
  32. package/dist/types/plugin.d.ts +76 -0
  33. package/dist/types/plugins/gen/camera/sdk/camera.d.ts +24 -0
  34. package/dist/types/plugins/gen/camera/sdk/camera.gen.d.ts +25 -0
  35. package/dist/types/plugins/{geolocation.d.ts → gen/geolocation/sdk/geolocation.d.ts} +2 -20
  36. package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +31 -0
  37. package/dist/types/plugins/{map.d.ts → gen/map/sdk/map.d.ts} +9 -50
  38. package/dist/types/plugins/gen/map/sdk/map.gen.d.ts +53 -0
  39. package/dist/types/plugins/gen/push/sdk/push.d.ts +23 -0
  40. package/dist/types/plugins/gen/push/sdk/push.gen.d.ts +35 -0
  41. package/dist/types/plugins/{qr.d.ts → gen/qr-scanner/sdk/qr-scanner.d.ts} +2 -3
  42. package/dist/types/plugins/gen/qr-scanner/sdk/qr-scanner.gen.d.ts +15 -0
  43. package/dist/types/runtime/app.d.ts +9 -2
  44. package/dist/types/runtime/fetch.d.ts +2 -0
  45. package/dist/types/runtime/input.d.ts +8 -1
  46. package/dist/types/runtime/media.d.ts +6 -10
  47. package/dist/types/runtime/misc.d.ts +4 -1
  48. package/dist/types/runtime/net.d.ts +3 -2
  49. package/dist/types/runtime/touch.d.ts +32 -0
  50. package/dist/types/scene/defineScene.d.ts +43 -2
  51. package/dist/types/scene/editor.d.ts +52 -0
  52. package/dist/types/scene/gizmos.d.ts +7 -4
  53. package/dist/types/ui/NativeView.d.ts +6 -4
  54. package/dist/types/ui/UI.d.ts +1 -1
  55. package/dist/types/ui/UIBottomSheet.d.ts +6 -12
  56. package/dist/types/ui/UIButton.d.ts +12 -14
  57. package/dist/types/ui/UIContainer.d.ts +0 -6
  58. package/dist/types/ui/UIImage.d.ts +1 -4
  59. package/dist/types/ui/UIInput.d.ts +8 -24
  60. package/dist/types/ui/UIModal.d.ts +0 -2
  61. package/dist/types/ui/UINode.d.ts +70 -56
  62. package/dist/types/ui/UIPager.d.ts +28 -27
  63. package/dist/types/ui/UIPopover.d.ts +0 -2
  64. package/dist/types/ui/UIScreen.d.ts +12 -17
  65. package/dist/types/ui/UIScrollable.d.ts +1 -4
  66. package/dist/types/ui/UIText.d.ts +0 -2
  67. package/dist/types/ui/UIVideo.d.ts +3 -5
  68. package/dist/types/ui/UIVirtualizedList.d.ts +14 -16
  69. package/dist/types/ui/UIWidget.d.ts +10 -9
  70. package/dist/types/ui/colorKeys.gen.d.ts +9 -0
  71. package/dist/types/ui/presentable.d.ts +46 -32
  72. package/dist/types/ui/router.d.ts +18 -7
  73. package/dist/types/ui/styleColor.d.ts +1 -0
  74. package/dist/types/ui/transitions.d.ts +18 -0
  75. package/dist/types/ui/tree.d.ts +75 -0
  76. package/dist/types/version.d.ts +10 -0
  77. package/dist/types.json +1 -1
  78. package/package.json +12 -3
  79. package/prompts/2d.md +2 -6
  80. package/prompts/3d.md +1 -5
  81. package/prompts/README.md +142 -142
  82. package/prompts/canvas.md +9 -8
  83. package/prompts/compose.ts +1 -1
  84. package/prompts/core.md +3 -3
  85. package/prompts/design.md +1 -1
  86. package/prompts/dist/2d-game.md +77 -54
  87. package/prompts/dist/3d-app.md +75 -52
  88. package/prompts/dist/ar-app.md +74 -51
  89. package/prompts/dist/design.md +30 -8
  90. package/prompts/dist/ui-app.md +73 -46
  91. package/prompts/ui-design.md +2 -3
  92. package/prompts/ui.md +45 -37
  93. package/src/animate/tween/Animation.ts +262 -378
  94. package/src/animate/tween/animateValue.ts +103 -100
  95. package/src/animate/tween/easing.ts +10 -3
  96. package/src/animate/tween/spec.ts +505 -479
  97. package/src/audio/Sound.ts +3 -3
  98. package/src/audio/audio.ts +162 -161
  99. package/src/bridges/2d.d.ts +317 -0
  100. package/src/bridges/app.d.ts +91 -0
  101. package/src/bridges/audio.d.ts +97 -0
  102. package/src/bridges/canvas.d.ts +79 -0
  103. package/src/bridges/device.d.ts +72 -0
  104. package/src/bridges/fetch.d.ts +80 -0
  105. package/src/bridges/files.d.ts +70 -0
  106. package/src/bridges/gl.d.ts +1133 -0
  107. package/src/bridges/input.d.ts +72 -0
  108. package/src/bridges/media.d.ts +55 -0
  109. package/src/bridges/nav.d.ts +71 -0
  110. package/src/bridges/net.d.ts +52 -0
  111. package/src/bridges/service.d.ts +52 -0
  112. package/src/bridges/socket.d.ts +31 -0
  113. package/src/bridges/storage.d.ts +33 -0
  114. package/src/bridges/tree.d.ts +301 -0
  115. package/src/bridges/types.d.ts +49 -0
  116. package/src/canvas/Canvas.ts +114 -159
  117. package/src/canvas/gen/cssColor.ts +224 -0
  118. package/src/canvas/gen/recorder.ts +212 -0
  119. package/src/canvas/gen/spec.ts +201 -0
  120. package/src/chisel.ts +193 -0
  121. package/src/compile/assetMacro.ts +1 -1
  122. package/src/compile/bundler.ts +11 -2
  123. package/src/compile/compileProject.ts +43 -4
  124. package/src/compile/fontMacro.ts +3 -4
  125. package/src/compile/header.ts +26 -5
  126. package/src/compile/index.ts +3 -1
  127. package/src/compile/liteMaterial.ts +1 -1
  128. package/src/compile/sceneEditor.ts +11 -26
  129. package/src/core/color.ts +73 -30
  130. package/src/core/pins.ts +51 -0
  131. package/src/core/signals.ts +8 -1
  132. package/src/g2/CharacterController2D.ts +3 -3
  133. package/src/g2/Node2D.ts +57 -39
  134. package/src/g2/Physics2D.ts +2 -2
  135. package/src/g2/Scene2D.ts +35 -23
  136. package/src/g2/Texture2D.ts +1 -1
  137. package/src/g2/loop.ts +4 -4
  138. package/src/gl/Foliage.ts +72 -17
  139. package/src/gl/Geometry.ts +1 -2
  140. package/src/gl/Light.ts +13 -0
  141. package/src/gl/Lightmap.ts +45 -29
  142. package/src/gl/Material.ts +95 -51
  143. package/src/gl/Model.ts +172 -167
  144. package/src/gl/Node.ts +91 -24
  145. package/src/gl/Particles.ts +20 -1
  146. package/src/gl/Scene.ts +100 -44
  147. package/src/gl/Texture.ts +8 -7
  148. package/src/gl/animation/AnimationClip.ts +1 -1
  149. package/src/gl/animation/Locomotion.ts +77 -8
  150. package/src/gl/nav/NavMesh.ts +3 -4
  151. package/src/gl/physics/Physics.ts +2 -2
  152. package/src/gl/physics/physicsEvents.ts +3 -3
  153. package/src/gl/terrain/Terrain.ts +33 -5
  154. package/src/gl/touch.ts +14 -15
  155. package/src/host.d.ts +2 -3
  156. package/src/inject.editor.ts +7 -0
  157. package/src/inject.ts +233 -236
  158. package/src/net/core.ts +6 -5
  159. package/src/net/index.ts +1 -1
  160. package/src/net/replication.ts +1 -1
  161. package/src/plugin.ts +191 -0
  162. package/src/plugins/gen/camera/contract.d.ts +27 -0
  163. package/src/plugins/gen/camera/sdk/camera.gen.ts +46 -0
  164. package/src/plugins/gen/camera/sdk/camera.ts +57 -0
  165. package/src/plugins/gen/geolocation/contract.d.ts +50 -0
  166. package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +54 -0
  167. package/src/plugins/{geolocation.ts → gen/geolocation/sdk/geolocation.ts} +22 -43
  168. package/src/plugins/gen/map/contract.d.ts +144 -0
  169. package/src/plugins/gen/map/sdk/map.gen.ts +88 -0
  170. package/src/plugins/{map.ts → gen/map/sdk/map.ts} +68 -102
  171. package/src/plugins/gen/push/contract.d.ts +61 -0
  172. package/src/plugins/gen/push/sdk/push.gen.ts +60 -0
  173. package/src/plugins/gen/push/sdk/push.ts +105 -0
  174. package/src/plugins/gen/qr-scanner/contract.d.ts +16 -0
  175. package/src/plugins/gen/qr-scanner/sdk/qr-scanner.gen.ts +29 -0
  176. package/src/plugins/gen/qr-scanner/sdk/qr-scanner.ts +52 -0
  177. package/src/plugins/permission.ts +5 -4
  178. package/src/runtime/app.ts +20 -9
  179. package/src/runtime/appEvents.ts +5 -4
  180. package/src/runtime/channel.ts +18 -15
  181. package/src/runtime/clipboard.ts +4 -3
  182. package/src/runtime/datetime.ts +2 -1
  183. package/src/runtime/device.ts +17 -15
  184. package/src/runtime/fetch.ts +30 -20
  185. package/src/runtime/files.ts +16 -15
  186. package/src/runtime/input.ts +21 -8
  187. package/src/runtime/media.ts +50 -46
  188. package/src/runtime/misc.ts +7 -3
  189. package/src/runtime/net.ts +8 -7
  190. package/src/runtime/rpc.ts +1 -3
  191. package/src/runtime/service.ts +19 -14
  192. package/src/runtime/share.ts +4 -3
  193. package/src/runtime/storage.ts +6 -4
  194. package/src/runtime/touch.ts +32 -0
  195. package/src/scene/defineScene.ts +61 -365
  196. package/src/scene/editor.ts +408 -0
  197. package/src/scene/editorPlugins.ts +3 -3
  198. package/src/scene/gizmos.ts +154 -148
  199. package/src/server/db/marci/query.ts +1 -1
  200. package/src/server/host.ts +1 -1
  201. package/src/server/runtime.ts +1 -1
  202. package/src/ui/NativeView.ts +72 -25
  203. package/src/ui/UI.ts +3 -3
  204. package/src/ui/UIBottomSheet.ts +16 -17
  205. package/src/ui/UIButton.ts +54 -16
  206. package/src/ui/UIContainer.ts +0 -6
  207. package/src/ui/UIImage.ts +34 -30
  208. package/src/ui/UIInput.ts +29 -37
  209. package/src/ui/UIModal.ts +1 -3
  210. package/src/ui/UINode.ts +347 -297
  211. package/src/ui/UIPager.ts +93 -78
  212. package/src/ui/UIPopover.ts +0 -2
  213. package/src/ui/UIScreen.ts +41 -36
  214. package/src/ui/UIScrollable.ts +19 -13
  215. package/src/ui/UISpacer.ts +1 -1
  216. package/src/ui/UITabs.ts +8 -6
  217. package/src/ui/UIText.ts +7 -17
  218. package/src/ui/UIVideo.ts +30 -27
  219. package/src/ui/UIVirtualizedList.ts +58 -59
  220. package/src/ui/UIWidget.ts +38 -18
  221. package/src/ui/colorKeys.gen.ts +37 -0
  222. package/src/ui/fonts.ts +2 -2
  223. package/src/ui/presentable.ts +59 -42
  224. package/src/ui/router.ts +52 -35
  225. package/src/ui/styleColor.ts +56 -0
  226. package/src/ui/theme.ts +7 -6
  227. package/src/ui/transitions.ts +249 -0
  228. package/src/ui/tree.ts +346 -0
  229. package/src/version.ts +24 -0
  230. package/tests/helpers/engineWorld.ts +11 -0
  231. package/tests/helpers/fakeTree.ts +353 -0
  232. package/tests/helpers/hostStubs.ts +31 -0
  233. package/tests/helpers/index.ts +14 -0
  234. package/tests/helpers/memoryMarci.ts +124 -0
  235. package/tests/helpers/phases.ts +23 -0
  236. package/tests/helpers/preload.ts +18 -0
  237. package/tests/helpers/stubApp.ts +2 -0
  238. package/tests/helpers/stubDevice.ts +2 -0
  239. package/tests/helpers/stubFetch.ts +2 -0
  240. package/tests/helpers/stubInput.ts +2 -0
  241. package/dist/inject.js +0 -4730
  242. package/dist/types/core/registry.d.ts +0 -7
  243. package/dist/types/plugins/camera.d.ts +0 -25
  244. package/dist/types/plugins/push.d.ts +0 -46
  245. package/src/bridges.d.ts +0 -1760
  246. package/src/compile/__tests__/assetIconMacro.test.ts +0 -219
  247. package/src/compile/__tests__/assetMacro.test.ts +0 -100
  248. package/src/compile/__tests__/assetName.test.ts +0 -55
  249. package/src/compile/__tests__/compile.test.ts +0 -310
  250. package/src/compile/__tests__/detectEntry.test.ts +0 -151
  251. package/src/compile/__tests__/fontMacro.test.ts +0 -199
  252. package/src/compile/__tests__/serverSplit.test.ts +0 -27
  253. package/src/core/__tests__/stateMachine.test.ts +0 -132
  254. package/src/core/registry.ts +0 -23
  255. package/src/plugins/camera.ts +0 -81
  256. package/src/plugins/push.ts +0 -132
  257. package/src/plugins/qr.ts +0 -73
@@ -407,7 +407,7 @@ const sendSnapshots = (): void => {
407
407
 
408
408
  // ---- client: snapshots in, inputs out, the clock, interpolation, correction ----------------------------
409
409
 
410
- const rttTicks = (): number => (bridge()?.clientInfo(0)[1] ?? 0) / TICK_MS
410
+ const rttTicks = (): number => (bridge()?.clientInfo(0)?.rtt ?? 0) / TICK_MS
411
411
 
412
412
  const onClientBinary = (channel: number, data: ArrayBuffer): void => {
413
413
  if (data.byteLength < 12) return
package/src/plugin.ts ADDED
@@ -0,0 +1,191 @@
1
+ // `lecodes-sdk/plugin` — the one entry a PLUGIN imports the SDK through (docs/plans/plugins-plan.md).
2
+ // Two readers:
3
+ // - a plugin's `contract.d.ts`: the channel markers and the precise value types the generator maps
4
+ // to Swift / Kotlin;
5
+ // - its `sdk/` wrapper, generated (`<id>.gen.ts`) and hand-written (`<id>.ts`): the two channel
6
+ // bases and the few SDK internals a wrapper needs.
7
+ //
8
+ // A contract describes the WIRE of one channel, never the app-facing API: three interfaces per
9
+ // channel — the methods (it extends a marker), the params, the events.
10
+
11
+ import { File as HostFile } from "./runtime/fetch"
12
+ import { ServiceClient } from "./runtime/service"
13
+ import { NativeView, NativeViewElement } from "./ui/NativeView"
14
+ import { _navEpoch } from "./ui/presentable"
15
+ import { _requestCameraPermission } from "./plugins/permission"
16
+
17
+ export type { PresentOptions } from "./ui/presentable"
18
+ export type { NativeView } from "./ui/NativeView"
19
+ export type { FetchResponse } from "./runtime/fetch"
20
+
21
+ // ---- the contract's vocabulary ---------------------------------------------------------------------
22
+
23
+ /** Signed 32-bit integer on the wire. */
24
+ export type i32 = number
25
+ /** Double on the wire — also every integer that can leave 32 bits (a millisecond time stamp). */
26
+ export type f64 = number
27
+
28
+ /** Bytes that STAY in the host's buffer table: the native half hands over its bytes + a name, the
29
+ * app gets the SDK's `File` handle (a texture source, a `FormData` value, a share) and the bytes
30
+ * never enter the JS heap. For bytes the app must read itself, a contract says `Uint8Array`. */
31
+ export type File = HostFile
32
+
33
+ /** Any JSON value, opaque to the generator (GeoJSON, a developer's own payload): it crosses as is
34
+ * and the native half receives the platform's untyped value. */
35
+ export type Json = null | boolean | number | string | Json[] | { [key: string]: Json }
36
+
37
+ declare const channel: unique symbol
38
+
39
+ /** Marks a contract interface as a NativeView channel. `Name` is what the halves register under
40
+ * (`registerView(name)`); `Params` is what a factory receives; `Events` maps an event's name to
41
+ * its payload (`void` = no payload). */
42
+ export interface View<Name extends string, Params extends object = {}, Events extends object = {}> {
43
+ readonly [channel]?: { kind: "view", name: Name, params: Params, events: Events }
44
+ }
45
+
46
+ /** Marks a contract interface as a headless Service channel — see {@link View}. */
47
+ export interface Service<Name extends string, Params extends object = {}, Events extends object = {}> {
48
+ readonly [channel]?: { kind: "service", name: Name, params: Params, events: Events }
49
+ }
50
+
51
+ // ---- the wrapper's bases ---------------------------------------------------------------------------
52
+
53
+ /** A listener of the event `K`: no argument for a `void` payload. */
54
+ export type Listener<Events, K extends keyof Events> = Events[K] extends void ? () => void : (payload: Events[K]) => void
55
+
56
+ /** A method the host's half is too old for (`@since`): refused HERE, by what it needs, before the
57
+ * half is asked. `version` = the half's contract version (0 = the host has none: the channel's own
58
+ * "not supported" speaks then). */
59
+ const tooOld = (iface: string, name: string, method: string, since: number, version: number): Error | null =>
60
+ since > 1 && version > 0 && version < since
61
+ ? new Error(`${iface}.${method} needs the "${name}" plugin at contract version ${since} — this host's is ${version}`)
62
+ : null
63
+
64
+ /** An optional argument the caller left out is ABSENT on the wire, not a null in its place. */
65
+ const present = (args: any[]): any[] => {
66
+ let n = args.length
67
+ while (n > 0 && args[n - 1] === undefined) n--
68
+ return n === args.length ? args : args.slice(0, n)
69
+ }
70
+
71
+ /** What an event's payload is read with when it is not on the wire what it is for the app (it
72
+ * holds a File): by the event's name. */
73
+ export type EventReaders = { [event: string]: (wire: any) => any }
74
+
75
+ /** The listeners of the events that have a reader: the app's callback → the one that reads first,
76
+ * so `off` finds what `on` registered. */
77
+ class Readers {
78
+ private readonly wrapped = new Map<string, Map<Function, (data?: any) => void>>()
79
+ private readonly readers: EventReaders | undefined
80
+ constructor(readers: EventReaders | undefined) { this.readers = readers }
81
+
82
+ on(event: string, callback: (data?: any) => void): (data?: any) => void {
83
+ const read = this.readers?.[event]
84
+ if (!read) return callback
85
+ let byCallback = this.wrapped.get(event)
86
+ if (!byCallback) this.wrapped.set(event, byCallback = new Map())
87
+ let listener = byCallback.get(callback)
88
+ if (!listener) byCallback.set(callback, listener = (wire?: any) => callback(read(wire)))
89
+ return listener
90
+ }
91
+ off(event: string, callback: (data?: any) => void): (data?: any) => void {
92
+ const byCallback = this.wrapped.get(event)
93
+ const listener = byCallback?.get(callback)
94
+ if (!listener) return callback
95
+ byCallback!.delete(callback)
96
+ return listener
97
+ }
98
+ }
99
+
100
+ /** The element of a view plugin: a `NativeView` whose events are the contract's. Generated wrappers
101
+ * extend it (`<Name>Base`); a hand-written one extends that, or this when its API is not the wire's. */
102
+ export class ViewChannel<Events extends object = {}> extends NativeViewElement {
103
+ /** @internal The contract interface's name, for messages. */
104
+ readonly _iface: string
105
+ private readonly _readers: Readers
106
+
107
+ constructor(iface: string, name: string, params: object, readers?: EventReaders) {
108
+ super(name, params)
109
+ this._iface = iface
110
+ this._readers = new Readers(readers)
111
+ }
112
+
113
+ /** Whether this host registered a view under `name` — at contract version `since` or later, when
114
+ * one is given (a method's `@since`). */
115
+ static supports(name: string, since = 1): boolean { return since <= 1 ? NativeView.isSupported(name) : NativeView.version(name) >= since }
116
+
117
+ override on<K extends keyof Events & string>(event: K, callback: Listener<Events, K>): this {
118
+ return super.on(event, this._readers.on(event, callback as (data?: any) => void))
119
+ }
120
+ override off<K extends keyof Events & string>(event: K, callback: Listener<Events, K>): this {
121
+ return super.off(event, this._readers.off(event, callback as (data?: any) => void))
122
+ }
123
+
124
+ /** @internal One call over the wire — what the generated functions are made of. */
125
+ _call(method: string, args: any[], since = 1): Promise<any> {
126
+ const old = since > 1 ? tooOld(this._iface, this.viewName, method, since, NativeView.version(this.viewName)) : null
127
+ return old ? Promise.reject(old) : this._channelSend(method, present(args))
128
+ }
129
+ }
130
+
131
+ /** The client of a service plugin: a session that opens on the first call, with the contract's
132
+ * events. The service twin of {@link ViewChannel}. */
133
+ export class ServiceChannel<Events extends object = {}> extends ServiceClient {
134
+ /** @internal */
135
+ readonly _iface: string
136
+ private readonly _readers: Readers
137
+
138
+ constructor(iface: string, name: string, params: object = {}, readers?: EventReaders) {
139
+ super(name, params)
140
+ this._iface = iface
141
+ this._readers = new Readers(readers)
142
+ }
143
+
144
+ /** Whether this host registered a service under `name` — at contract version `since` or later,
145
+ * when one is given (a method's `@since`). */
146
+ static supports(name: string, since = 1): boolean { return since <= 1 ? ServiceClient.isSupported(name) : ServiceClient.version(name) >= since }
147
+
148
+ override on<K extends keyof Events & string>(event: K, callback: Listener<Events, K>): this {
149
+ return super.on(event, this._readers.on(event, callback as (data?: any) => void))
150
+ }
151
+ override off<K extends keyof Events & string>(event: K, callback: Listener<Events, K>): this {
152
+ return super.off(event, this._readers.off(event, callback as (data?: any) => void))
153
+ }
154
+
155
+ /** @internal */
156
+ _call(method: string, args: any[], since = 1): Promise<any> {
157
+ const old = since > 1 ? tooOld(this._iface, this.serviceName, method, since, ServiceClient.version(this.serviceName)) : null
158
+ return old ? Promise.reject(old) : this._channelSend(method, present(args))
159
+ }
160
+ }
161
+
162
+ // ---- what a wrapper reads off the wire -------------------------------------------------------------
163
+
164
+ /** @internal A `File` of the contract as it crosses: the host buffer's id, the name, the size. */
165
+ export const _toFile = (wire: { systemId: number, name: string, size: number }): File =>
166
+ new HostFile(wire.systemId, wire.name, wire.size)
167
+
168
+ /** @internal …and the other way: a File the app hands a half. The bytes stay where they are — the
169
+ * half reads them out of the host's buffer table. */
170
+ export const _fromFile = (file: File): { systemId: number, name: string, size: number } =>
171
+ ({ systemId: file._h.id, name: file.name, size: file.size })
172
+
173
+ // ---- for the hand-written part ---------------------------------------------------------------------
174
+
175
+ /** The text of a host buffer — what a bundled `asset()` is in a shell compile (`"id:N"`): the host
176
+ * already holds the bytes. Empty when there is no such buffer. */
177
+ export const bufferText = (systemId: number): string => _creatorFetch.toText(systemId)
178
+
179
+ /** Prepare-then-present for a view that needs the camera: fail before any prompt on a host without
180
+ * the view, ask for the permission while the current destination stays visible, give up when
181
+ * another navigation superseded this one. Resolves when the view may open. */
182
+ export const prepareCamera = async (view: ViewChannel<any>): Promise<void> => {
183
+ if (!NativeView.isSupported(view.viewName)) {
184
+ throw new Error(`${view._iface} isn't available on this host — it needs a registered "${view.viewName}" view.`)
185
+ }
186
+ const epoch = _navEpoch()
187
+ await _requestCameraPermission()
188
+ if (_navEpoch() !== epoch) {
189
+ throw new Error(`${view._iface}.open() superseded: another destination was opened while preparing`)
190
+ }
191
+ }
@@ -0,0 +1,27 @@
1
+ // GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/camera/contract.d.ts — do not edit here.
2
+ // Change it in the plugin repo and run `bun run vendor:plugins`; `bun run check:plugins` fails when this copy is stale.
3
+ // camera — a live camera preview with still capture.
4
+ // The contract of the WIRE: what the app-facing `CameraView` (sdk/) and the halves (ios/, android/,
5
+ // web/) agree on. The generator reads this file; nothing else describes the channel.
6
+
7
+ import type { File, View } from "../../../plugin"
8
+
9
+ export type Facing = "front" | "back"
10
+
11
+ export interface CameraParams {
12
+ /** Default "back". */
13
+ facingMode?: Facing
14
+ }
15
+
16
+ export interface CameraEvents {
17
+ /** The preview could not start or stopped (no camera, the stream was refused). */
18
+ error: { message: string }
19
+ }
20
+
21
+ export interface CameraView extends View<"camera", CameraParams, CameraEvents> {
22
+ /** Capture a still frame as a JPEG.
23
+ * @rejects failed */
24
+ takePhoto(): Promise<File>
25
+ /** Switch the camera while the preview is live. */
26
+ setFacingMode(mode: Facing): Promise<void>
27
+ }
@@ -0,0 +1,46 @@
1
+ // GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/camera/sdk/camera.gen.ts — do not edit here.
2
+ // Change it in the plugin repo and run `bun run vendor:plugins`; `bun run check:plugins` fails when this copy is stale.
3
+ // GENERATED by `lecodes plugin gen` from contract.d.ts — do not edit.
4
+ // camera: the view "camera", contract version 1.
5
+
6
+ import { ViewChannel, _toFile, type File } from "../../../../plugin"
7
+ import type { CameraEvents, CameraParams, Facing } from "../contract"
8
+
9
+ /** What the halves register under. */
10
+ export const NAME = "camera"
11
+ /** The contract's version: the highest `@since` of its members. */
12
+ export const VERSION = 1
13
+
14
+ // ---- the wire, call by call ----------------------------------------------------------------------
15
+
16
+ /** Capture a still frame as a JPEG.
17
+ * Rejects with "failed". */
18
+ export const takePhoto = (view: ViewChannel<CameraEvents>): Promise<File> =>
19
+ view._call("takePhoto", []).then((w: any) => _toFile(w))
20
+
21
+ /** Switch the camera while the preview is live. */
22
+ export const setFacingMode = (view: ViewChannel<CameraEvents>, mode: Facing): Promise<void> =>
23
+ view._call("setFacingMode", [mode])
24
+
25
+ // ---- what the host's half has --------------------------------------------------------------------
26
+
27
+ /** A method of the contract. */
28
+ export type Method = "takePhoto" | "setFacingMode"
29
+
30
+ /** Whether this host has the view "camera" — and, given a method, a half new enough for it
31
+ * (a method added after the first version of the contract is tagged `@since`). */
32
+ export const supports = (method?: Method): boolean =>
33
+ ViewChannel.supports(NAME) // every method is of version 1
34
+
35
+ // ---- the element ----------------------------------------------------------------------------------
36
+
37
+ /** The view "camera" with the wire's methods as its own. */
38
+ export class CameraViewBase extends ViewChannel<CameraEvents> {
39
+ constructor(params: CameraParams = {}) { super("CameraView", NAME, params) }
40
+
41
+ /** Capture a still frame as a JPEG.
42
+ * Rejects with "failed". */
43
+ takePhoto(): Promise<File> { return takePhoto(this) }
44
+ /** Switch the camera while the preview is live. */
45
+ setFacingMode(mode: Facing): Promise<void> { return setFacingMode(this, mode) }
46
+ }
@@ -0,0 +1,57 @@
1
+ // GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/camera/sdk/camera.ts — do not edit here.
2
+ // Change it in the plugin repo and run `bun run vendor:plugins`; `bun run check:plugins` fails when this copy is stale.
3
+ // CameraView — the device camera (a live preview + still capture) as a view the app opens
4
+ // fullscreen (`await camera.open()`), pushes (`Router.push(camera)`) or embeds among a screen's
5
+ // children. `takePhoto()` returns a `File`: a texture source, a `FormData` value, a share.
6
+ //
7
+ // Hand-written over the generated wire for ONE reason: the permission is asked BEFORE the view is
8
+ // presented, while the current destination stays visible.
9
+
10
+ import { prepareCamera, type File, type NativeView, type PresentOptions } from "../../../../plugin"
11
+ import { CameraViewBase, NAME } from "./camera.gen"
12
+ import type { Facing } from "../contract"
13
+
14
+ export type CameraFacing = Facing
15
+
16
+ export interface CameraView extends NativeView {
17
+ /** Request the camera permission, then present the preview. The current destination stays
18
+ * visible until the camera is ready; rejects if the permission is denied, the host has no
19
+ * camera, or another navigation superseded this one. */
20
+ open(options?: PresentOptions): Promise<void>
21
+ /** Capture a still frame as an image `File` (host-encoded). */
22
+ takePhoto(): Promise<File>
23
+ /** Switch between the front and the back camera while the preview is live. */
24
+ setFacingMode(facingMode: CameraFacing): Promise<void>
25
+ }
26
+
27
+ class CameraViewElement extends CameraViewBase {
28
+ constructor(options: { facingMode?: CameraFacing } = {}) {
29
+ super({ facingMode: options.facingMode ?? "back" })
30
+ }
31
+
32
+ /** @internal prepare-then-present (see Presentable._prepare). */
33
+ _prepare(): Promise<void> { return prepareCamera(this) }
34
+
35
+ override async open(options?: PresentOptions): Promise<void> {
36
+ await this._prepare()
37
+ super.open(options)
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Create a camera view (default: the back camera). `CameraView.isSupported` reports whether this
43
+ * host has the capability — check it before offering the feature.
44
+ */
45
+ // PURE IIFE so an app that never uses the camera tree-shakes the whole plugin away.
46
+ export const CameraView: {
47
+ (options?: { facingMode?: CameraFacing }): CameraView
48
+ /** Whether this host registered a "camera" view. */
49
+ readonly isSupported: boolean
50
+ } = /*#__PURE__*/ (() => {
51
+ const factory = (options?: { facingMode?: CameraFacing }): CameraView =>
52
+ new CameraViewElement(options) as unknown as CameraView
53
+ Object.defineProperty(factory, "isSupported", {
54
+ get: (): boolean => CameraViewBase.supports(NAME),
55
+ })
56
+ return factory as any
57
+ })()
@@ -0,0 +1,50 @@
1
+ // GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/geolocation/contract.d.ts — do not edit here.
2
+ // Change it in the plugin repo and run `bun run vendor:plugins`; `bun run check:plugins` fails when this copy is stale.
3
+ // geolocation — the device position.
4
+ // The wire has ONE watch per session (startWatch / stopWatch + the `position` event); the
5
+ // app-facing `Geolocation.watch(callback)` fans it out to its subscribers in sdk/.
6
+ //
7
+ // A session opens without a prompt. The OS permission prompt belongs to the first call that needs
8
+ // the position.
9
+
10
+ import type { Service, f64 } from "../../../plugin"
11
+
12
+ export interface GeoOptions {
13
+ /** Prefer the precise (GPS) provider — slower first fix, more battery. Default false. */
14
+ highAccuracy?: boolean
15
+ /** getCurrent only: reject with "timeout" when no fix arrives in this many ms. */
16
+ timeout?: f64
17
+ }
18
+
19
+ export interface GeoPosition {
20
+ latitude: f64
21
+ longitude: f64
22
+ /** Horizontal accuracy radius, meters. */
23
+ accuracy: f64
24
+ /** Meters above sea level; null when the provider does not know. */
25
+ altitude: f64 | null
26
+ /** Direction of travel, degrees clockwise from north; null when stationary or unknown. */
27
+ heading: f64 | null
28
+ /** Ground speed, m/s; null when unknown. */
29
+ speed: f64 | null
30
+ /** When the fix was taken, ms since the epoch. */
31
+ timestamp: f64
32
+ }
33
+
34
+ export interface GeolocationParams {}
35
+
36
+ export interface GeolocationEvents {
37
+ /** A fix of the live watch. */
38
+ position: GeoPosition
39
+ }
40
+
41
+ export interface Geolocation extends Service<"geolocation", GeolocationParams, GeolocationEvents> {
42
+ /** One fix.
43
+ * @rejects denied unavailable timeout */
44
+ getCurrent(options?: GeoOptions): Promise<GeoPosition>
45
+ /** Start the session's watch; resolves once it is live (permission granted, provider started).
46
+ * A second call while watching resolves at once and keeps the first call's options.
47
+ * @rejects denied unavailable */
48
+ startWatch(options?: GeoOptions): Promise<void>
49
+ stopWatch(): Promise<void>
50
+ }
@@ -0,0 +1,54 @@
1
+ // GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/geolocation/sdk/geolocation.gen.ts — do not edit here.
2
+ // Change it in the plugin repo and run `bun run vendor:plugins`; `bun run check:plugins` fails when this copy is stale.
3
+ // GENERATED by `lecodes plugin gen` from contract.d.ts — do not edit.
4
+ // geolocation: the service "geolocation", contract version 1.
5
+
6
+ import { ServiceChannel } from "../../../../plugin"
7
+ import type { GeoOptions, GeoPosition, GeolocationEvents, GeolocationParams } from "../contract"
8
+
9
+ /** What the halves register under. */
10
+ export const NAME = "geolocation"
11
+ /** The contract's version: the highest `@since` of its members. */
12
+ export const VERSION = 1
13
+
14
+ // ---- the wire, call by call ----------------------------------------------------------------------
15
+
16
+ /** One fix.
17
+ * Rejects with "denied" / "unavailable" / "timeout". */
18
+ export const getCurrent = (service: ServiceChannel<GeolocationEvents>, options?: GeoOptions): Promise<GeoPosition> =>
19
+ service._call("getCurrent", [options])
20
+
21
+ /** Start the session's watch; resolves once it is live (permission granted, provider started).
22
+ * A second call while watching resolves at once and keeps the first call's options.
23
+ * Rejects with "denied" / "unavailable". */
24
+ export const startWatch = (service: ServiceChannel<GeolocationEvents>, options?: GeoOptions): Promise<void> =>
25
+ service._call("startWatch", [options])
26
+
27
+ export const stopWatch = (service: ServiceChannel<GeolocationEvents>): Promise<void> =>
28
+ service._call("stopWatch", [])
29
+
30
+ // ---- what the host's half has --------------------------------------------------------------------
31
+
32
+ /** A method of the contract. */
33
+ export type Method = "getCurrent" | "startWatch" | "stopWatch"
34
+
35
+ /** Whether this host has the service "geolocation" — and, given a method, a half new enough for it
36
+ * (a method added after the first version of the contract is tagged `@since`). */
37
+ export const supports = (method?: Method): boolean =>
38
+ ServiceChannel.supports(NAME) // every method is of version 1
39
+
40
+ // ---- the client -----------------------------------------------------------------------------------
41
+
42
+ /** The service "geolocation" with the wire's methods as its own. */
43
+ export class GeolocationBase extends ServiceChannel<GeolocationEvents> {
44
+ constructor(params: GeolocationParams = {}) { super("Geolocation", NAME, params) }
45
+
46
+ /** One fix.
47
+ * Rejects with "denied" / "unavailable" / "timeout". */
48
+ getCurrent(options?: GeoOptions): Promise<GeoPosition> { return getCurrent(this, options) }
49
+ /** Start the session's watch; resolves once it is live (permission granted, provider started).
50
+ * A second call while watching resolves at once and keeps the first call's options.
51
+ * Rejects with "denied" / "unavailable". */
52
+ startWatch(options?: GeoOptions): Promise<void> { return startWatch(this, options) }
53
+ stopWatch(): Promise<void> { return stopWatch(this) }
54
+ }
@@ -1,39 +1,18 @@
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()`.
1
+ // GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/geolocation/sdk/geolocation.ts — do not edit here.
2
+ // Change it in the plugin repo and run `bun run vendor:plugins`; `bun run check:plugins` fails when this copy is stale.
3
+ // Geolocation — the device position: `getCurrent()` and `watch()`.
5
4
  //
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.
5
+ // Hand-written over the generated wire because the public API is not the wire's: the wire has ONE
6
+ // watch per session (startWatch / stopWatch + the `position` event), the app gets `watch(callback)`
7
+ // — any number of subscribers on that one watch, each with its own `stop()`.
10
8
  //
11
- // Service contract ("geolocation"): calls `getCurrent(options) → GeoPosition`,
12
- // `startWatch(options)` (resolves once watching), `stopWatch()`; event `position` → GeoPosition.
9
+ // The OS permission prompt happens on the first call that needs it (host-side); a denial is a
10
+ // rejection. Both methods reject on a host without the service BEFORE any permission path.
13
11
 
14
- import { ServiceClient } from "../runtime/service"
12
+ import { GeolocationBase, NAME } from "./geolocation.gen"
13
+ import type { GeoOptions, GeoPosition } from "../contract"
15
14
 
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
- }
15
+ export type { GeoOptions, GeoPosition }
37
16
 
38
17
  /** A live `Geolocation.watch()` subscription — `stop()` it when the screen goes away. */
39
18
  export interface GeoWatch {
@@ -44,7 +23,7 @@ export interface GeoWatch {
44
23
  * The device position. `Geolocation.isSupported` reports whether this host registered the
45
24
  * service — check it before offering location features.
46
25
  */
47
- // PURE IIFE so an app that never uses location tree-shakes the whole plugin away (see NativeView).
26
+ // PURE IIFE so an app that never uses location tree-shakes the whole plugin away.
48
27
  export const Geolocation: {
49
28
  /** Whether this host registered a "geolocation" service. */
50
29
  readonly isSupported: boolean
@@ -56,18 +35,18 @@ export const Geolocation: {
56
35
  * subscriber; the options of the watch that starts it win. Always `stop()` when done. */
57
36
  watch(callback: (position: GeoPosition) => void, options?: GeoOptions): Promise<GeoWatch>
58
37
  } = /*#__PURE__*/ (() => {
59
- let client: ServiceClient | null = null
60
- const service = (): ServiceClient => client ??= new ServiceClient("geolocation")
38
+ let client: GeolocationBase | null = null
39
+ const service = (): GeolocationBase => client ??= new GeolocationBase()
61
40
 
62
- // Watch state: one host watch fans out to N subscribers (SDK-side), the last stop() releases it.
41
+ // Watch state: one host watch fans out to N subscribers, the last stop() releases it.
63
42
  const watchers: ((position: GeoPosition) => void)[] = []
64
43
  let positionHooked = false
65
44
  let startPromise: Promise<void> | null = null
66
45
 
67
46
  const assertSupported = (): void => {
68
47
  // 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")) {
48
+ // would be worse than the error.
49
+ if (!GeolocationBase.supports(NAME)) {
71
50
  throw new Error('Geolocation isn\'t available on this host — it needs a registered "geolocation" service.')
72
51
  }
73
52
  }
@@ -77,26 +56,26 @@ export const Geolocation: {
77
56
  if (i >= 0) watchers.splice(i, 1)
78
57
  if (watchers.length === 0 && startPromise !== null) {
79
58
  startPromise = null
80
- service().call("stopWatch").catch(() => {})
59
+ service().stopWatch().catch(() => {})
81
60
  }
82
61
  }
83
62
 
84
63
  const geolocation = {
85
64
  async getCurrent(options?: GeoOptions): Promise<GeoPosition> {
86
65
  assertSupported()
87
- return await service().call("getCurrent", options ?? {})
66
+ return await service().getCurrent(options ?? {})
88
67
  },
89
68
 
90
69
  async watch(callback: (position: GeoPosition) => void, options?: GeoOptions): Promise<GeoWatch> {
91
70
  assertSupported()
92
71
  if (!positionHooked) {
93
72
  positionHooked = true
94
- service().on("position", (position?: GeoPosition) => {
73
+ service().on("position", position => {
95
74
  if (position) for (const cb of [...watchers]) cb(position)
96
75
  })
97
76
  }
98
77
  watchers.push(callback)
99
- startPromise ??= service().call("startWatch", options ?? {})
78
+ startPromise ??= service().startWatch(options ?? {})
100
79
  try {
101
80
  await startPromise
102
81
  } catch (e) {
@@ -117,7 +96,7 @@ export const Geolocation: {
117
96
  },
118
97
  }
119
98
  Object.defineProperty(geolocation, "isSupported", {
120
- get: (): boolean => ServiceClient.isSupported("geolocation"),
99
+ get: (): boolean => GeolocationBase.supports(NAME),
121
100
  })
122
101
  return geolocation as any
123
102
  })()