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.
- package/README.md +5 -2
- package/dist/editor.d.ts +12 -0
- package/dist/global.d.ts +4 -8
- package/dist/host.d.ts +2 -3
- package/dist/types/animate/tween/Animation.d.ts +0 -3
- package/dist/types/animate/tween/animateValue.d.ts +4 -2
- package/dist/types/animate/tween/easing.d.ts +8 -0
- package/dist/types/animate/tween/spec.d.ts +15 -7
- package/dist/types/audio/audio.d.ts +2 -1
- package/dist/types/canvas/Canvas.d.ts +40 -108
- package/dist/types/canvas/gen/cssColor.d.ts +17 -0
- package/dist/types/canvas/gen/recorder.d.ts +118 -0
- package/dist/types/canvas/gen/spec.d.ts +144 -0
- package/dist/types/core/color.d.ts +3 -1
- package/dist/types/core/pins.d.ts +18 -0
- package/dist/types/g2/Node2D.d.ts +5 -8
- package/dist/types/g2/Scene2D.d.ts +6 -2
- package/dist/types/gl/Foliage.d.ts +30 -7
- package/dist/types/gl/Light.d.ts +11 -0
- package/dist/types/gl/Lightmap.d.ts +13 -2
- package/dist/types/gl/Material.d.ts +17 -3
- package/dist/types/gl/Model.d.ts +6 -2
- package/dist/types/gl/Node.d.ts +3 -6
- package/dist/types/gl/Particles.d.ts +15 -0
- package/dist/types/gl/Scene.d.ts +41 -13
- package/dist/types/gl/Texture.d.ts +1 -1
- package/dist/types/gl/animation/Locomotion.d.ts +59 -3
- package/dist/types/gl/terrain/Terrain.d.ts +8 -0
- package/dist/types/inject.d.ts +11 -11
- package/dist/types/inject.editor.d.ts +1 -0
- package/dist/types/net/core.d.ts +7 -0
- package/dist/types/plugin.d.ts +76 -0
- package/dist/types/plugins/gen/camera/sdk/camera.d.ts +24 -0
- package/dist/types/plugins/gen/camera/sdk/camera.gen.d.ts +25 -0
- package/dist/types/plugins/{geolocation.d.ts → gen/geolocation/sdk/geolocation.d.ts} +2 -20
- package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +31 -0
- package/dist/types/plugins/{map.d.ts → gen/map/sdk/map.d.ts} +9 -50
- package/dist/types/plugins/gen/map/sdk/map.gen.d.ts +53 -0
- package/dist/types/plugins/gen/push/sdk/push.d.ts +23 -0
- package/dist/types/plugins/gen/push/sdk/push.gen.d.ts +35 -0
- package/dist/types/plugins/{qr.d.ts → gen/qr-scanner/sdk/qr-scanner.d.ts} +2 -3
- package/dist/types/plugins/gen/qr-scanner/sdk/qr-scanner.gen.d.ts +15 -0
- package/dist/types/runtime/app.d.ts +9 -2
- package/dist/types/runtime/fetch.d.ts +2 -0
- package/dist/types/runtime/input.d.ts +8 -1
- package/dist/types/runtime/media.d.ts +6 -10
- package/dist/types/runtime/misc.d.ts +4 -1
- package/dist/types/runtime/net.d.ts +3 -2
- package/dist/types/runtime/touch.d.ts +32 -0
- package/dist/types/scene/defineScene.d.ts +43 -2
- package/dist/types/scene/editor.d.ts +52 -0
- package/dist/types/scene/gizmos.d.ts +7 -4
- package/dist/types/ui/NativeView.d.ts +6 -4
- package/dist/types/ui/UI.d.ts +1 -1
- package/dist/types/ui/UIBottomSheet.d.ts +6 -12
- package/dist/types/ui/UIButton.d.ts +12 -14
- package/dist/types/ui/UIContainer.d.ts +0 -6
- package/dist/types/ui/UIImage.d.ts +1 -4
- package/dist/types/ui/UIInput.d.ts +8 -24
- package/dist/types/ui/UIModal.d.ts +0 -2
- package/dist/types/ui/UINode.d.ts +70 -56
- package/dist/types/ui/UIPager.d.ts +28 -27
- package/dist/types/ui/UIPopover.d.ts +0 -2
- package/dist/types/ui/UIScreen.d.ts +12 -17
- package/dist/types/ui/UIScrollable.d.ts +1 -4
- package/dist/types/ui/UIText.d.ts +0 -2
- package/dist/types/ui/UIVideo.d.ts +3 -5
- package/dist/types/ui/UIVirtualizedList.d.ts +14 -16
- package/dist/types/ui/UIWidget.d.ts +10 -9
- package/dist/types/ui/colorKeys.gen.d.ts +9 -0
- package/dist/types/ui/presentable.d.ts +46 -32
- package/dist/types/ui/router.d.ts +18 -7
- package/dist/types/ui/styleColor.d.ts +1 -0
- package/dist/types/ui/transitions.d.ts +18 -0
- package/dist/types/ui/tree.d.ts +75 -0
- package/dist/types/version.d.ts +10 -0
- package/dist/types.json +1 -1
- package/package.json +12 -3
- package/prompts/2d.md +2 -6
- package/prompts/3d.md +1 -5
- package/prompts/README.md +142 -142
- package/prompts/canvas.md +9 -8
- package/prompts/compose.ts +1 -1
- package/prompts/core.md +3 -3
- package/prompts/design.md +1 -1
- package/prompts/dist/2d-game.md +77 -54
- package/prompts/dist/3d-app.md +75 -52
- package/prompts/dist/ar-app.md +74 -51
- package/prompts/dist/design.md +30 -8
- package/prompts/dist/ui-app.md +73 -46
- package/prompts/ui-design.md +2 -3
- package/prompts/ui.md +45 -37
- package/src/animate/tween/Animation.ts +262 -378
- package/src/animate/tween/animateValue.ts +103 -100
- package/src/animate/tween/easing.ts +10 -3
- package/src/animate/tween/spec.ts +505 -479
- package/src/audio/Sound.ts +3 -3
- package/src/audio/audio.ts +162 -161
- package/src/bridges/2d.d.ts +317 -0
- package/src/bridges/app.d.ts +91 -0
- package/src/bridges/audio.d.ts +97 -0
- package/src/bridges/canvas.d.ts +79 -0
- package/src/bridges/device.d.ts +72 -0
- package/src/bridges/fetch.d.ts +80 -0
- package/src/bridges/files.d.ts +70 -0
- package/src/bridges/gl.d.ts +1133 -0
- package/src/bridges/input.d.ts +72 -0
- package/src/bridges/media.d.ts +55 -0
- package/src/bridges/nav.d.ts +71 -0
- package/src/bridges/net.d.ts +52 -0
- package/src/bridges/service.d.ts +52 -0
- package/src/bridges/socket.d.ts +31 -0
- package/src/bridges/storage.d.ts +33 -0
- package/src/bridges/tree.d.ts +301 -0
- package/src/bridges/types.d.ts +49 -0
- package/src/canvas/Canvas.ts +114 -159
- package/src/canvas/gen/cssColor.ts +224 -0
- package/src/canvas/gen/recorder.ts +212 -0
- package/src/canvas/gen/spec.ts +201 -0
- package/src/chisel.ts +193 -0
- package/src/compile/assetMacro.ts +1 -1
- package/src/compile/bundler.ts +11 -2
- package/src/compile/compileProject.ts +43 -4
- package/src/compile/fontMacro.ts +3 -4
- package/src/compile/header.ts +26 -5
- package/src/compile/index.ts +3 -1
- package/src/compile/liteMaterial.ts +1 -1
- package/src/compile/sceneEditor.ts +11 -26
- package/src/core/color.ts +73 -30
- package/src/core/pins.ts +51 -0
- package/src/core/signals.ts +8 -1
- package/src/g2/CharacterController2D.ts +3 -3
- package/src/g2/Node2D.ts +57 -39
- package/src/g2/Physics2D.ts +2 -2
- package/src/g2/Scene2D.ts +35 -23
- package/src/g2/Texture2D.ts +1 -1
- package/src/g2/loop.ts +4 -4
- package/src/gl/Foliage.ts +72 -17
- package/src/gl/Geometry.ts +1 -2
- package/src/gl/Light.ts +13 -0
- package/src/gl/Lightmap.ts +45 -29
- package/src/gl/Material.ts +95 -51
- package/src/gl/Model.ts +172 -167
- package/src/gl/Node.ts +91 -24
- package/src/gl/Particles.ts +20 -1
- package/src/gl/Scene.ts +100 -44
- package/src/gl/Texture.ts +8 -7
- package/src/gl/animation/AnimationClip.ts +1 -1
- package/src/gl/animation/Locomotion.ts +77 -8
- package/src/gl/nav/NavMesh.ts +3 -4
- package/src/gl/physics/Physics.ts +2 -2
- package/src/gl/physics/physicsEvents.ts +3 -3
- package/src/gl/terrain/Terrain.ts +33 -5
- package/src/gl/touch.ts +14 -15
- package/src/host.d.ts +2 -3
- package/src/inject.editor.ts +7 -0
- package/src/inject.ts +233 -236
- package/src/net/core.ts +6 -5
- package/src/net/index.ts +1 -1
- package/src/net/replication.ts +1 -1
- package/src/plugin.ts +191 -0
- package/src/plugins/gen/camera/contract.d.ts +27 -0
- package/src/plugins/gen/camera/sdk/camera.gen.ts +46 -0
- package/src/plugins/gen/camera/sdk/camera.ts +57 -0
- package/src/plugins/gen/geolocation/contract.d.ts +50 -0
- package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +54 -0
- package/src/plugins/{geolocation.ts → gen/geolocation/sdk/geolocation.ts} +22 -43
- package/src/plugins/gen/map/contract.d.ts +144 -0
- package/src/plugins/gen/map/sdk/map.gen.ts +88 -0
- package/src/plugins/{map.ts → gen/map/sdk/map.ts} +68 -102
- package/src/plugins/gen/push/contract.d.ts +61 -0
- package/src/plugins/gen/push/sdk/push.gen.ts +60 -0
- package/src/plugins/gen/push/sdk/push.ts +105 -0
- package/src/plugins/gen/qr-scanner/contract.d.ts +16 -0
- package/src/plugins/gen/qr-scanner/sdk/qr-scanner.gen.ts +29 -0
- package/src/plugins/gen/qr-scanner/sdk/qr-scanner.ts +52 -0
- package/src/plugins/permission.ts +5 -4
- package/src/runtime/app.ts +20 -9
- package/src/runtime/appEvents.ts +5 -4
- package/src/runtime/channel.ts +18 -15
- package/src/runtime/clipboard.ts +4 -3
- package/src/runtime/datetime.ts +2 -1
- package/src/runtime/device.ts +17 -15
- package/src/runtime/fetch.ts +30 -20
- package/src/runtime/files.ts +16 -15
- package/src/runtime/input.ts +21 -8
- package/src/runtime/media.ts +50 -46
- package/src/runtime/misc.ts +7 -3
- package/src/runtime/net.ts +8 -7
- package/src/runtime/rpc.ts +1 -3
- package/src/runtime/service.ts +19 -14
- package/src/runtime/share.ts +4 -3
- package/src/runtime/storage.ts +6 -4
- package/src/runtime/touch.ts +32 -0
- package/src/scene/defineScene.ts +61 -365
- package/src/scene/editor.ts +408 -0
- package/src/scene/editorPlugins.ts +3 -3
- package/src/scene/gizmos.ts +154 -148
- package/src/server/db/marci/query.ts +1 -1
- package/src/server/host.ts +1 -1
- package/src/server/runtime.ts +1 -1
- package/src/ui/NativeView.ts +72 -25
- package/src/ui/UI.ts +3 -3
- package/src/ui/UIBottomSheet.ts +16 -17
- package/src/ui/UIButton.ts +54 -16
- package/src/ui/UIContainer.ts +0 -6
- package/src/ui/UIImage.ts +34 -30
- package/src/ui/UIInput.ts +29 -37
- package/src/ui/UIModal.ts +1 -3
- package/src/ui/UINode.ts +347 -297
- package/src/ui/UIPager.ts +93 -78
- package/src/ui/UIPopover.ts +0 -2
- package/src/ui/UIScreen.ts +41 -36
- package/src/ui/UIScrollable.ts +19 -13
- package/src/ui/UISpacer.ts +1 -1
- package/src/ui/UITabs.ts +8 -6
- package/src/ui/UIText.ts +7 -17
- package/src/ui/UIVideo.ts +30 -27
- package/src/ui/UIVirtualizedList.ts +58 -59
- package/src/ui/UIWidget.ts +38 -18
- package/src/ui/colorKeys.gen.ts +37 -0
- package/src/ui/fonts.ts +2 -2
- package/src/ui/presentable.ts +59 -42
- package/src/ui/router.ts +52 -35
- package/src/ui/styleColor.ts +56 -0
- package/src/ui/theme.ts +7 -6
- package/src/ui/transitions.ts +249 -0
- package/src/ui/tree.ts +346 -0
- package/src/version.ts +24 -0
- package/tests/helpers/engineWorld.ts +11 -0
- package/tests/helpers/fakeTree.ts +353 -0
- package/tests/helpers/hostStubs.ts +31 -0
- package/tests/helpers/index.ts +14 -0
- package/tests/helpers/memoryMarci.ts +124 -0
- package/tests/helpers/phases.ts +23 -0
- package/tests/helpers/preload.ts +18 -0
- package/tests/helpers/stubApp.ts +2 -0
- package/tests/helpers/stubDevice.ts +2 -0
- package/tests/helpers/stubFetch.ts +2 -0
- package/tests/helpers/stubInput.ts +2 -0
- package/dist/inject.js +0 -4730
- package/dist/types/core/registry.d.ts +0 -7
- package/dist/types/plugins/camera.d.ts +0 -25
- package/dist/types/plugins/push.d.ts +0 -46
- package/src/bridges.d.ts +0 -1760
- package/src/compile/__tests__/assetIconMacro.test.ts +0 -219
- package/src/compile/__tests__/assetMacro.test.ts +0 -100
- package/src/compile/__tests__/assetName.test.ts +0 -55
- package/src/compile/__tests__/compile.test.ts +0 -310
- package/src/compile/__tests__/detectEntry.test.ts +0 -151
- package/src/compile/__tests__/fontMacro.test.ts +0 -199
- package/src/compile/__tests__/serverSplit.test.ts +0 -27
- package/src/core/__tests__/stateMachine.test.ts +0 -132
- package/src/core/registry.ts +0 -23
- package/src/plugins/camera.ts +0 -81
- package/src/plugins/push.ts +0 -132
- package/src/plugins/qr.ts +0 -73
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/map/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
|
+
// map — a vector map (maplibre-native on iOS / Android, maplibre-gl in the browser).
|
|
4
|
+
// The wire is lower than the app-facing `MapView`: the wrapper in sdk/ resolves a style to a url or
|
|
5
|
+
// to text, turns marker items into GeoJSON, queues calls until `ready` and re-applies the
|
|
6
|
+
// style-lifetime state (layers, data, padding, the user puck) after every style load.
|
|
7
|
+
//
|
|
8
|
+
// The plugin never touches the location hardware: the user puck is fed by the app.
|
|
9
|
+
|
|
10
|
+
import type { Json, View, f64 } from "../../../plugin"
|
|
11
|
+
|
|
12
|
+
/** `[longitude, latitude]` — GeoJSON order. */
|
|
13
|
+
export type LngLat = [f64, f64]
|
|
14
|
+
|
|
15
|
+
export interface MapParams {
|
|
16
|
+
/** A style URL the map fetches itself. */
|
|
17
|
+
style?: string
|
|
18
|
+
/** A style as JSON text; wins over `style`. It has no base url: every url INSIDE it (sources,
|
|
19
|
+
* sprite, glyphs) must be absolute. */
|
|
20
|
+
styleJson?: string
|
|
21
|
+
center?: LngLat
|
|
22
|
+
/** Default 12 with a center. */
|
|
23
|
+
zoom?: f64
|
|
24
|
+
minZoom?: f64
|
|
25
|
+
maxZoom?: f64
|
|
26
|
+
/** Degrees clockwise from north. */
|
|
27
|
+
bearing?: f64
|
|
28
|
+
/** Degrees from the vertical. */
|
|
29
|
+
pitch?: f64
|
|
30
|
+
/** The two-finger rotate gesture. Default true. */
|
|
31
|
+
rotate?: boolean
|
|
32
|
+
/** The two-finger tilt gesture. Default false. */
|
|
33
|
+
tilt?: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface MapCamera {
|
|
37
|
+
center: LngLat
|
|
38
|
+
zoom: f64
|
|
39
|
+
bearing: f64
|
|
40
|
+
pitch: f64
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** A feature of a managed source under a tap. */
|
|
44
|
+
export interface MapFeature {
|
|
45
|
+
/** The source's name — the layer's name in the app. */
|
|
46
|
+
source: string
|
|
47
|
+
id: string | f64 | null
|
|
48
|
+
properties: { [key: string]: Json }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface MapTap {
|
|
52
|
+
lngLat: LngLat
|
|
53
|
+
/** View-space point, logical px. */
|
|
54
|
+
point: [f64, f64]
|
|
55
|
+
/** Absent for a tap that hit no feature of a managed source. A tap on a CLUSTER zooms in and
|
|
56
|
+
* sends no event. */
|
|
57
|
+
feature?: MapFeature
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface MapEvents {
|
|
61
|
+
/** The style loaded and the map takes calls. Fires again after every style reload. */
|
|
62
|
+
ready: void
|
|
63
|
+
tap: MapTap
|
|
64
|
+
/** The camera settled after a gesture or an animation. */
|
|
65
|
+
moveEnd: MapCamera
|
|
66
|
+
/** A problem that is never fatal: a style that would not load, a source out of reach. */
|
|
67
|
+
error: { message: string }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type MapLayerKind = "markers" | "line"
|
|
71
|
+
|
|
72
|
+
/** The keys of both kinds; a half reads the ones of the kind it is asked for. */
|
|
73
|
+
export interface MapLayerOptions {
|
|
74
|
+
/** markers: group nearby markers (a cluster tap zooms in). Default false. */
|
|
75
|
+
cluster?: boolean
|
|
76
|
+
/** markers: px. Default 50. */
|
|
77
|
+
clusterRadius?: f64
|
|
78
|
+
/** markers: the zoom at which clusters stop forming. */
|
|
79
|
+
clusterMaxZoom?: f64
|
|
80
|
+
/** line. */
|
|
81
|
+
color?: string
|
|
82
|
+
/** line: px. Default 4. */
|
|
83
|
+
width?: f64
|
|
84
|
+
/** line: 0–1. Default 1. */
|
|
85
|
+
opacity?: f64
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface MapCameraMove {
|
|
89
|
+
zoom?: f64
|
|
90
|
+
bearing?: f64
|
|
91
|
+
pitch?: f64
|
|
92
|
+
/** flyTo only: ms. Default 600. */
|
|
93
|
+
duration?: f64
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Px, or a share of the view's size as "NN%". */
|
|
97
|
+
export type MapInset = f64 | string
|
|
98
|
+
|
|
99
|
+
export interface MapPadding {
|
|
100
|
+
top?: MapInset
|
|
101
|
+
left?: MapInset
|
|
102
|
+
bottom?: MapInset
|
|
103
|
+
right?: MapInset
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface MapFitOptions {
|
|
107
|
+
/** Around the points: px on every edge, or per edge. Added to the view padding. */
|
|
108
|
+
padding?: f64 | MapPadding
|
|
109
|
+
/** Default: 16 for one point, none otherwise. */
|
|
110
|
+
maxZoom?: f64
|
|
111
|
+
/** Default true. */
|
|
112
|
+
animate?: boolean
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface MapUserLocationOptions {
|
|
116
|
+
/** Meters — the halo around the dot. */
|
|
117
|
+
accuracy?: f64 | null
|
|
118
|
+
/** Degrees clockwise from north — the direction wedge; null hides it. */
|
|
119
|
+
heading?: f64 | null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface MapView extends View<"map", MapParams, MapEvents> {
|
|
123
|
+
/** Make sure the GeoJSON source `source` exists. A source the style declares is used as is (its
|
|
124
|
+
* layers draw, only data flows); otherwise the half creates it with the default layers of `kind`.
|
|
125
|
+
* @rejects notReady */
|
|
126
|
+
ensureLayer(source: string, kind: MapLayerKind, options?: MapLayerOptions): Promise<void>
|
|
127
|
+
removeLayer(source: string): Promise<void>
|
|
128
|
+
/** Replace the data of a GeoJSON source: a Feature or a FeatureCollection.
|
|
129
|
+
* @rejects notReady */
|
|
130
|
+
setData(source: string, geojson: Json): Promise<void>
|
|
131
|
+
|
|
132
|
+
flyTo(center: LngLat, options?: MapCameraMove): Promise<void>
|
|
133
|
+
jumpTo(center: LngLat, options?: MapCameraMove): Promise<void>
|
|
134
|
+
/** Fit the camera to the points.
|
|
135
|
+
* @rejects noPoints */
|
|
136
|
+
fitPoints(points: LngLat[], options?: MapFitOptions): Promise<void>
|
|
137
|
+
/** The part of the view covered by the app's UI; camera moves center inside the rest. */
|
|
138
|
+
setPadding(padding: MapPadding): Promise<void>
|
|
139
|
+
/** @rejects notReady */
|
|
140
|
+
getCamera(): Promise<MapCamera>
|
|
141
|
+
|
|
142
|
+
/** Move the user puck; null hides it. */
|
|
143
|
+
setUserLocation(lngLat: LngLat | null, options?: MapUserLocationOptions): Promise<void>
|
|
144
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/map/sdk/map.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
|
+
// map: the view "map", contract version 1.
|
|
5
|
+
|
|
6
|
+
import { ViewChannel, type Json } from "../../../../plugin"
|
|
7
|
+
import type { LngLat, MapCamera, MapCameraMove, MapEvents, MapFitOptions, MapLayerKind, MapLayerOptions, MapPadding, MapParams, MapUserLocationOptions } from "../contract"
|
|
8
|
+
|
|
9
|
+
/** What the halves register under. */
|
|
10
|
+
export const NAME = "map"
|
|
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
|
+
/** Make sure the GeoJSON source `source` exists. A source the style declares is used as is (its
|
|
17
|
+
* layers draw, only data flows); otherwise the half creates it with the default layers of `kind`.
|
|
18
|
+
* Rejects with "notReady". */
|
|
19
|
+
export const ensureLayer = (view: ViewChannel<MapEvents>, source: string, kind: MapLayerKind, options?: MapLayerOptions): Promise<void> =>
|
|
20
|
+
view._call("ensureLayer", [source, kind, options])
|
|
21
|
+
|
|
22
|
+
export const removeLayer = (view: ViewChannel<MapEvents>, source: string): Promise<void> =>
|
|
23
|
+
view._call("removeLayer", [source])
|
|
24
|
+
|
|
25
|
+
/** Replace the data of a GeoJSON source: a Feature or a FeatureCollection.
|
|
26
|
+
* Rejects with "notReady". */
|
|
27
|
+
export const setData = (view: ViewChannel<MapEvents>, source: string, geojson: Json): Promise<void> =>
|
|
28
|
+
view._call("setData", [source, geojson])
|
|
29
|
+
|
|
30
|
+
export const flyTo = (view: ViewChannel<MapEvents>, center: LngLat, options?: MapCameraMove): Promise<void> =>
|
|
31
|
+
view._call("flyTo", [center, options])
|
|
32
|
+
|
|
33
|
+
export const jumpTo = (view: ViewChannel<MapEvents>, center: LngLat, options?: MapCameraMove): Promise<void> =>
|
|
34
|
+
view._call("jumpTo", [center, options])
|
|
35
|
+
|
|
36
|
+
/** Fit the camera to the points.
|
|
37
|
+
* Rejects with "noPoints". */
|
|
38
|
+
export const fitPoints = (view: ViewChannel<MapEvents>, points: LngLat[], options?: MapFitOptions): Promise<void> =>
|
|
39
|
+
view._call("fitPoints", [points, options])
|
|
40
|
+
|
|
41
|
+
/** The part of the view covered by the app's UI; camera moves center inside the rest. */
|
|
42
|
+
export const setPadding = (view: ViewChannel<MapEvents>, padding: MapPadding): Promise<void> =>
|
|
43
|
+
view._call("setPadding", [padding])
|
|
44
|
+
|
|
45
|
+
/** Rejects with "notReady". */
|
|
46
|
+
export const getCamera = (view: ViewChannel<MapEvents>): Promise<MapCamera> =>
|
|
47
|
+
view._call("getCamera", [])
|
|
48
|
+
|
|
49
|
+
/** Move the user puck; null hides it. */
|
|
50
|
+
export const setUserLocation = (view: ViewChannel<MapEvents>, lngLat: LngLat | null, options?: MapUserLocationOptions): Promise<void> =>
|
|
51
|
+
view._call("setUserLocation", [lngLat, options])
|
|
52
|
+
|
|
53
|
+
// ---- what the host's half has --------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
/** A method of the contract. */
|
|
56
|
+
export type Method = "ensureLayer" | "removeLayer" | "setData" | "flyTo" | "jumpTo" | "fitPoints" | "setPadding" | "getCamera" | "setUserLocation"
|
|
57
|
+
|
|
58
|
+
/** Whether this host has the view "map" — and, given a method, a half new enough for it
|
|
59
|
+
* (a method added after the first version of the contract is tagged `@since`). */
|
|
60
|
+
export const supports = (method?: Method): boolean =>
|
|
61
|
+
ViewChannel.supports(NAME) // every method is of version 1
|
|
62
|
+
|
|
63
|
+
// ---- the element ----------------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
/** The view "map" with the wire's methods as its own. */
|
|
66
|
+
export class MapViewBase extends ViewChannel<MapEvents> {
|
|
67
|
+
constructor(params: MapParams = {}) { super("MapView", NAME, params) }
|
|
68
|
+
|
|
69
|
+
/** Make sure the GeoJSON source `source` exists. A source the style declares is used as is (its
|
|
70
|
+
* layers draw, only data flows); otherwise the half creates it with the default layers of `kind`.
|
|
71
|
+
* Rejects with "notReady". */
|
|
72
|
+
ensureLayer(source: string, kind: MapLayerKind, options?: MapLayerOptions): Promise<void> { return ensureLayer(this, source, kind, options) }
|
|
73
|
+
removeLayer(source: string): Promise<void> { return removeLayer(this, source) }
|
|
74
|
+
/** Replace the data of a GeoJSON source: a Feature or a FeatureCollection.
|
|
75
|
+
* Rejects with "notReady". */
|
|
76
|
+
setData(source: string, geojson: Json): Promise<void> { return setData(this, source, geojson) }
|
|
77
|
+
flyTo(center: LngLat, options?: MapCameraMove): Promise<void> { return flyTo(this, center, options) }
|
|
78
|
+
jumpTo(center: LngLat, options?: MapCameraMove): Promise<void> { return jumpTo(this, center, options) }
|
|
79
|
+
/** Fit the camera to the points.
|
|
80
|
+
* Rejects with "noPoints". */
|
|
81
|
+
fitPoints(points: LngLat[], options?: MapFitOptions): Promise<void> { return fitPoints(this, points, options) }
|
|
82
|
+
/** The part of the view covered by the app's UI; camera moves center inside the rest. */
|
|
83
|
+
setPadding(padding: MapPadding): Promise<void> { return setPadding(this, padding) }
|
|
84
|
+
/** Rejects with "notReady". */
|
|
85
|
+
getCamera(): Promise<MapCamera> { return getCamera(this) }
|
|
86
|
+
/** Move the user puck; null hides it. */
|
|
87
|
+
setUserLocation(lngLat: LngLat | null, options?: MapUserLocationOptions): Promise<void> { return setUserLocation(this, lngLat, options) }
|
|
88
|
+
}
|
|
@@ -1,22 +1,33 @@
|
|
|
1
|
-
//
|
|
2
|
-
// the `
|
|
3
|
-
//
|
|
4
|
-
// the
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
1
|
+
// GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/map/sdk/map.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
|
+
// MapView — a vector map (maplibre-native on iOS / Android, maplibre-gl in the browser) as a view
|
|
4
|
+
// the app pushes (`Router.push(map)`), opens fullscreen (`map.open()`) or embeds among a screen's
|
|
5
|
+
// children. The style is a name, a URL, a bundled `asset()`, a read file or the style object (see
|
|
6
|
+
// MapStyle). Data goes in as named layers (`map.markers(name)`, `map.line(name)`) whose `set()`
|
|
7
|
+
// replaces their content; the camera is driven with `flyTo` / `fitPoints`; the user puck is fed by
|
|
8
|
+
// the app (`setUserLocation`) — the plugin never touches the location hardware.
|
|
8
9
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
import
|
|
10
|
+
// Hand-written over the generated wire because the public API is higher than the wire:
|
|
11
|
+
// - a style is resolved to a url or to text HERE, once, at creation;
|
|
12
|
+
// - marker items and coordinate lists become GeoJSON here;
|
|
13
|
+
// - the native map exists only once presented and its style loaded (`ready`): calls issued before
|
|
14
|
+
// that are queued and replayed in order, so an app builds the map declaratively and pushes it
|
|
15
|
+
// later without a ready-dance of its own;
|
|
16
|
+
// - what lives in the STYLE (layers, data, the padding, the user puck) is kept and re-applied
|
|
17
|
+
// after every style load.
|
|
18
|
+
|
|
19
|
+
import { ViewChannel, bufferText, type FetchResponse, type Json, type NativeView } from "../../../../plugin"
|
|
20
|
+
import { NAME, ensureLayer, fitPoints, flyTo, getCamera, jumpTo, setData, setPadding, setUserLocation } from "./map.gen"
|
|
21
|
+
import type {
|
|
22
|
+
LngLat, MapCamera, MapCameraMove, MapEvents, MapFitOptions, MapLayerKind, MapPadding, MapParams,
|
|
23
|
+
MapTap as WireTap, MapUserLocationOptions,
|
|
24
|
+
} from "../contract"
|
|
25
|
+
|
|
26
|
+
export type { LngLat, MapCamera }
|
|
27
|
+
export type CameraMove = MapCameraMove
|
|
28
|
+
export type FitOptions = MapFitOptions
|
|
29
|
+
export type MapPaddingValues = MapPadding
|
|
30
|
+
export type UserLocationOptions = MapUserLocationOptions
|
|
20
31
|
|
|
21
32
|
/** The ready-made styles a map falls back on — [OpenFreeMap](https://openfreemap.org): OSM data,
|
|
22
33
|
* no key, no registration, no request limits, commercial use allowed, and the whole stack is
|
|
@@ -56,34 +67,12 @@ const NAMED_STYLES: Record<string, string> = {
|
|
|
56
67
|
// `string & {}` keeps the named suggestions in the editor while still accepting any URL.
|
|
57
68
|
export type MapStyle = MapStyleName | (string & {}) | FetchResponse | object
|
|
58
69
|
|
|
59
|
-
|
|
60
|
-
export type LngLat = [number, number]
|
|
61
|
-
|
|
62
|
-
export interface MapCamera {
|
|
63
|
-
center: LngLat
|
|
64
|
-
zoom: number
|
|
65
|
-
/** Degrees clockwise from north. */
|
|
66
|
-
bearing: number
|
|
67
|
-
/** Degrees from the vertical. */
|
|
68
|
-
pitch: number
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export interface MapOptions {
|
|
70
|
+
export interface MapOptions extends Omit<MapParams, "style" | "styleJson"> {
|
|
72
71
|
/** The MapLibre style: a ready-made name (`"liberty"` — the default, `"positron"`, …), a URL,
|
|
73
72
|
* a style bundled with the app (`asset("./style.json")`), an already-read file
|
|
74
73
|
* (`fetchLocal("style.json")`, `await fetch(url)`) or the style object itself — see
|
|
75
74
|
* {@link MapStyle}. */
|
|
76
75
|
style?: MapStyle
|
|
77
|
-
center?: LngLat
|
|
78
|
-
zoom?: number
|
|
79
|
-
minZoom?: number
|
|
80
|
-
maxZoom?: number
|
|
81
|
-
bearing?: number
|
|
82
|
-
pitch?: number
|
|
83
|
-
/** Two-finger rotate gesture (default true). */
|
|
84
|
-
rotate?: boolean
|
|
85
|
-
/** Two-finger tilt gesture (default false — most city maps stay flat). */
|
|
86
|
-
tilt?: boolean
|
|
87
76
|
}
|
|
88
77
|
|
|
89
78
|
/** A tap on the map itself — not on a feature of a managed layer. */
|
|
@@ -93,29 +82,6 @@ export interface MapTap {
|
|
|
93
82
|
point: [number, number]
|
|
94
83
|
}
|
|
95
84
|
|
|
96
|
-
export interface CameraMove {
|
|
97
|
-
zoom?: number
|
|
98
|
-
bearing?: number
|
|
99
|
-
pitch?: number
|
|
100
|
-
/** Animation length, ms (flyTo only; default 600). */
|
|
101
|
-
duration?: number
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export interface MapPaddingValues {
|
|
105
|
-
top?: number | string
|
|
106
|
-
left?: number | string
|
|
107
|
-
bottom?: number | string
|
|
108
|
-
right?: number | string
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export interface FitOptions {
|
|
112
|
-
/** Px around the points, or per edge. Added to the view padding set by `setPadding`. */
|
|
113
|
-
padding?: number | MapPaddingValues
|
|
114
|
-
maxZoom?: number
|
|
115
|
-
/** Default true. */
|
|
116
|
-
animate?: boolean
|
|
117
|
-
}
|
|
118
|
-
|
|
119
85
|
/** One marker. `id` comes back in `onTap`; `icon` names an image of the style's sprite; `color` /
|
|
120
86
|
* `title` feed the default layers; extra keys become feature properties. */
|
|
121
87
|
export interface MarkerItem {
|
|
@@ -151,13 +117,6 @@ export interface LineLayerOptions {
|
|
|
151
117
|
opacity?: number
|
|
152
118
|
}
|
|
153
119
|
|
|
154
|
-
export interface UserLocationOptions {
|
|
155
|
-
/** Horizontal accuracy radius, meters — drawn as the halo around the dot. */
|
|
156
|
-
accuracy?: number | null
|
|
157
|
-
/** Degrees clockwise from north — drawn as the direction wedge; null hides it. */
|
|
158
|
-
heading?: number | null
|
|
159
|
-
}
|
|
160
|
-
|
|
161
120
|
export interface MarkerLayer {
|
|
162
121
|
readonly name: string
|
|
163
122
|
/** Replace the layer's markers. */
|
|
@@ -208,15 +167,21 @@ export interface MapView extends NativeView {
|
|
|
208
167
|
setUserLocation(lngLat: LngLat | null, options?: UserLocationOptions): this
|
|
209
168
|
}
|
|
210
169
|
|
|
170
|
+
/** The wire's calls the wrapper sends without waiting for an answer, by name: what a queue entry
|
|
171
|
+
* replays. Every one takes the view first. */
|
|
172
|
+
const SEND = { ensureLayer, setData, flyTo, jumpTo, fitPoints, setPadding, setUserLocation }
|
|
173
|
+
type Send = typeof SEND
|
|
174
|
+
type SendArgs<M extends keyof Send> = Send[M] extends (view: any, ...args: infer A) => any ? A : never
|
|
175
|
+
|
|
211
176
|
type Queued = { method: string, args: any[] }
|
|
212
|
-
type LayerSpec = { kind:
|
|
177
|
+
type LayerSpec = { kind: MapLayerKind, options: object }
|
|
213
178
|
|
|
214
179
|
/** Methods whose effect lives in the STYLE, not in the view: a style (re)load wipes them, so the
|
|
215
180
|
* wrapper keeps their last value and re-applies it on every `ready` instead of replaying them
|
|
216
181
|
* from the queue. Camera calls are not here — the camera survives a style reload. */
|
|
217
182
|
const STATEFUL = ["ensureLayer", "setData", "setPadding", "setUserLocation"]
|
|
218
183
|
|
|
219
|
-
class MapViewElement extends
|
|
184
|
+
class MapViewElement extends ViewChannel<MapEvents> {
|
|
220
185
|
private _ready = false
|
|
221
186
|
private _queue: Queued[] = []
|
|
222
187
|
private readonly _layers = new Map<string, MapLayerHandle>()
|
|
@@ -224,18 +189,18 @@ class MapViewElement extends NativeViewElement {
|
|
|
224
189
|
private readonly _specs = new Map<string, LayerSpec>()
|
|
225
190
|
private readonly _data = new Map<string, object>()
|
|
226
191
|
private _padding?: MapPaddingValues
|
|
227
|
-
private _user?:
|
|
192
|
+
private _user?: SendArgs<"setUserLocation">
|
|
228
193
|
private readonly _readyCallbacks: (() => void)[] = []
|
|
229
194
|
private readonly _tapCallbacks: ((tap: MapTap) => void)[] = []
|
|
230
195
|
private readonly _moveCallbacks: ((camera: MapCamera) => void)[] = []
|
|
231
196
|
private readonly _errorCallbacks: ((error: { message: string }) => void)[] = []
|
|
232
197
|
|
|
233
198
|
constructor(options: MapOptions = {}) {
|
|
234
|
-
super("
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
199
|
+
super("MapView", NAME, _styleParams(options))
|
|
200
|
+
this.on("ready", () => this._onReady())
|
|
201
|
+
this.on("tap", tap => this._onTap(tap))
|
|
202
|
+
this.on("moveEnd", camera => { for (const cb of [...this._moveCallbacks]) cb(camera) })
|
|
203
|
+
this.on("error", error => this._onError(error))
|
|
239
204
|
}
|
|
240
205
|
|
|
241
206
|
onReady(callback: () => void): this { this._readyCallbacks.push(callback); return this }
|
|
@@ -251,7 +216,7 @@ class MapViewElement extends NativeViewElement {
|
|
|
251
216
|
}
|
|
252
217
|
setData(source: string, geojson: object): this {
|
|
253
218
|
this._data.set(source, geojson)
|
|
254
|
-
this._send("setData", [source, geojson])
|
|
219
|
+
this._send("setData", [source, geojson as Json])
|
|
255
220
|
return this
|
|
256
221
|
}
|
|
257
222
|
|
|
@@ -259,22 +224,23 @@ class MapViewElement extends NativeViewElement {
|
|
|
259
224
|
jumpTo(center: LngLat, options: CameraMove = {}): this { this._send("jumpTo", [center, options]); return this }
|
|
260
225
|
fitPoints(points: LngLat[], options: FitOptions = {}): this { this._send("fitPoints", [points, options]); return this }
|
|
261
226
|
setPadding(padding: MapPaddingValues): this { this._padding = padding; this._send("setPadding", [padding]); return this }
|
|
262
|
-
getCamera(): Promise<MapCamera> { return this
|
|
227
|
+
getCamera(): Promise<MapCamera> { return getCamera(this) }
|
|
263
228
|
|
|
264
229
|
setUserLocation(lngLat: LngLat | null, options: UserLocationOptions = {}): this {
|
|
265
230
|
this._user = [lngLat, options]
|
|
266
|
-
this._send("setUserLocation",
|
|
231
|
+
this._send("setUserLocation", this._user)
|
|
267
232
|
return this
|
|
268
233
|
}
|
|
269
234
|
|
|
270
235
|
/** @internal queue until `ready`, then straight through. Rejections are logged, never thrown:
|
|
271
|
-
* a fire-and-forget camera/data call has no caller to reject to. */
|
|
272
|
-
_send(method:
|
|
236
|
+
* a fire-and-forget camera / data call has no caller to reject to. */
|
|
237
|
+
_send<M extends keyof Send>(method: M, args: SendArgs<M>): void {
|
|
273
238
|
if (!this._ready) { this._queue.push({ method, args }); return }
|
|
274
|
-
|
|
239
|
+
const call: (view: this, ...args: any[]) => Promise<void> = SEND[method] ?? ((view, ...rest) => view._call(method, rest))
|
|
240
|
+
call(this, ...args).catch((e: Error) => console.error(`MapView.${method}: ${e.message}`))
|
|
275
241
|
}
|
|
276
242
|
|
|
277
|
-
private _layer(name: string, kind:
|
|
243
|
+
private _layer(name: string, kind: MapLayerKind, options: object): MapLayerHandle {
|
|
278
244
|
let layer = this._layers.get(name)
|
|
279
245
|
if (!layer) {
|
|
280
246
|
layer = new MapLayerHandle(this, name)
|
|
@@ -294,28 +260,28 @@ class MapViewElement extends NativeViewElement {
|
|
|
294
260
|
const queue = this._queue
|
|
295
261
|
this._queue = []
|
|
296
262
|
for (const [name, spec] of this._specs) this._send("ensureLayer", [name, spec.kind, spec.options])
|
|
297
|
-
for (const [source, geojson] of this._data) this._send("setData", [source, geojson])
|
|
263
|
+
for (const [source, geojson] of this._data) this._send("setData", [source, geojson as Json])
|
|
298
264
|
if (this._padding) this._send("setPadding", [this._padding])
|
|
299
265
|
if (this._user) this._send("setUserLocation", this._user)
|
|
300
|
-
for (const q of queue) if (STATEFUL.indexOf(q.method) < 0) this._send(q.method, q.args)
|
|
266
|
+
for (const q of queue) if (STATEFUL.indexOf(q.method) < 0) this._send(q.method as keyof Send, q.args as any)
|
|
301
267
|
for (const cb of [...this._readyCallbacks]) cb()
|
|
302
268
|
}
|
|
303
269
|
|
|
304
|
-
private _onError(
|
|
270
|
+
private _onError(error: { message: string }): void {
|
|
305
271
|
// Unhandled is not unheard: a style the host couldn't load is exactly the failure that looks
|
|
306
272
|
// like "the map is blank and nothing happened".
|
|
307
|
-
if (this._errorCallbacks.length === 0) { console.error(`MapView: ${
|
|
308
|
-
for (const cb of [...this._errorCallbacks]) cb(
|
|
273
|
+
if (this._errorCallbacks.length === 0) { console.error(`MapView: ${error?.message ?? "unknown error"}`); return }
|
|
274
|
+
for (const cb of [...this._errorCallbacks]) cb(error)
|
|
309
275
|
}
|
|
310
276
|
|
|
311
|
-
private _onTap(
|
|
312
|
-
const feature =
|
|
277
|
+
private _onTap(tap: WireTap): void {
|
|
278
|
+
const feature = tap?.feature
|
|
313
279
|
const layer = feature ? this._layers.get(feature.source) : undefined
|
|
314
|
-
if (layer) {
|
|
315
|
-
layer._tap({ id: feature.id, lngLat:
|
|
280
|
+
if (feature && layer) {
|
|
281
|
+
layer._tap({ id: feature.id as string | number, lngLat: tap.lngLat, properties: feature.properties ?? {} })
|
|
316
282
|
return
|
|
317
283
|
}
|
|
318
|
-
for (const cb of [...this._tapCallbacks]) cb({ lngLat:
|
|
284
|
+
for (const cb of [...this._tapCallbacks]) cb({ lngLat: tap.lngLat, point: tap.point })
|
|
319
285
|
}
|
|
320
286
|
}
|
|
321
287
|
|
|
@@ -348,15 +314,15 @@ class MapLayerHandle {
|
|
|
348
314
|
}
|
|
349
315
|
}
|
|
350
316
|
|
|
351
|
-
/** @internal `MapOptions` →
|
|
317
|
+
/** @internal `MapOptions` → the wire's params. A style URL stays a url the map fetches itself; every
|
|
352
318
|
* other form is read HERE and crosses as `styleJson` text — one string, once, at creation. A
|
|
353
319
|
* bundled `asset()` is `"id:N"` in shell compiles (the host already holds the bytes) and a plain
|
|
354
320
|
* url in server compiles, so the same call works on every host. */
|
|
355
|
-
export const _styleParams = (options: MapOptions = {}):
|
|
321
|
+
export const _styleParams = (options: MapOptions = {}): MapParams => {
|
|
356
322
|
const { style = "liberty", ...rest } = options
|
|
357
323
|
if (typeof style === "string") {
|
|
358
324
|
if (!style.startsWith("id:")) return { ...rest, style: NAMED_STYLES[style] ?? style }
|
|
359
|
-
const text =
|
|
325
|
+
const text = bufferText(+style.slice(3))
|
|
360
326
|
if (!text) throw new Error("MapView: the style asset is empty or missing")
|
|
361
327
|
return { ...rest, styleJson: text }
|
|
362
328
|
}
|
|
@@ -380,9 +346,9 @@ export const _toGeoJson = (items: MarkerItem[] | LngLat[]): object => {
|
|
|
380
346
|
|
|
381
347
|
/**
|
|
382
348
|
* Create a map view. `MapView.isSupported` reports whether this host registered a "map" view —
|
|
383
|
-
* check it before offering the feature
|
|
349
|
+
* check it before offering the feature.
|
|
384
350
|
*/
|
|
385
|
-
// PURE IIFE so an app that never uses the map tree-shakes the whole plugin away
|
|
351
|
+
// PURE IIFE so an app that never uses the map tree-shakes the whole plugin away.
|
|
386
352
|
export const MapView: {
|
|
387
353
|
(options?: MapOptions): MapView
|
|
388
354
|
/** Whether this host registered a "map" view. */
|
|
@@ -390,7 +356,7 @@ export const MapView: {
|
|
|
390
356
|
} = /*#__PURE__*/ (() => {
|
|
391
357
|
const factory = (options: MapOptions = {}): MapView => new MapViewElement(options) as unknown as MapView
|
|
392
358
|
Object.defineProperty(factory, "isSupported", {
|
|
393
|
-
get: (): boolean =>
|
|
359
|
+
get: (): boolean => ViewChannel.supports(NAME),
|
|
394
360
|
})
|
|
395
361
|
return factory as any
|
|
396
362
|
})()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/push/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
|
+
// push — remote notifications (APNs on iOS, FCM on Android).
|
|
4
|
+
// The host owns the OS token, the permission prompt and the registration with the le.codes relay;
|
|
5
|
+
// the app sees the opaque project-scoped ADDRESS and the payloads, never the raw token.
|
|
6
|
+
//
|
|
7
|
+
// An OPEN SESSION is the delivery pipe of the events. Opening one is free (no prompt, no I/O).
|
|
8
|
+
// Identity: every call is attributed to the world that makes it (the project's uuid, held by the
|
|
9
|
+
// host); a world without one rejects "unavailable" BEFORE any permission path.
|
|
10
|
+
|
|
11
|
+
import type { Json, Service, i32 } from "../../../plugin"
|
|
12
|
+
|
|
13
|
+
/** A delivered notification, as the app sees it. */
|
|
14
|
+
export interface PushPayload {
|
|
15
|
+
title: string
|
|
16
|
+
body?: string
|
|
17
|
+
/** A deep link — it also rides the app's own link path (`app.launchUrl`, the "url" event). */
|
|
18
|
+
url?: string
|
|
19
|
+
/** The developer's own JSON object, ≤ 2 KB serialized. */
|
|
20
|
+
data?: { [key: string]: Json }
|
|
21
|
+
badge?: i32
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface PushStatus {
|
|
25
|
+
/** The OS permission of the host app. "prompt" = not asked yet. */
|
|
26
|
+
permission: "granted" | "denied" | "prompt"
|
|
27
|
+
/** Whether this device holds a registration (an address) for this app. */
|
|
28
|
+
registered: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface PushRegisterOptions {
|
|
32
|
+
/** The app's OWN user id. Client-asserted and unverified. The registration reflects the latest
|
|
33
|
+
* call: registering without it clears it. */
|
|
34
|
+
user?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface PushRegistration {
|
|
38
|
+
address: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface PushParams {}
|
|
42
|
+
|
|
43
|
+
export interface PushEvents {
|
|
44
|
+
/** Arrived while this app was in the foreground (the host shows no banner). */
|
|
45
|
+
message: PushPayload
|
|
46
|
+
/** Tapped while the world was alive. */
|
|
47
|
+
tap: PushPayload
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface Push extends Service<"push", PushParams, PushEvents> {
|
|
51
|
+
/** Permission + registration state. Never prompts. */
|
|
52
|
+
getStatus(): Promise<PushStatus>
|
|
53
|
+
/** Ask for the permission (the first time) and register this device. Idempotent.
|
|
54
|
+
* @rejects denied unavailable */
|
|
55
|
+
register(options?: PushRegisterOptions): Promise<PushRegistration>
|
|
56
|
+
/** Remove this device's registration.
|
|
57
|
+
* @rejects unavailable */
|
|
58
|
+
unregister(): Promise<void>
|
|
59
|
+
/** The notification that cold-started this world, or null. Stable across calls. */
|
|
60
|
+
getLaunch(): Promise<PushPayload | null>
|
|
61
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/push/sdk/push.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
|
+
// push: the service "push", contract version 1.
|
|
5
|
+
|
|
6
|
+
import { ServiceChannel } from "../../../../plugin"
|
|
7
|
+
import type { PushEvents, PushParams, PushPayload, PushRegisterOptions, PushRegistration, PushStatus } from "../contract"
|
|
8
|
+
|
|
9
|
+
/** What the halves register under. */
|
|
10
|
+
export const NAME = "push"
|
|
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
|
+
/** Permission + registration state. Never prompts. */
|
|
17
|
+
export const getStatus = (service: ServiceChannel<PushEvents>): Promise<PushStatus> =>
|
|
18
|
+
service._call("getStatus", [])
|
|
19
|
+
|
|
20
|
+
/** Ask for the permission (the first time) and register this device. Idempotent.
|
|
21
|
+
* Rejects with "denied" / "unavailable". */
|
|
22
|
+
export const register = (service: ServiceChannel<PushEvents>, options?: PushRegisterOptions): Promise<PushRegistration> =>
|
|
23
|
+
service._call("register", [options])
|
|
24
|
+
|
|
25
|
+
/** Remove this device's registration.
|
|
26
|
+
* Rejects with "unavailable". */
|
|
27
|
+
export const unregister = (service: ServiceChannel<PushEvents>): Promise<void> =>
|
|
28
|
+
service._call("unregister", [])
|
|
29
|
+
|
|
30
|
+
/** The notification that cold-started this world, or null. Stable across calls. */
|
|
31
|
+
export const getLaunch = (service: ServiceChannel<PushEvents>): Promise<PushPayload | null> =>
|
|
32
|
+
service._call("getLaunch", []).then((w: any) => w ?? null)
|
|
33
|
+
|
|
34
|
+
// ---- what the host's half has --------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
/** A method of the contract. */
|
|
37
|
+
export type Method = "getStatus" | "register" | "unregister" | "getLaunch"
|
|
38
|
+
|
|
39
|
+
/** Whether this host has the service "push" — and, given a method, a half new enough for it
|
|
40
|
+
* (a method added after the first version of the contract is tagged `@since`). */
|
|
41
|
+
export const supports = (method?: Method): boolean =>
|
|
42
|
+
ServiceChannel.supports(NAME) // every method is of version 1
|
|
43
|
+
|
|
44
|
+
// ---- the client -----------------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
/** The service "push" with the wire's methods as its own. */
|
|
47
|
+
export class PushBase extends ServiceChannel<PushEvents> {
|
|
48
|
+
constructor(params: PushParams = {}) { super("Push", NAME, params) }
|
|
49
|
+
|
|
50
|
+
/** Permission + registration state. Never prompts. */
|
|
51
|
+
getStatus(): Promise<PushStatus> { return getStatus(this) }
|
|
52
|
+
/** Ask for the permission (the first time) and register this device. Idempotent.
|
|
53
|
+
* Rejects with "denied" / "unavailable". */
|
|
54
|
+
register(options?: PushRegisterOptions): Promise<PushRegistration> { return register(this, options) }
|
|
55
|
+
/** Remove this device's registration.
|
|
56
|
+
* Rejects with "unavailable". */
|
|
57
|
+
unregister(): Promise<void> { return unregister(this) }
|
|
58
|
+
/** The notification that cold-started this world, or null. Stable across calls. */
|
|
59
|
+
getLaunch(): Promise<PushPayload | null> { return getLaunch(this) }
|
|
60
|
+
}
|