lecodes-sdk 2.4.1 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/types/core/Aspect.d.ts +34 -5
  2. package/dist/types/g2/OneWay2D.d.ts +0 -1
  3. package/dist/types/g2/Physics2D.d.ts +8 -6
  4. package/dist/types/g2/Scene2D.d.ts +1 -1
  5. package/dist/types/g2/Shape2D.d.ts +43 -15
  6. package/dist/types/gl/Model.d.ts +3 -2
  7. package/dist/types/gl/Scene.d.ts +1 -1
  8. package/dist/types/gl/animation/DynamicBone.d.ts +99 -35
  9. package/dist/types/gl/animation/IK.d.ts +0 -1
  10. package/dist/types/gl/nav/NavAgent.d.ts +3 -2
  11. package/dist/types/gl/physics/Physics.d.ts +4 -5
  12. package/dist/types/gl/physics/Shape.d.ts +40 -15
  13. package/dist/types/gl/vehicle/Vehicle.d.ts +54 -35
  14. package/dist/types/gl/vehicle/Wheel.d.ts +31 -10
  15. package/dist/types/plugins/gen/ar/contract.d.ts +152 -0
  16. package/dist/types/plugins/gen/ar/sdk/ar.d.ts +47 -21
  17. package/dist/types/plugins/gen/ar/sdk/ar.gen.d.ts +17 -3
  18. package/dist/types/plugins/gen/audio-recorder/contract.d.ts +64 -0
  19. package/dist/types/plugins/gen/browser/contract.d.ts +108 -0
  20. package/dist/types/plugins/gen/camera/contract.d.ts +27 -0
  21. package/dist/types/plugins/gen/geolocation/contract.d.ts +52 -0
  22. package/dist/types/plugins/gen/map/contract.d.ts +144 -0
  23. package/dist/types/plugins/gen/push/contract.d.ts +83 -0
  24. package/dist/types/plugins/gen/qr-scanner/contract.d.ts +16 -0
  25. package/dist/types/plugins/gen/speech/contract.d.ts +67 -0
  26. package/dist/types/plugins/gen/webrtc/contract.d.ts +342 -0
  27. package/dist/types/version.d.ts +1 -1
  28. package/dist/types.json +1 -1
  29. package/package.json +1 -1
  30. package/src/bridges/gl.d.ts +5 -3
  31. package/src/compile/compileProject.ts +45 -10
  32. package/src/compile/index.ts +7 -0
  33. package/src/compile/webHalf.ts +255 -0
  34. package/src/core/Aspect.ts +118 -35
  35. package/src/g2/OneWay2D.ts +0 -2
  36. package/src/g2/Physics2D.ts +8 -16
  37. package/src/g2/Scene2D.ts +1 -1
  38. package/src/g2/Shape2D.ts +51 -22
  39. package/src/gl/Model.ts +3 -2
  40. package/src/gl/Scene.ts +1 -1
  41. package/src/gl/animation/DynamicBone.ts +549 -486
  42. package/src/gl/animation/IK.ts +0 -2
  43. package/src/gl/nav/NavAgent.ts +14 -12
  44. package/src/gl/physics/Physics.ts +4 -5
  45. package/src/gl/physics/Shape.ts +49 -23
  46. package/src/gl/vehicle/Vehicle.ts +102 -58
  47. package/src/gl/vehicle/Wheel.ts +45 -11
  48. package/src/plugins/gen/ar/contract.d.ts +35 -1
  49. package/src/plugins/gen/ar/sdk/ar.gen.ts +25 -6
  50. package/src/plugins/gen/ar/sdk/ar.ts +102 -35
  51. package/src/version.ts +1 -1
@@ -0,0 +1,108 @@
1
+ // GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/browser/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
+ // browser — web content inside the app, on the two surfaces the platforms give:
4
+ // - the SYSTEM SHEET (the "browser" service): SFSafariViewController / ASWebAuthenticationSession on
5
+ // iOS, Chrome Custom Tabs on Android, a popup on the web. It shares the system browser's logins,
6
+ // passkeys and Apple Pay, and OAuth providers accept it. The app sees nothing of the page.
7
+ // - the EMBEDDED PAGE (the "webView" view): WKWebView / android.webkit.WebView / an iframe. A tree
8
+ // node the app embeds or presents fullscreen, with navigation events the app acts on.
9
+ //
10
+ // The hosts are dumb on purpose: they intercept a URL prefix and report it, forward the platform's
11
+ // callback URL, hand a non-http link to the system. Which URL ends a flow and what it means is the
12
+ // app's side (`Browser.open` in sdk/).
13
+
14
+ import type { Json, Service, View } from "../../../plugin"
15
+
16
+ // ---- the sheet ------------------------------------------------------------------------------------
17
+
18
+ export interface BrowserParams {}
19
+
20
+ export interface BrowserOpenOptions {
21
+ /** An auth flow: the scheme the provider redirects back to (`myshop` of `myshop://oauth`). iOS
22
+ * runs an ASWebAuthenticationSession and resolves `open` with the callback URL itself. Android
23
+ * and the web have nothing to do here: the redirect reaches the app as its "url" event. */
24
+ callbackScheme?: string
25
+ /** A private session: no cookies shared with the system browser. Honored on iOS (with a
26
+ * callbackScheme), ignored elsewhere. Default false. */
27
+ ephemeral?: boolean
28
+ /** The sheet's tint, "#RRGGBB". */
29
+ color?: string
30
+ }
31
+
32
+ export interface BrowserResult {
33
+ /** The callback URL the platform intercepted (iOS, with a callbackScheme); absent when the sheet
34
+ * was dismissed. */
35
+ url?: string
36
+ }
37
+
38
+ export interface BrowserEvents {
39
+ /** The sheet went away: the user dismissed it, the platform handed back the callback, or
40
+ * close(). */
41
+ closed: void
42
+ }
43
+
44
+ export interface Browser extends Service<"browser", BrowserParams, BrowserEvents> {
45
+ /** Show `url` in the system's in-app browser sheet. Resolves when the sheet is dismissed — with
46
+ * the callback URL when the platform intercepted one. One sheet at a time.
47
+ * @rejects busy invalid */
48
+ show(url: string, options?: BrowserOpenOptions): Promise<BrowserResult>
49
+ /** Dismiss the sheet; a no-op without one. */
50
+ dismiss(): Promise<void>
51
+ }
52
+
53
+ // ---- the embedded page ----------------------------------------------------------------------------
54
+
55
+ export interface WebViewParams {
56
+ /** What to load first. */
57
+ url?: string
58
+ /** Main-frame URLs that END a flow: a navigation whose URL starts with one is NOT loaded, the
59
+ * view emits `stop` instead. */
60
+ stops?: string[]
61
+ /** Install `window.lecodes.postMessage(data)` for the page (the `message` event) and let
62
+ * `postMessage` reach it (`window.lecodes.onMessage`). Default false — a user script disables
63
+ * Apple Pay in that WKWebView, so a checkout page wants it off. */
64
+ bridge?: boolean
65
+ /** A navigation to a non-http(s) URL — tel:, mailto:, intent:, a bank's app in a checkout:
66
+ * "open" hands it to the system, "block" drops it. Default "open". */
67
+ externalLinks?: "open" | "block"
68
+ userAgent?: string
69
+ /** A private session: no cookies or storage kept after the view goes. Default false. */
70
+ ephemeral?: boolean
71
+ }
72
+
73
+ export interface WebViewEvents {
74
+ /** A main-frame navigation started. */
75
+ navigate: { url: string }
76
+ /** The page finished loading. */
77
+ load: { url: string, title: string }
78
+ /** The page failed to load. */
79
+ error: { url: string, message: string }
80
+ /** A stop URL was reached: the navigation was cancelled, the page did not load. */
81
+ stop: { url: string }
82
+ /** The page called `window.lecodes.postMessage(data)` (with `bridge`). */
83
+ message: { data: Json }
84
+ /** The page called `window.close()`. */
85
+ exit: void
86
+ }
87
+
88
+ export interface WebView extends View<"webView", WebViewParams, WebViewEvents> {
89
+ /** Load `url`, with extra request headers for the first request. @rejects invalid */
90
+ load(url: string, headers?: { [name: string]: string }): Promise<void>
91
+ /** Show `html` as the page; relative URLs in it resolve against `baseUrl`. */
92
+ loadHtml(html: string, baseUrl?: string): Promise<void>
93
+ /** Replace the stop list (see WebViewParams.stops). */
94
+ setStops(stops: string[]): Promise<void>
95
+ reload(): Promise<void>
96
+ /** Stop the current load. */
97
+ stopLoading(): Promise<void>
98
+ goBack(): Promise<void>
99
+ goForward(): Promise<void>
100
+ canGoBack(): Promise<boolean>
101
+ /** Run `js` in the page and return what it evaluates to, as JSON. On iOS this disables Apple Pay
102
+ * in the view until its next navigation. @rejects failed */
103
+ evaluate(js: string): Promise<Json>
104
+ /** Deliver `data` to the page's `window.lecodes.onMessage(data)` (with `bridge`). */
105
+ postMessage(data: Json): Promise<void>
106
+ /** Clear the cookies and the storage of this view's session. */
107
+ clearData(): Promise<void>
108
+ }
@@ -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,52 @@
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 is the calls' (`@permission location`): the
8
+ // host asks before the first call that needs the position, the halves never do.
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
+ * @permission location
44
+ * @rejects unavailable timeout */
45
+ getCurrent(options?: GeoOptions): Promise<GeoPosition>
46
+ /** Start the session's watch; resolves once it is live (the provider started).
47
+ * A second call while watching resolves at once and keeps the first call's options.
48
+ * @permission location
49
+ * @rejects unavailable */
50
+ startWatch(options?: GeoOptions): Promise<void>
51
+ stopWatch(): Promise<void>
52
+ }
@@ -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,83 @@
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
+ // A notification reaches the JS world through this channel ALONE: a tap is the "tap" event (or
12
+ // `getLaunch()` for the one that started the process — the SDK's wrapper replays it into the first
13
+ // "tap" listener), a foreground arrival is "message". Its `url` is the app's to route — it does NOT
14
+ // ride the app's deep-link path (`app.launchUrl`, the "url" event).
15
+
16
+ import type { Json, Service, i32 } from "../../../plugin"
17
+
18
+ /** A delivered notification, as the app sees it. */
19
+ export interface PushPayload {
20
+ /** The push channel it was sent through — the id of the `pushChannel()` export of the project's
21
+ * server (`<path>#<name>`). Absent = sent outside one (a push key, `lecodes pn send`). */
22
+ channel?: string
23
+ title: string
24
+ body?: string
25
+ /** A deep link, for the app to open itself (it does not ride `app.launchUrl`). */
26
+ url?: string
27
+ /** The developer's own JSON object, ≤ 2 KB serialized. */
28
+ data?: { [key: string]: Json }
29
+ badge?: i32
30
+ }
31
+
32
+ export interface PushStatus {
33
+ /** The OS permission of the host app. "prompt" = not asked yet. */
34
+ permission: "granted" | "denied" | "prompt"
35
+ /** Whether this device holds a registration (an address) for this app. */
36
+ registered: boolean
37
+ /** The device's address for this app, when it is registered. @since 2 */
38
+ address?: string
39
+ }
40
+
41
+ export interface PushRegisterOptions {
42
+ /** The app's OWN user id. Client-asserted and unverified. The registration reflects the latest
43
+ * call: registering without it clears it. */
44
+ user?: string
45
+ }
46
+
47
+ export interface PushRegistration {
48
+ address: string
49
+ }
50
+
51
+ /** What the app shows itself when a notification arrives while it is in the foreground (the
52
+ * "message" event): the host shows the system banner for every other one. */
53
+ export interface PushHandled {
54
+ /** The push channels with a foreground handler. */
55
+ channels: string[]
56
+ /** Whether notifications sent outside a channel have one. */
57
+ untyped: boolean
58
+ }
59
+
60
+ export interface PushParams {}
61
+
62
+ export interface PushEvents {
63
+ /** Arrived while this app was in the foreground and the app handles it (PushHandled): no banner. */
64
+ message: PushPayload
65
+ /** Tapped while the world was alive. */
66
+ tap: PushPayload
67
+ }
68
+
69
+ export interface Push extends Service<"push", PushParams, PushEvents> {
70
+ /** Permission + registration state. Never prompts. */
71
+ getStatus(): Promise<PushStatus>
72
+ /** Ask for the permission (the first time) and register this device. Idempotent.
73
+ * @rejects denied unavailable */
74
+ register(options?: PushRegisterOptions): Promise<PushRegistration>
75
+ /** Remove this device's registration.
76
+ * @rejects unavailable */
77
+ unregister(): Promise<void>
78
+ /** The notification that cold-started this world, or null. Stable across calls. */
79
+ getLaunch(): Promise<PushPayload | null>
80
+ /** Which notifications the app handles itself in the foreground; the rest get the system banner.
81
+ * Until it is called nothing is handled. @since 2 */
82
+ setMessageHandled(handled: PushHandled): Promise<void>
83
+ }
@@ -0,0 +1,16 @@
1
+ // GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/qr-scanner/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
+ // qr-scanner — a camera view that decodes QR codes.
4
+ // A view with events only: the app opens / embeds it and listens.
5
+
6
+ import type { View } from "../../../plugin"
7
+
8
+ export interface QRScannerParams {}
9
+
10
+ export interface QRScannerEvents {
11
+ /** One per decoded camera frame: the payload, or null for a frame with no readable code (it
12
+ * repeats while the user aims). The same code may be reported more than once. */
13
+ scan: { data: string | null }
14
+ }
15
+
16
+ export interface QRScanner extends View<"qrScanner", QRScannerParams, QRScannerEvents> {}
@@ -0,0 +1,67 @@
1
+ // GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/speech/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
+ // speech — speech recognition: the microphone to text, live (dictation, voice commands).
4
+ // A session is ONE listening (the app-facing `SpeechRecognizer` opens one per `start()`): results
5
+ // arrive as events, partial ones while the user speaks and a final one after, then `end`. The
6
+ // permissions are the contract's (`@permission speech microphone` on start): the host asks, the
7
+ // halves never do.
8
+ //
9
+ // The recognizer is the platform's: Apple's (SFSpeechRecognizer: on-device where the language
10
+ // allows, else Apple's servers), Google's on Android (the device's RecognitionService — absent on a
11
+ // phone without Google's app: `start` rejects "unavailable"), the browser's on the web (Chrome sends
12
+ // the audio to Google; Safari to Apple). Languages and quality differ with them.
13
+
14
+ import type { Service, f64 } from "../../../plugin"
15
+
16
+ export interface SpeechOptions {
17
+ /** BCP-47 — "en-US", "ru-RU". Default: the device's language. */
18
+ language?: string
19
+ /** Keep listening and delivering results until `stop()`; default false: one utterance — the
20
+ * listening ends after a pause in the speech. */
21
+ continuous?: boolean
22
+ /** Deliver partial results while the user speaks (`isFinal` false). Default true. */
23
+ interimResults?: boolean
24
+ /** Recognize on the device only — no audio leaves it; fewer languages, and "unavailable" where
25
+ * the device cannot. Default false. The web ignores it. */
26
+ onDevice?: boolean
27
+ }
28
+
29
+ export interface SpeechResult {
30
+ /** The text so far (a partial), or of the utterance (final). */
31
+ transcript: string
32
+ isFinal: boolean
33
+ /** 0..1 when the recognizer says; null when it does not (partials, Android). */
34
+ confidence: f64 | null
35
+ }
36
+
37
+ export type SpeechErrorCode = "noSpeech" | "audio" | "network" | "denied" | "aborted" | "unavailable" | "failed"
38
+
39
+ export interface SpeechParams {}
40
+
41
+ export interface SpeechEvents {
42
+ /** A result: partials while speaking (when `interimResults`), then the final one. */
43
+ result: SpeechResult
44
+ /** The user started speaking. */
45
+ speechStart: void
46
+ /** The user stopped speaking (a pause): the final result follows. */
47
+ speechEnd: void
48
+ /** The listening is over — after the final result, after `stop()` / `abort()`, after an error. */
49
+ end: void
50
+ /** The listening failed; `end` follows. "noSpeech": nothing was heard (not continuous). */
51
+ error: { code: SpeechErrorCode, message: string }
52
+ }
53
+
54
+ export interface Speech extends Service<"speech", SpeechParams, SpeechEvents> {
55
+ /** Start listening. One listening per session: a second start rejects "busy". "unavailable":
56
+ * no recognizer on this device, or none for the language / on the device.
57
+ * @permission speech microphone
58
+ * @rejects busy unavailable failed */
59
+ start(options?: SpeechOptions): Promise<void>
60
+ /** Stop listening and finish: the final result of what was heard, then `end`. Not listening is
61
+ * not an error. */
62
+ stop(): Promise<void>
63
+ /** Stop and drop what was heard: `end`, no result. */
64
+ abort(): Promise<void>
65
+ /** The languages this host recognizes, BCP-47; empty when the host cannot say (the web). */
66
+ supportedLanguages(): Promise<string[]>
67
+ }