lecodes-cli 0.19.0 → 0.20.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 (270) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +1069 -396
  3. package/package.json +14 -13
  4. package/runtime/scene-harness.json +1 -1
  5. package/runtime/sdk-types.json +1 -1
  6. package/runtime/web/assets/Roboto-Variable-DHm9jpd-.woff2 +0 -0
  7. package/runtime/web/assets/__vite-browser-external-BIHI7g3E.js +1 -0
  8. package/runtime/web/assets/basis-C64VHDVD.js +1 -0
  9. package/runtime/web/assets/basis_transcoder-VXdx5NbI.wasm +0 -0
  10. package/runtime/web/assets/createViewerLite-BvHobVhp.js +852 -0
  11. package/runtime/web/assets/draco-BiISTFcR.js +118 -0
  12. package/runtime/web/assets/draco_decoder-DsQ12WqX.wasm +0 -0
  13. package/runtime/web/assets/index-CfNCCBjN.js +2 -0
  14. package/runtime/web/assets/mapViewImpl-B2JcES8l.js +810 -0
  15. package/runtime/web/assets/maplibre-gl-worker-CJfwIrte.js +8 -0
  16. package/runtime/web/assets/neutral_ibl128-D-lzdkLN.ktx +0 -0
  17. package/runtime/web/assets/worker-Caf-yYEI.js +2 -0
  18. package/runtime/web/embed.html +32 -0
  19. package/runtime/web/embed.js +171 -0
  20. package/src/api.ts +302 -0
  21. package/src/browserAuth.ts +87 -0
  22. package/src/cmgenTool.ts +104 -0
  23. package/src/commands/app.ts +896 -0
  24. package/src/commands/appAndroid.ts +603 -0
  25. package/src/commands/appDesktop.ts +339 -0
  26. package/src/commands/appDesktopMac.ts +470 -0
  27. package/src/commands/appIcon.ts +187 -0
  28. package/src/commands/appShared.ts +542 -0
  29. package/src/commands/appTemplates.ts +907 -0
  30. package/src/commands/appTemplatesAndroid.ts +599 -0
  31. package/src/commands/appTemplatesGradlew.ts +9 -0
  32. package/src/commands/assets.ts +28 -0
  33. package/src/commands/clone.ts +60 -0
  34. package/src/commands/compile.ts +253 -0
  35. package/src/commands/create.ts +59 -0
  36. package/src/commands/design.ts +863 -0
  37. package/src/commands/designTemplates.ts +11 -0
  38. package/src/commands/desktop.ts +127 -0
  39. package/src/commands/dev.ts +236 -0
  40. package/src/commands/diff.ts +59 -0
  41. package/src/commands/init.ts +191 -0
  42. package/src/commands/install.ts +198 -0
  43. package/src/commands/lightmap.ts +290 -0
  44. package/src/commands/link.ts +147 -0
  45. package/src/commands/login.ts +24 -0
  46. package/src/commands/navmesh.ts +225 -0
  47. package/src/commands/pn.ts +305 -0
  48. package/src/commands/projectTemplates.ts +130 -0
  49. package/src/commands/pull.ts +109 -0
  50. package/src/commands/push.ts +173 -0
  51. package/src/commands/render.ts +416 -0
  52. package/src/commands/scene.ts +148 -0
  53. package/src/commands/shaders.ts +157 -0
  54. package/src/commands/status.ts +29 -0
  55. package/src/commands/test.ts +371 -0
  56. package/src/commands/thumbs.ts +178 -0
  57. package/src/commands/types.ts +87 -0
  58. package/src/commands/update.ts +190 -0
  59. package/src/compile/assetIcons.ts +214 -0
  60. package/src/compile/collect.ts +46 -0
  61. package/src/compile/collectLocal.ts +37 -0
  62. package/src/compile/designCompile.ts +104 -0
  63. package/src/compile/fonts.ts +156 -0
  64. package/src/compile/headlessBundle.ts +129 -0
  65. package/src/compile/nativeStack.ts +21 -0
  66. package/src/compile/projectCompile.ts +109 -0
  67. package/src/compile/sceneCompile.ts +113 -0
  68. package/src/compile/screenEntry.ts +127 -0
  69. package/src/compile/shaders.ts +245 -0
  70. package/src/config.ts +42 -0
  71. package/src/designMeta.ts +35 -0
  72. package/src/desktopRenderer.ts +545 -0
  73. package/src/desktopScript.ts +276 -0
  74. package/src/dev/clientTemplates.ts +160 -0
  75. package/src/dev/devServer.ts +333 -0
  76. package/src/dev/webRunner.ts +22 -0
  77. package/src/dev/wsServer.ts +144 -0
  78. package/src/distRoot.ts +20 -0
  79. package/src/ignore.ts +163 -0
  80. package/src/index.ts +494 -0
  81. package/src/lecodes-3d-editor.d.ts +41 -0
  82. package/src/lecodes-assets.d.ts +7 -0
  83. package/src/lecodes-design.d.ts +191 -0
  84. package/src/lecodes-renderer.d.ts +131 -0
  85. package/src/localFiles.ts +144 -0
  86. package/src/manifest.ts +46 -0
  87. package/src/matcTool.ts +137 -0
  88. package/src/peers.ts +40 -0
  89. package/src/project.ts +61 -0
  90. package/src/projectEnv.ts +94 -0
  91. package/src/qrcode-terminal.d.ts +9 -0
  92. package/src/releases.ts +125 -0
  93. package/src/serverDiff.ts +78 -0
  94. package/src/textDiff.ts +103 -0
  95. package/src/types.ts +0 -0
  96. package/src/util.ts +146 -0
  97. package/runtime/sdk/animate/animate.ts +0 -238
  98. package/runtime/sdk/animate/bezier.ts +0 -138
  99. package/runtime/sdk/animate/easings.ts +0 -126
  100. package/runtime/sdk/canvas/Canvas.ts +0 -305
  101. package/runtime/sdk/compile/aspectMacro.ts +0 -86
  102. package/runtime/sdk/compile/assetIconMacro.ts +0 -384
  103. package/runtime/sdk/compile/assetMacro.ts +0 -146
  104. package/runtime/sdk/compile/assetName.ts +0 -50
  105. package/runtime/sdk/compile/bundler.ts +0 -287
  106. package/runtime/sdk/compile/compileProject.ts +0 -129
  107. package/runtime/sdk/compile/detectEntry.ts +0 -128
  108. package/runtime/sdk/compile/fontMacro.ts +0 -459
  109. package/runtime/sdk/compile/fontRegistry.ts +0 -78
  110. package/runtime/sdk/compile/header.ts +0 -67
  111. package/runtime/sdk/compile/index.ts +0 -100
  112. package/runtime/sdk/compile/libraryImports.ts +0 -52
  113. package/runtime/sdk/compile/liteMaterial.ts +0 -247
  114. package/runtime/sdk/compile/sceneEditor.ts +0 -88
  115. package/runtime/sdk/compile/serverSplit.ts +0 -233
  116. package/runtime/sdk/compile/serverTypes.ts +0 -227
  117. package/runtime/sdk/compile/sfnt.ts +0 -98
  118. package/runtime/sdk/compile/shaderSchema.ts +0 -202
  119. package/runtime/sdk/compile/shaderTargets.ts +0 -81
  120. package/runtime/sdk/compile/sourcemap.ts +0 -25
  121. package/runtime/sdk/core/Aspect.ts +0 -568
  122. package/runtime/sdk/core/InspectorUI.ts +0 -212
  123. package/runtime/sdk/core/StateMachine.ts +0 -308
  124. package/runtime/sdk/core/color.ts +0 -66
  125. package/runtime/sdk/core/compWrite.ts +0 -42
  126. package/runtime/sdk/core/events.ts +0 -38
  127. package/runtime/sdk/core/fields.ts +0 -120
  128. package/runtime/sdk/core/registry.ts +0 -23
  129. package/runtime/sdk/core/signals.ts +0 -277
  130. package/runtime/sdk/core/time.ts +0 -84
  131. package/runtime/sdk/g2/Camera2D.ts +0 -40
  132. package/runtime/sdk/g2/CharacterController2D.ts +0 -276
  133. package/runtime/sdk/g2/Node2D.ts +0 -267
  134. package/runtime/sdk/g2/OneWay2D.ts +0 -66
  135. package/runtime/sdk/g2/Physics2D.ts +0 -346
  136. package/runtime/sdk/g2/Scene2D.ts +0 -209
  137. package/runtime/sdk/g2/Shape2D.ts +0 -259
  138. package/runtime/sdk/g2/Sprite.ts +0 -89
  139. package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
  140. package/runtime/sdk/g2/SpriteSheet.ts +0 -166
  141. package/runtime/sdk/g2/Texture2D.ts +0 -47
  142. package/runtime/sdk/g2/Tilemap.ts +0 -41
  143. package/runtime/sdk/g2/Tileset.ts +0 -71
  144. package/runtime/sdk/g2/Trigger2D.ts +0 -77
  145. package/runtime/sdk/g2/autotile.ts +0 -433
  146. package/runtime/sdk/g2/cells.ts +0 -91
  147. package/runtime/sdk/g2/defineScene2d.ts +0 -381
  148. package/runtime/sdk/g2/groups2d.ts +0 -106
  149. package/runtime/sdk/g2/loop.ts +0 -50
  150. package/runtime/sdk/g2/scenarios2d.ts +0 -69
  151. package/runtime/sdk/g2/touch.ts +0 -83
  152. package/runtime/sdk/gl/Camera.ts +0 -160
  153. package/runtime/sdk/gl/CameraPlace.ts +0 -52
  154. package/runtime/sdk/gl/CharacterController.ts +0 -238
  155. package/runtime/sdk/gl/Gearbox.ts +0 -212
  156. package/runtime/sdk/gl/Geometry.ts +0 -279
  157. package/runtime/sdk/gl/IK.ts +0 -193
  158. package/runtime/sdk/gl/InstancedMesh.ts +0 -132
  159. package/runtime/sdk/gl/Light.ts +0 -107
  160. package/runtime/sdk/gl/Lightmap.ts +0 -256
  161. package/runtime/sdk/gl/Material.ts +0 -260
  162. package/runtime/sdk/gl/Mesh.ts +0 -83
  163. package/runtime/sdk/gl/Model.ts +0 -124
  164. package/runtime/sdk/gl/NavAgent.ts +0 -337
  165. package/runtime/sdk/gl/NavMesh.ts +0 -397
  166. package/runtime/sdk/gl/Node.ts +0 -350
  167. package/runtime/sdk/gl/Noise.ts +0 -30
  168. package/runtime/sdk/gl/Particles.ts +0 -676
  169. package/runtime/sdk/gl/Physics.ts +0 -222
  170. package/runtime/sdk/gl/Plane.ts +0 -53
  171. package/runtime/sdk/gl/Ragdoll.ts +0 -270
  172. package/runtime/sdk/gl/Ray.ts +0 -16
  173. package/runtime/sdk/gl/Scene.ts +0 -486
  174. package/runtime/sdk/gl/Shape.ts +0 -409
  175. package/runtime/sdk/gl/Terrain.ts +0 -1092
  176. package/runtime/sdk/gl/Texture.ts +0 -63
  177. package/runtime/sdk/gl/Trigger.ts +0 -45
  178. package/runtime/sdk/gl/Vehicle.ts +0 -473
  179. package/runtime/sdk/gl/Wheel.ts +0 -240
  180. package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
  181. package/runtime/sdk/gl/animation/Animator.ts +0 -120
  182. package/runtime/sdk/gl/animation/Layer.ts +0 -31
  183. package/runtime/sdk/gl/animation/Loop.ts +0 -30
  184. package/runtime/sdk/gl/animation/Playback.ts +0 -44
  185. package/runtime/sdk/gl/animation/core.ts +0 -336
  186. package/runtime/sdk/gl/controls.ts +0 -95
  187. package/runtime/sdk/gl/physicsEvents.ts +0 -20
  188. package/runtime/sdk/gl/scenarios.ts +0 -291
  189. package/runtime/sdk/gl/state.ts +0 -6
  190. package/runtime/sdk/gl/terrainMesh.ts +0 -219
  191. package/runtime/sdk/gl/touch.ts +0 -68
  192. package/runtime/sdk/inject.ts +0 -197
  193. package/runtime/sdk/math/Mathf.ts +0 -118
  194. package/runtime/sdk/math/mat4.ts +0 -278
  195. package/runtime/sdk/math/quat.ts +0 -232
  196. package/runtime/sdk/math/vec.ts +0 -255
  197. package/runtime/sdk/net/codec.ts +0 -119
  198. package/runtime/sdk/net/core.ts +0 -384
  199. package/runtime/sdk/net/index.ts +0 -181
  200. package/runtime/sdk/net/replication.ts +0 -622
  201. package/runtime/sdk/plugins/camera.ts +0 -81
  202. package/runtime/sdk/plugins/geolocation.ts +0 -123
  203. package/runtime/sdk/plugins/oauth.ts +0 -61
  204. package/runtime/sdk/plugins/permission.ts +0 -7
  205. package/runtime/sdk/plugins/push.ts +0 -132
  206. package/runtime/sdk/plugins/qr.ts +0 -73
  207. package/runtime/sdk/plugins/service.ts +0 -47
  208. package/runtime/sdk/runtime/app.ts +0 -101
  209. package/runtime/sdk/runtime/appEvents.ts +0 -54
  210. package/runtime/sdk/runtime/channel.ts +0 -61
  211. package/runtime/sdk/runtime/clipboard.ts +0 -20
  212. package/runtime/sdk/runtime/datetime.ts +0 -329
  213. package/runtime/sdk/runtime/device.ts +0 -293
  214. package/runtime/sdk/runtime/fetch.ts +0 -77
  215. package/runtime/sdk/runtime/files.ts +0 -23
  216. package/runtime/sdk/runtime/input.ts +0 -175
  217. package/runtime/sdk/runtime/media.ts +0 -111
  218. package/runtime/sdk/runtime/misc.ts +0 -16
  219. package/runtime/sdk/runtime/net.ts +0 -36
  220. package/runtime/sdk/runtime/rpc.ts +0 -218
  221. package/runtime/sdk/runtime/service.ts +0 -83
  222. package/runtime/sdk/runtime/share.ts +0 -9
  223. package/runtime/sdk/runtime/storage.ts +0 -13
  224. package/runtime/sdk/runtime/touch.ts +0 -76
  225. package/runtime/sdk/scene/defineScene.ts +0 -1353
  226. package/runtime/sdk/scene/editorPlugins.ts +0 -110
  227. package/runtime/sdk/scene/gizmos.ts +0 -148
  228. package/runtime/sdk/scene/grammar.ts +0 -120
  229. package/runtime/sdk/scene/material.ts +0 -188
  230. package/runtime/sdk/server/auth/appConfig.ts +0 -12
  231. package/runtime/sdk/server/auth/global.ts +0 -80
  232. package/runtime/sdk/server/auth/host.ts +0 -318
  233. package/runtime/sdk/server/auth/models.ts +0 -83
  234. package/runtime/sdk/server/auth/types.ts +0 -50
  235. package/runtime/sdk/server/channel.ts +0 -56
  236. package/runtime/sdk/server/context.ts +0 -36
  237. package/runtime/sdk/server/db/defineDb.ts +0 -237
  238. package/runtime/sdk/server/db/fields.ts +0 -132
  239. package/runtime/sdk/server/db/httpTransport.ts +0 -93
  240. package/runtime/sdk/server/db/index.ts +0 -7
  241. package/runtime/sdk/server/db/marci/query.ts +0 -412
  242. package/runtime/sdk/server/db/types.ts +0 -202
  243. package/runtime/sdk/server/errors.ts +0 -12
  244. package/runtime/sdk/server/host.ts +0 -74
  245. package/runtime/sdk/server/inject.ts +0 -13
  246. package/runtime/sdk/server/runtime.ts +0 -133
  247. package/runtime/sdk/server/validate.ts +0 -87
  248. package/runtime/sdk/ui/NativeView.ts +0 -142
  249. package/runtime/sdk/ui/UI.ts +0 -39
  250. package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
  251. package/runtime/sdk/ui/UIButton.ts +0 -101
  252. package/runtime/sdk/ui/UIContainer.ts +0 -60
  253. package/runtime/sdk/ui/UIImage.ts +0 -83
  254. package/runtime/sdk/ui/UIInput.ts +0 -185
  255. package/runtime/sdk/ui/UIModal.ts +0 -139
  256. package/runtime/sdk/ui/UINode.ts +0 -830
  257. package/runtime/sdk/ui/UIPager.ts +0 -362
  258. package/runtime/sdk/ui/UIPopover.ts +0 -100
  259. package/runtime/sdk/ui/UIScreen.ts +0 -123
  260. package/runtime/sdk/ui/UIScrollable.ts +0 -87
  261. package/runtime/sdk/ui/UISpacer.ts +0 -14
  262. package/runtime/sdk/ui/UITabs.ts +0 -236
  263. package/runtime/sdk/ui/UIText.ts +0 -51
  264. package/runtime/sdk/ui/UIVideo.ts +0 -88
  265. package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
  266. package/runtime/sdk/ui/UIWidget.ts +0 -127
  267. package/runtime/sdk/ui/fonts.ts +0 -13
  268. package/runtime/sdk/ui/presentable.ts +0 -117
  269. package/runtime/sdk/ui/router.ts +0 -132
  270. package/runtime/sdk/ui/theme.ts +0 -84
@@ -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
- })()
@@ -1,73 +0,0 @@
1
- // QRScanner — the host's camera QR scanner as a Presentable. A typed wrapper over the
2
- // `NativeView("qrScanner")` capability (docs/navigation-presentable-plan.md): open it fullscreen
3
- // (`await scanner.open()`), push it (`Router.push(scanner)`), or embed it among a screen's
4
- // children as a live preview node — everything a NativeView can do, plus a typed `onScan`.
5
- //
6
- // Host-OPTIONAL, like every plugin: it exists where the host registers a "qrScanner" view
7
- // (Android; web can via `_creatorUI._registerView`) — gate on `QRScanner.isSupported`. Opening
8
- // prepares first (camera permission) while the current destination stays visible — the same
9
- // prepare-then-present contract as ARScene.
10
- //
11
- // The pre-Presentable `_creatorUtils.openScanner/closeScanner` bridges are gone from the ABI
12
- // (2026-07-15): hosts keep their implementations only to serve previously compiled bundles.
13
-
14
- import { _navEpoch, type PresentOptions } from "../ui/presentable"
15
- import { NativeView, NativeViewElement } from "../ui/NativeView"
16
- import { _requestCameraPermission } from "./permission"
17
-
18
- export interface QRScanner extends NativeView {
19
- /** Fires per decoded camera frame: the decoded string, or `null` for a frame with no readable
20
- * code (expect those repeatedly while the user aims). The same code can be reported more than
21
- * once — `close()` or debounce once you have what you need. */
22
- onScan(callback: (data: string | null) => void): this
23
- /** Request camera permission, then present the scanner. The current destination (and its
24
- * loading state) stays visible until the scanner is ready; rejects if the camera is denied,
25
- * the host can't scan, or another navigation superseded this one. */
26
- open(options?: PresentOptions): Promise<void>
27
- }
28
-
29
- class QRScannerElement extends NativeViewElement {
30
- constructor() {
31
- super("qrScanner", {})
32
- }
33
-
34
- onScan(callback: (data: string | null) => void): this {
35
- return this.on("scan", (payload?: { data?: string | null }) => callback(payload?.data ?? null))
36
- }
37
-
38
- /** @internal prepare-then-present: permission before the swap (see Presentable._prepare). */
39
- async _prepare(): Promise<void> {
40
- const epoch = _navEpoch()
41
- await _requestCameraPermission()
42
- if (_navEpoch() !== epoch) {
43
- throw new Error("QRScanner.open() superseded: another destination was opened while preparing")
44
- }
45
- }
46
-
47
- override async open(options?: PresentOptions): Promise<void> {
48
- // Fail before prompting for the camera — a permission dialog for a scanner that can't exist
49
- // would be worse than the error.
50
- if (!NativeView.isSupported("qrScanner")) {
51
- throw new Error("QRScanner isn't available on this host — it needs a registered \"qrScanner\" view.")
52
- }
53
- await this._prepare()
54
- super.open(options)
55
- }
56
- }
57
-
58
- /**
59
- * Create a QR scanner view. `QRScanner.isSupported` reports whether this host can scan at all —
60
- * check it before offering the feature.
61
- */
62
- // PURE IIFE so an app that never scans tree-shakes the whole plugin away (see NativeView).
63
- export const QRScanner: {
64
- (): QRScanner
65
- /** Whether this host registered a "qrScanner" view. */
66
- readonly isSupported: boolean
67
- } = /*#__PURE__*/ (() => {
68
- const factory = (): QRScanner => new QRScannerElement() as unknown as QRScanner
69
- Object.defineProperty(factory, "isSupported", {
70
- get: (): boolean => NativeView.isSupported("qrScanner"),
71
- })
72
- return factory as any
73
- })()
@@ -1,47 +0,0 @@
1
- // Raw access to host-registered headless services — the escape hatch for services that
2
- // have no typed wrapper (yet). First-party services ship wrapped (Geolocation); app-local
3
- // native code (an `engine.registerService` in the app shell) and third-party plugins are
4
- // reachable through this global immediately, wrapper or not. Thin sugar over the internal
5
- // ServiceClient (src/runtime/service.ts) — same JSON call/event channel, same lazy session.
6
-
7
- import { ServiceClient } from "../runtime/service"
8
-
9
- export interface Service {
10
- /** Invoke a method on the service (`battery.call("level")`). Arguments and the result are
11
- * JSON-serialized; rejects when the host has no such service or the method reports an
12
- * error. The host session opens lazily on the first call. */
13
- call(method: string, ...args: any[]): Promise<any>
14
- /** Subscribe to an event the service emits while a session is open
15
- * (`battery.on("change", data => …)`). Payloads are JSON-decoded. */
16
- on(event: string, callback: (data?: any) => void): this
17
- /** Remove a previously added listener. */
18
- off(event: string, callback: (data?: any) => void): this
19
- /** Close the host session (stops whatever the service was doing). Listeners stay
20
- * registered — a later `call()` opens a fresh session that delivers to them again. */
21
- close(): void
22
- }
23
-
24
- /**
25
- * A host-registered headless service — the UI-less sibling of `NativeView`. Use it for
26
- * services without a typed wrapper: native code registered in your app shell
27
- * (`engine.registerService("app.battery") { … }`) or a third-party plugin's channel.
28
- * **Host-optional** — gate on `Service.isSupported` (the web editor registers none of them).
29
- *
30
- * ```ts
31
- * if (Service.isSupported("app.battery")) {
32
- * const battery = Service("app.battery")
33
- * battery.on("change", data => { level.text = data.level + "%" })
34
- * const { level } = await battery.call("level")
35
- * }
36
- * ```
37
- */
38
- // PURE IIFE so an app that never touches raw services tree-shakes this away.
39
- export const Service: {
40
- (name: string, params?: any): Service
41
- /** Whether this host registered a service under `name`. */
42
- isSupported(name: string): boolean
43
- } = /*#__PURE__*/ (() => {
44
- const factory = (name: string, params: any = {}): Service => new ServiceClient(name, params) as unknown as Service
45
- factory.isSupported = (name: string): boolean => ServiceClient.isSupported(name)
46
- return factory as any
47
- })()
@@ -1,101 +0,0 @@
1
- // The app's own lifecycle + how it was opened (docs/app-lifecycle-plan.md). Events are semantic —
2
- // "pause"/"resume", never Activity/Scene vocabulary — and the current values are synchronous pulls
3
- // so render-loop code can read them without subscribing.
4
-
5
- import { appEventsOn, appEventsOff, keyboardEventsOn, keyboardEventsOff } from "./appEvents"
6
-
7
- export type AppState = "active" | "background"
8
-
9
- /** `"landscape"` — either landscape direction (sensor landscape), `"portrait"` — upright only,
10
- * `"auto"` — released: the device's own rotation rules apply. */
11
- export type OrientationLock = "auto" | "portrait" | "landscape"
12
-
13
- type AppEventMap = {
14
- /** The app left the foreground (home button, tab hidden, another app on top). Delivered before
15
- * the host halts the frame loop — the last chance to persist state / pause work. */
16
- pause: () => void
17
- /** The app returned to the foreground; frames are running again. */
18
- resume: () => void
19
- /** A link arrived while the app was running (warm deep link). `app.launchUrl` is already
20
- * updated to the same value when this fires. */
21
- url: (url: string) => void
22
- /** The on-screen keyboard changed. `height` mirrors `app.keyboardHeight` (the raw overlap, see
23
- * there); `duration` is the platform's keyboard animation duration in ms (0 where none) — pass
24
- * it to `animateTo` to move a composer in sync with the keyboard. */
25
- keyboard: (height: number, duration: number) => void
26
- }
27
-
28
- export const app = {
29
- /** Current lifecycle state. `"background"` while the app is not the foreground app / the tab is
30
- * hidden. `"active"` on hosts that don't track it. */
31
- get state(): AppState {
32
- return _creatorUtils.appState?.() === "background" ? "background" : "active"
33
- },
34
- /** The URL the app was (most recently) opened with — the deep link — or `null` for a plain
35
- * launch. Warm links update it and fire the `"url"` event. */
36
- get launchUrl(): string | null {
37
- return _creatorUtils.getLaunchUrl?.() ?? null
38
- },
39
- /** On-screen keyboard height in logical px currently overlapping the app viewport, `0` when
40
- * hidden. This is the RAW overlap, independent of the focused input's `keyboardShrink` policy —
41
- * with the default shrink the layout already avoids the keyboard; the main consumer is overlay
42
- * mode (`keyboardShrink: false`), e.g. a chat composer's `pb`. `0` on hosts without an
43
- * on-screen keyboard. */
44
- get keyboardHeight(): number {
45
- return _creatorUtils.keyboardHeight?.() ?? 0
46
- },
47
- /** Lock the screen orientation — `"landscape"` for a horizontal game (the device rotates to
48
- * landscape right away and stays there whichever way it is held), `"portrait"` for upright
49
- * only, `"auto"` to release. Call it once at startup; the lock is per app run (it resets
50
- * when the app quits or restarts), so there is nothing to undo. The rotation arrives as an
51
- * ordinary resize — layouts reflow, a scene's viewport follows. Silent no-op on hosts
52
- * without a rotatable screen (desktop, headless, web). */
53
- setOrientation(mode: OrientationLock): void {
54
- _creatorUtils.setOrientation?.(mode)
55
- },
56
- /** Re-run this app's bundle in a fresh world — the platform's `location.reload()`. Pass `url`
57
- * to reboot with different launch arguments (the new run reads them via `app.launchUrl`),
58
- * `null` to reboot with a cleared one, or omit it to replay the current one. No-op on hosts
59
- * without world control (headless). */
60
- restart(url?: string | null): void {
61
- _creatorUtils.restart?.(url)
62
- },
63
- /** Leave this app. Inside the LeCodes viewer that returns to the launcher (the LeCodes home
64
- * screen); a standalone-built app leaves to the platform instead — Android backgrounds it,
65
- * iOS ignores the call (the platform forbids programmatic exit). */
66
- quit(): void {
67
- _creatorUtils.quit?.()
68
- },
69
- /** Global back-press fallback (Android back button/gesture — iOS has no system back). Fires
70
- * only when nothing else consumed the press: no open widget/modal claimed it, the current
71
- * screen has no `onBackPressed`, every pager is at its tab root and the router is at its
72
- * stack root. The place for app-wide policy — e.g. double-press-to-quit:
73
- *
74
- * ```ts
75
- * let armedAt = 0
76
- * app.onBackPressed(() => {
77
- * if (Date.now() - armedAt < 2000) { app.quit(); return }
78
- * armedAt = Date.now()
79
- * toast("Press back again to exit")
80
- * })
81
- * ```
82
- *
83
- * Replaces the previous handler; pass `null` to remove. */
84
- onBackPressed(callback: (() => void) | null): void {
85
- _creatorUI._backButtonCallback = callback ?? undefined
86
- },
87
- addEventListener<E extends keyof AppEventMap>(event: E, callback: AppEventMap[E]): void {
88
- if (event === "keyboard") {
89
- keyboardEventsOn(callback as (height: number, duration: number) => void)
90
- return
91
- }
92
- appEventsOn(event, callback as (data?: string) => void)
93
- },
94
- removeEventListener<E extends keyof AppEventMap>(event: E, callback: AppEventMap[E]): void {
95
- if (event === "keyboard") {
96
- keyboardEventsOff(callback as (height: number, duration: number) => void)
97
- return
98
- }
99
- appEventsOff(event, callback as (data?: string) => void)
100
- },
101
- }