lecodes-sdk 2.2.6 → 2.2.8
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/dist/global.d.ts +40 -0
- package/dist/types/inject.d.ts +2 -0
- package/dist/types/plugin.d.ts +5 -0
- package/dist/types/plugins/gen/browser/sdk/browser.Browser.gen.d.ts +27 -0
- package/dist/types/plugins/gen/browser/sdk/browser.WebView.gen.d.ts +61 -0
- package/dist/types/plugins/gen/browser/sdk/browser.d.ts +71 -0
- package/dist/types/plugins/gen/webrtc/sdk/webrtc.RtcMedia.gen.d.ts +55 -0
- package/dist/types/plugins/gen/webrtc/sdk/webrtc.RtcPeerConnection.gen.d.ts +121 -0
- package/dist/types/plugins/gen/webrtc/sdk/webrtc.RtcVideo.gen.d.ts +25 -0
- package/dist/types/plugins/gen/webrtc/sdk/webrtc.d.ts +328 -0
- package/dist/types/version.d.ts +1 -1
- package/dist/types.json +1 -1
- package/package.json +1 -1
- package/src/chisel.ts +2 -2
- package/src/inject.ts +8 -0
- package/src/plugin.ts +13 -0
- package/src/plugins/gen/browser/contract.d.ts +108 -0
- package/src/plugins/gen/browser/sdk/browser.Browser.gen.ts +48 -0
- package/src/plugins/gen/browser/sdk/browser.WebView.gen.ts +96 -0
- package/src/plugins/gen/browser/sdk/browser.ts +218 -0
- package/src/plugins/gen/webrtc/contract.d.ts +342 -0
- package/src/plugins/gen/webrtc/sdk/webrtc.RtcMedia.gen.ts +86 -0
- package/src/plugins/gen/webrtc/sdk/webrtc.RtcPeerConnection.gen.ts +184 -0
- package/src/plugins/gen/webrtc/sdk/webrtc.RtcVideo.gen.ts +48 -0
- package/src/plugins/gen/webrtc/sdk/webrtc.ts +760 -0
- package/src/runtime/appEvents.ts +9 -0
- package/src/version.ts +1 -1
package/package.json
CHANGED
package/src/chisel.ts
CHANGED
|
@@ -118,7 +118,7 @@ export const chiselVocab: ChiselVocab = {
|
|
|
118
118
|
},
|
|
119
119
|
leaves: [
|
|
120
120
|
"UIText", "UIImage", "UIVideo", "UIInput", "UITextArea", "UISpacer",
|
|
121
|
-
"UITabs", "UIVirtualizedList", "NativeView", "CameraView", "MapView", "QRScanner",
|
|
121
|
+
"UITabs", "UIVirtualizedList", "NativeView", "CameraView", "MapView", "QRScanner", "WebView", "RTCVideoView",
|
|
122
122
|
],
|
|
123
123
|
containers: [ "UIColumn", "UIRow", "UIBox", "UIButton", "UIScrollable", "UIScreen", "UIWidget" ],
|
|
124
124
|
text: "UIText",
|
|
@@ -133,7 +133,7 @@ export const chiselVocab: ChiselVocab = {
|
|
|
133
133
|
"append", "insert", "remove", "setContent", "widgets", "keepAlive", "url",
|
|
134
134
|
"detents", "dismissible", "transition", "setSourceRect",
|
|
135
135
|
"push", "pop", "popToRoot", "replace", "select",
|
|
136
|
-
// UITabs / UIVirtualizedList's hand feed / the native views (NativeView, CameraView, MapView, QRScanner)
|
|
136
|
+
// UITabs / UIVirtualizedList's hand feed / the native views (NativeView, CameraView, MapView, QRScanner, WebView, RTCVideoView)
|
|
137
137
|
"badge", "setData", "update", "prepend", "removeByKey",
|
|
138
138
|
"on", "off", "onReady", "onError", "onTap", "onMove", "onScan",
|
|
139
139
|
"flyTo", "jumpTo", "fitPoints", "setPadding", "setUserLocation",
|
package/src/inject.ts
CHANGED
|
@@ -151,6 +151,14 @@ export {
|
|
|
151
151
|
type LineLayer, type LineLayerOptions, type UserLocationOptions,
|
|
152
152
|
} from "./plugins/gen/map/sdk/map"
|
|
153
153
|
export { Push, type PushPayload, type PushStatus, type PushRegisterOptions, type PushEvent } from "./plugins/gen/push/sdk/push"
|
|
154
|
+
export { Browser, WebView, type BrowserOpenOptions, type WebViewParams, type WebViewEvents } from "./plugins/gen/browser/sdk/browser"
|
|
155
|
+
export {
|
|
156
|
+
RTCPeerConnection, MediaStream, MediaStreamTrack, RTCDataChannel, RTCRtpSender, RTCRtpReceiver, RTCRtpTransceiver,
|
|
157
|
+
RTCSessionDescription, RTCIceCandidate, RTCEventTarget, mediaDevices, RTCVideoView,
|
|
158
|
+
type RTCConfiguration, type RTCVideoViewOptions, type MediaStreamConstraints, type RTCIceCandidateInit,
|
|
159
|
+
type RTCPeerConnectionIceEvent, type RTCTrackEvent, type RTCDataChannelEvent, type RTCMessageEvent, type RTCErrorEvent, type RTCEvent,
|
|
160
|
+
type RTCEventListener,
|
|
161
|
+
} from "./plugins/gen/webrtc/sdk/webrtc"
|
|
154
162
|
export { Service } from "./plugins/service"
|
|
155
163
|
|
|
156
164
|
// ---- scenes as data (.scene.ts files — see src/scene/defineScene.ts) ----
|
package/src/plugin.ts
CHANGED
|
@@ -13,8 +13,13 @@ import { ServiceClient } from "./runtime/service"
|
|
|
13
13
|
import { NativeView, NativeViewElement } from "./ui/NativeView"
|
|
14
14
|
import { _navEpoch } from "./ui/presentable"
|
|
15
15
|
import { _requestCameraPermission } from "./plugins/permission"
|
|
16
|
+
import { appEventsOff, appEventsOn } from "./runtime/appEvents"
|
|
16
17
|
|
|
17
18
|
export type { PresentOptions } from "./ui/presentable"
|
|
19
|
+
// The SDK UI a wrapper builds its own chrome with (the browser plugin's bar over an embedded page) and
|
|
20
|
+
// the navigation it presents through. Re-exported, never copied: chisel keys on these names.
|
|
21
|
+
export { Presentable, Router, UIBox, UIButton, UIColumn, UIRow, UIScreen, UIText } from "./ui/UI"
|
|
22
|
+
export { theme } from "./ui/theme"
|
|
18
23
|
export type { NativeView } from "./ui/NativeView"
|
|
19
24
|
export type { FetchResponse } from "./runtime/fetch"
|
|
20
25
|
|
|
@@ -172,6 +177,14 @@ export const _fromFile = (file: File): { systemId: number, name: string, size: n
|
|
|
172
177
|
|
|
173
178
|
// ---- for the hand-written part ---------------------------------------------------------------------
|
|
174
179
|
|
|
180
|
+
/** Hear the app's "url" events (a deep link while the app runs — an OAuth redirect, a checkout's
|
|
181
|
+
* return) until the returned function is called. The listener the `app` global fans out from. */
|
|
182
|
+
export const _onAppUrl = (callback: (url: string) => void): (() => void) => {
|
|
183
|
+
const listener = (data?: string): void => { if (data) callback(data) }
|
|
184
|
+
appEventsOn("url", listener)
|
|
185
|
+
return () => appEventsOff("url", listener)
|
|
186
|
+
}
|
|
187
|
+
|
|
175
188
|
/** The text of a host buffer — what a bundled `asset()` is in a shell compile (`"id:N"`): the host
|
|
176
189
|
* already holds the bytes. Empty when there is no such buffer. */
|
|
177
190
|
export const bufferText = (systemId: number): string => _creatorFetch.toText(systemId)
|
|
@@ -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,48 @@
|
|
|
1
|
+
// GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/browser/sdk/browser.Browser.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
|
+
// browser: the service "browser", contract version 1.
|
|
5
|
+
|
|
6
|
+
import { ServiceChannel } from "../../../../plugin"
|
|
7
|
+
import type { BrowserEvents, BrowserOpenOptions, BrowserParams, BrowserResult } from "../contract"
|
|
8
|
+
|
|
9
|
+
/** What the halves register under. */
|
|
10
|
+
export const NAME = "browser"
|
|
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
|
+
/** Show `url` in the system's in-app browser sheet. Resolves when the sheet is dismissed — with
|
|
17
|
+
* the callback URL when the platform intercepted one. One sheet at a time.
|
|
18
|
+
* Rejects with "busy" / "invalid". */
|
|
19
|
+
export const show = (service: ServiceChannel<BrowserEvents>, url: string, options?: BrowserOpenOptions): Promise<BrowserResult> =>
|
|
20
|
+
service._call("show", [url, options])
|
|
21
|
+
|
|
22
|
+
/** Dismiss the sheet; a no-op without one. */
|
|
23
|
+
export const dismiss = (service: ServiceChannel<BrowserEvents>): Promise<void> =>
|
|
24
|
+
service._call("dismiss", [])
|
|
25
|
+
|
|
26
|
+
// ---- what the host's half has --------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
/** A method of the contract. */
|
|
29
|
+
export type Method = "show" | "dismiss"
|
|
30
|
+
|
|
31
|
+
/** Whether this host has the service "browser" — and, given a method, a half new enough for it
|
|
32
|
+
* (a method added after the first version of the contract is tagged `@since`). */
|
|
33
|
+
export const supports = (method?: Method): boolean =>
|
|
34
|
+
ServiceChannel.supports(NAME) // every method is of version 1
|
|
35
|
+
|
|
36
|
+
// ---- the client -----------------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
/** The service "browser" with the wire's methods as its own. */
|
|
39
|
+
export class BrowserBase extends ServiceChannel<BrowserEvents> {
|
|
40
|
+
constructor(params: BrowserParams = {}) { super("Browser", NAME, params) }
|
|
41
|
+
|
|
42
|
+
/** Show `url` in the system's in-app browser sheet. Resolves when the sheet is dismissed — with
|
|
43
|
+
* the callback URL when the platform intercepted one. One sheet at a time.
|
|
44
|
+
* Rejects with "busy" / "invalid". */
|
|
45
|
+
show(url: string, options?: BrowserOpenOptions): Promise<BrowserResult> { return show(this, url, options) }
|
|
46
|
+
/** Dismiss the sheet; a no-op without one. */
|
|
47
|
+
dismiss(): Promise<void> { return dismiss(this) }
|
|
48
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/browser/sdk/browser.WebView.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
|
+
// browser: the view "webView", contract version 1.
|
|
5
|
+
|
|
6
|
+
import { ViewChannel, type Json } from "../../../../plugin"
|
|
7
|
+
import type { WebViewEvents, WebViewParams } from "../contract"
|
|
8
|
+
|
|
9
|
+
/** What the halves register under. */
|
|
10
|
+
export const NAME = "webView"
|
|
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
|
+
/** Load `url`, with extra request headers for the first request.
|
|
17
|
+
* Rejects with "invalid". */
|
|
18
|
+
export const load = (view: ViewChannel<WebViewEvents>, url: string, headers?: { [key: string]: string }): Promise<void> =>
|
|
19
|
+
view._call("load", [url, headers])
|
|
20
|
+
|
|
21
|
+
/** Show `html` as the page; relative URLs in it resolve against `baseUrl`. */
|
|
22
|
+
export const loadHtml = (view: ViewChannel<WebViewEvents>, html: string, baseUrl?: string): Promise<void> =>
|
|
23
|
+
view._call("loadHtml", [html, baseUrl])
|
|
24
|
+
|
|
25
|
+
/** Replace the stop list (see WebViewParams.stops). */
|
|
26
|
+
export const setStops = (view: ViewChannel<WebViewEvents>, stops: string[]): Promise<void> =>
|
|
27
|
+
view._call("setStops", [stops])
|
|
28
|
+
|
|
29
|
+
export const reload = (view: ViewChannel<WebViewEvents>): Promise<void> =>
|
|
30
|
+
view._call("reload", [])
|
|
31
|
+
|
|
32
|
+
/** Stop the current load. */
|
|
33
|
+
export const stopLoading = (view: ViewChannel<WebViewEvents>): Promise<void> =>
|
|
34
|
+
view._call("stopLoading", [])
|
|
35
|
+
|
|
36
|
+
export const goBack = (view: ViewChannel<WebViewEvents>): Promise<void> =>
|
|
37
|
+
view._call("goBack", [])
|
|
38
|
+
|
|
39
|
+
export const goForward = (view: ViewChannel<WebViewEvents>): Promise<void> =>
|
|
40
|
+
view._call("goForward", [])
|
|
41
|
+
|
|
42
|
+
export const canGoBack = (view: ViewChannel<WebViewEvents>): Promise<boolean> =>
|
|
43
|
+
view._call("canGoBack", [])
|
|
44
|
+
|
|
45
|
+
/** Run `js` in the page and return what it evaluates to, as JSON. On iOS this disables Apple Pay
|
|
46
|
+
* in the view until its next navigation.
|
|
47
|
+
* Rejects with "failed". */
|
|
48
|
+
export const evaluate = (view: ViewChannel<WebViewEvents>, js: string): Promise<Json> =>
|
|
49
|
+
view._call("evaluate", [js])
|
|
50
|
+
|
|
51
|
+
/** Deliver `data` to the page's `window.lecodes.onMessage(data)` (with `bridge`). */
|
|
52
|
+
export const postMessage = (view: ViewChannel<WebViewEvents>, data: Json): Promise<void> =>
|
|
53
|
+
view._call("postMessage", [data])
|
|
54
|
+
|
|
55
|
+
/** Clear the cookies and the storage of this view's session. */
|
|
56
|
+
export const clearData = (view: ViewChannel<WebViewEvents>): Promise<void> =>
|
|
57
|
+
view._call("clearData", [])
|
|
58
|
+
|
|
59
|
+
// ---- what the host's half has --------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
/** A method of the contract. */
|
|
62
|
+
export type Method = "load" | "loadHtml" | "setStops" | "reload" | "stopLoading" | "goBack" | "goForward" | "canGoBack" | "evaluate" | "postMessage" | "clearData"
|
|
63
|
+
|
|
64
|
+
/** Whether this host has the view "webView" — and, given a method, a half new enough for it
|
|
65
|
+
* (a method added after the first version of the contract is tagged `@since`). */
|
|
66
|
+
export const supports = (method?: Method): boolean =>
|
|
67
|
+
ViewChannel.supports(NAME) // every method is of version 1
|
|
68
|
+
|
|
69
|
+
// ---- the element ----------------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
/** The view "webView" with the wire's methods as its own. */
|
|
72
|
+
export class WebViewBase extends ViewChannel<WebViewEvents> {
|
|
73
|
+
constructor(params: WebViewParams = {}) { super("WebView", NAME, params) }
|
|
74
|
+
|
|
75
|
+
/** Load `url`, with extra request headers for the first request.
|
|
76
|
+
* Rejects with "invalid". */
|
|
77
|
+
load(url: string, headers?: { [key: string]: string }): Promise<void> { return load(this, url, headers) }
|
|
78
|
+
/** Show `html` as the page; relative URLs in it resolve against `baseUrl`. */
|
|
79
|
+
loadHtml(html: string, baseUrl?: string): Promise<void> { return loadHtml(this, html, baseUrl) }
|
|
80
|
+
/** Replace the stop list (see WebViewParams.stops). */
|
|
81
|
+
setStops(stops: string[]): Promise<void> { return setStops(this, stops) }
|
|
82
|
+
reload(): Promise<void> { return reload(this) }
|
|
83
|
+
/** Stop the current load. */
|
|
84
|
+
stopLoading(): Promise<void> { return stopLoading(this) }
|
|
85
|
+
goBack(): Promise<void> { return goBack(this) }
|
|
86
|
+
goForward(): Promise<void> { return goForward(this) }
|
|
87
|
+
canGoBack(): Promise<boolean> { return canGoBack(this) }
|
|
88
|
+
/** Run `js` in the page and return what it evaluates to, as JSON. On iOS this disables Apple Pay
|
|
89
|
+
* in the view until its next navigation.
|
|
90
|
+
* Rejects with "failed". */
|
|
91
|
+
evaluate(js: string): Promise<Json> { return evaluate(this, js) }
|
|
92
|
+
/** Deliver `data` to the page's `window.lecodes.onMessage(data)` (with `bridge`). */
|
|
93
|
+
postMessage(data: Json): Promise<void> { return postMessage(this, data) }
|
|
94
|
+
/** Clear the cookies and the storage of this view's session. */
|
|
95
|
+
clearData(): Promise<void> { return clearData(this) }
|
|
96
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/browser/sdk/browser.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.open — a URL on the surface the scenario needs, ONE verb for the three of them:
|
|
4
|
+
//
|
|
5
|
+
// await Browser.open(url) a page, in the system sheet
|
|
6
|
+
// const back = await Browser.open(authUrl, { returnUrl: "myshop://oauth" }) OAuth: the sheet,
|
|
7
|
+
// resolved with the redirect URL
|
|
8
|
+
// const back = await Browser.open(checkoutUrl, { returnUrl: [paid, failed], embedded: true })
|
|
9
|
+
// a checkout in an embedded page,
|
|
10
|
+
// resolved with the URL it reached
|
|
11
|
+
//
|
|
12
|
+
// It resolves with the return URL reached, or null when the user left. `returnUrl` says what ENDS
|
|
13
|
+
// the page; `embedded` says WHERE it runs — the system sheet (shared logins, passkeys, Apple Pay;
|
|
14
|
+
// the only surface OAuth providers accept) or a WebView presented fullscreen under a bar the SDK
|
|
15
|
+
// draws (an https return without verified app links, a 3DS flow, no system chrome).
|
|
16
|
+
//
|
|
17
|
+
// Hand-written over the generated wire because the flows are the SDK's, not the hosts':
|
|
18
|
+
// - the sheet's return is ONE of two paths — iOS intercepts the scheme itself and resolves `show`
|
|
19
|
+
// with the URL, Android and the web deliver the redirect as the app's "url" event. The wrapper
|
|
20
|
+
// races both and the host does nothing it would not do for a plain deep link;
|
|
21
|
+
// - the embedded flow is a WebView with the return URLs as its `stops`, inside a screen with a
|
|
22
|
+
// close bar, pushed through the Router when one is showing and opened directly otherwise.
|
|
23
|
+
//
|
|
24
|
+
// WebView() is the element itself: embed it among a screen's children, push it with your own
|
|
25
|
+
// chrome, drive it (`load`, `evaluate`, `postMessage`) and listen (`navigate`, `load`, `stop`,
|
|
26
|
+
// `message`).
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
Presentable, Router, UIBox, UIButton, UIRow, UIScreen, UIText, theme, _onAppUrl, type Json, type Listener, type NativeView,
|
|
30
|
+
} from "../../../../plugin"
|
|
31
|
+
import { BrowserBase, NAME as SHEET_NAME } from "./browser.Browser.gen"
|
|
32
|
+
import { WebViewBase, NAME as VIEW_NAME, supports as viewSupports, type Method as WebViewMethod } from "./browser.WebView.gen"
|
|
33
|
+
import type { WebViewEvents, WebViewParams } from "../contract"
|
|
34
|
+
|
|
35
|
+
export type { WebViewEvents, WebViewParams } from "../contract"
|
|
36
|
+
|
|
37
|
+
export interface BrowserOpenOptions {
|
|
38
|
+
/** What ends the page: a URL prefix, or several (a checkout's success and failure URLs). The
|
|
39
|
+
* promise resolves with the URL reached. In the sheet every prefix must share one scheme — the
|
|
40
|
+
* platform intercepts one; a custom scheme needs app.json `urlScheme`, an https one the app's
|
|
41
|
+
* verified links. */
|
|
42
|
+
returnUrl?: string | string[]
|
|
43
|
+
/** Run the page in an embedded WebView under a close bar instead of the system sheet. The
|
|
44
|
+
* WebView intercepts any return URL itself, https included. */
|
|
45
|
+
embedded?: boolean
|
|
46
|
+
/** A private session: no cookies shared with the system browser, nothing kept after. */
|
|
47
|
+
ephemeral?: boolean
|
|
48
|
+
/** The sheet's tint, "#RRGGBB". */
|
|
49
|
+
color?: string
|
|
50
|
+
/** Embedded: the bar's title until the page reports its own. */
|
|
51
|
+
title?: string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The embedded page — the element of the "webView" view: embeddable among a screen's children,
|
|
55
|
+
* presentable (`open()`, `Router.push`), with the page's controls and events. */
|
|
56
|
+
export interface WebView extends NativeView {
|
|
57
|
+
/** Load `url`, with extra request headers for the first request. Rejects "invalid" for a URL
|
|
58
|
+
* that is not a web one. */
|
|
59
|
+
load(url: string, headers?: { [name: string]: string }): Promise<void>
|
|
60
|
+
/** Show `html` as the page; relative URLs in it resolve against `baseUrl`. */
|
|
61
|
+
loadHtml(html: string, baseUrl?: string): Promise<void>
|
|
62
|
+
/** Replace the URL prefixes that end a navigation with the `stop` event. */
|
|
63
|
+
setStops(stops: string[]): Promise<void>
|
|
64
|
+
reload(): Promise<void>
|
|
65
|
+
/** Stop the current load. */
|
|
66
|
+
stopLoading(): Promise<void>
|
|
67
|
+
goBack(): Promise<void>
|
|
68
|
+
goForward(): Promise<void>
|
|
69
|
+
canGoBack(): Promise<boolean>
|
|
70
|
+
/** Run `js` in the page and get what it evaluates to, as JSON. On iOS this disables Apple Pay
|
|
71
|
+
* in the view until its next navigation. */
|
|
72
|
+
evaluate(js: string): Promise<Json>
|
|
73
|
+
/** Deliver `data` to the page's `window.lecodes.onMessage(data)` (a view created with `bridge`). */
|
|
74
|
+
postMessage(data: Json): Promise<void>
|
|
75
|
+
/** Clear the cookies and the storage of this view's session. */
|
|
76
|
+
clearData(): Promise<void>
|
|
77
|
+
/** The page's events: `navigate`, `load`, `error`, `stop`, `message`, `exit`. */
|
|
78
|
+
on<K extends keyof WebViewEvents & string>(event: K, callback: Listener<WebViewEvents, K>): this
|
|
79
|
+
off<K extends keyof WebViewEvents & string>(event: K, callback: Listener<WebViewEvents, K>): this
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const returnUrls = (options: BrowserOpenOptions): string[] =>
|
|
83
|
+
options.returnUrl === undefined ? [] : Array.isArray(options.returnUrl) ? options.returnUrl : [options.returnUrl]
|
|
84
|
+
|
|
85
|
+
const matching = (url: string, prefixes: string[]): string | undefined =>
|
|
86
|
+
prefixes.some(p => url.startsWith(p)) ? url : undefined
|
|
87
|
+
|
|
88
|
+
const schemeOf = (url: string): string => {
|
|
89
|
+
const m = /^([a-z][a-z0-9+.-]*):/i.exec(url)
|
|
90
|
+
return m ? m[1]!.toLowerCase() : ""
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const unsupported = (what: string, channel: string): Error =>
|
|
94
|
+
new Error(`${what} isn't available on this host — it needs the "browser" plugin (app.json "plugins"), a registered "${channel}" ${channel === SHEET_NAME ? "service" : "view"}.`)
|
|
95
|
+
|
|
96
|
+
// ---- the sheet -------------------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
let sheet: BrowserBase | null = null
|
|
99
|
+
const service = (): BrowserBase => sheet ??= new BrowserBase()
|
|
100
|
+
|
|
101
|
+
const openSheet = (url: string, options: BrowserOpenOptions): Promise<string | null> => new Promise((resolve, reject) => {
|
|
102
|
+
if (!BrowserBase.supports(SHEET_NAME)) return reject(unsupported("Browser.open", SHEET_NAME))
|
|
103
|
+
const stops = returnUrls(options)
|
|
104
|
+
const schemes = new Set(stops.map(schemeOf))
|
|
105
|
+
if (schemes.size > 1) return reject(new Error("Browser.open: every returnUrl must share one scheme — the platform intercepts one."))
|
|
106
|
+
const scheme = stops.length ? [...schemes][0]! : ""
|
|
107
|
+
const wire: { callbackScheme?: string, ephemeral?: boolean, color?: string } = {}
|
|
108
|
+
// A custom scheme is what a platform can intercept; an https return is the app's verified link,
|
|
109
|
+
// which reaches it as a "url" event like any deep link.
|
|
110
|
+
if (scheme && scheme !== "http" && scheme !== "https") wire.callbackScheme = scheme
|
|
111
|
+
if (options.ephemeral) wire.ephemeral = true
|
|
112
|
+
if (options.color) wire.color = options.color
|
|
113
|
+
|
|
114
|
+
let done = false
|
|
115
|
+
const off = stops.length ? _onAppUrl(back => {
|
|
116
|
+
if (!matching(back, stops)) return
|
|
117
|
+
finish(back)
|
|
118
|
+
service().dismiss().catch(() => {})
|
|
119
|
+
}) : () => {}
|
|
120
|
+
const finish = (back: string | null): void => {
|
|
121
|
+
if (done) return
|
|
122
|
+
done = true
|
|
123
|
+
off()
|
|
124
|
+
resolve(back)
|
|
125
|
+
}
|
|
126
|
+
service().show(url, wire).then(
|
|
127
|
+
result => finish(result?.url ?? null),
|
|
128
|
+
error => { if (done) return; done = true; off(); reject(error) },
|
|
129
|
+
)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
// ---- the embedded page ----------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
const openEmbedded = (url: string, options: BrowserOpenOptions): Promise<string | null> => new Promise((resolve, reject) => {
|
|
135
|
+
if (!WebViewBase.supports(VIEW_NAME)) return reject(unsupported("Browser.open({ embedded: true })", VIEW_NAME))
|
|
136
|
+
const stops = returnUrls(options)
|
|
137
|
+
const params: WebViewParams = { url, stops }
|
|
138
|
+
if (options.ephemeral) params.ephemeral = true
|
|
139
|
+
const view = new WebViewBase(params)
|
|
140
|
+
|
|
141
|
+
const title = UIText(options.title ?? "")
|
|
142
|
+
.style({ flex: 1, fontSize: 16, fontWeight: "600", color: theme.text, textAlign: "center", lineClamp: 1 })
|
|
143
|
+
const close = UIButton(UIText("✕").style({ fontSize: 20, color: theme.text }))
|
|
144
|
+
.style({ width: 44, height: 44, alignItems: "center", justifyContent: "center" })
|
|
145
|
+
const bar = UIRow(close, title, UIBox().style({ width: 44 }))
|
|
146
|
+
.style({ pt: "safe-top", alignItems: "center", bgColor: theme.surface, borderBottomWidth: 1, borderColor: theme.border })
|
|
147
|
+
const screen = UIScreen(bar, view.style({ flex: 1 })).style({ bgColor: theme.bg })
|
|
148
|
+
|
|
149
|
+
// Where it goes: onto the Router when its page is what is on screen, else in place of what is —
|
|
150
|
+
// and back to it after.
|
|
151
|
+
const previous = Presentable.current
|
|
152
|
+
const routed = previous !== null && previous === Router.current
|
|
153
|
+
let left = false
|
|
154
|
+
const leave = (): void => {
|
|
155
|
+
if (left) return
|
|
156
|
+
left = true
|
|
157
|
+
if (routed) Router.pop()
|
|
158
|
+
else { screen.close(); previous?.open() }
|
|
159
|
+
}
|
|
160
|
+
let done = false
|
|
161
|
+
const finish = (back: string | null): void => {
|
|
162
|
+
if (done) return
|
|
163
|
+
done = true
|
|
164
|
+
leave()
|
|
165
|
+
resolve(back)
|
|
166
|
+
}
|
|
167
|
+
view.on("stop", ({ url: back }) => finish(back))
|
|
168
|
+
view.on("exit", () => finish(null))
|
|
169
|
+
view.on("load", ({ title: loaded }) => { if (loaded) title.text = loaded })
|
|
170
|
+
close.onClick(() => finish(null))
|
|
171
|
+
screen.onBack(() => finish(null))
|
|
172
|
+
// Gone by another hand (the back swipe popped it, another destination replaced it): the flow is
|
|
173
|
+
// over and there is nothing left to leave.
|
|
174
|
+
screen.onClose(() => { left = true; finish(null) })
|
|
175
|
+
|
|
176
|
+
if (routed) Router.push(screen)
|
|
177
|
+
else screen.open()
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
// ---- the app's side -------------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Web content inside the app. `Browser.isSupported` reports whether this host registered the
|
|
184
|
+
* system sheet; `WebView.isSupported` the embedded page.
|
|
185
|
+
*/
|
|
186
|
+
// PURE IIFE so an app that never opens a page tree-shakes the whole plugin away.
|
|
187
|
+
export const Browser: {
|
|
188
|
+
/** Whether this host registered the "browser" service — the system sheet. */
|
|
189
|
+
readonly isSupported: boolean
|
|
190
|
+
/** Open `url`: in the system sheet, or (`embedded`) in a WebView under a close bar. Resolves
|
|
191
|
+
* with the return URL reached (`returnUrl`), or null when the user left. */
|
|
192
|
+
open(url: string, options?: BrowserOpenOptions): Promise<string | null>
|
|
193
|
+
} = /*#__PURE__*/ (() => {
|
|
194
|
+
const browser = {
|
|
195
|
+
open: (url: string, options: BrowserOpenOptions = {}): Promise<string | null> =>
|
|
196
|
+
options.embedded ? openEmbedded(url, options) : openSheet(url, options),
|
|
197
|
+
}
|
|
198
|
+
Object.defineProperty(browser, "isSupported", { get: (): boolean => BrowserBase.supports(SHEET_NAME) })
|
|
199
|
+
return browser as any
|
|
200
|
+
})()
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Create an embedded page. `WebView.isSupported` reports whether this host has the capability —
|
|
204
|
+
* check it before offering the feature.
|
|
205
|
+
*/
|
|
206
|
+
// PURE IIFE so an app that never embeds a page tree-shakes the view away.
|
|
207
|
+
export const WebView: {
|
|
208
|
+
(params?: WebViewParams): WebView
|
|
209
|
+
/** Whether this host registered a "webView" view. */
|
|
210
|
+
readonly isSupported: boolean
|
|
211
|
+
/** Whether the host's half has `method`: false on a host whose half is older than it. */
|
|
212
|
+
supports(method: WebViewMethod): boolean
|
|
213
|
+
} = /*#__PURE__*/ (() => {
|
|
214
|
+
const factory = (params?: WebViewParams): WebView => new WebViewBase(params) as unknown as WebView
|
|
215
|
+
Object.defineProperty(factory, "isSupported", { get: (): boolean => viewSupports() })
|
|
216
|
+
factory.supports = viewSupports
|
|
217
|
+
return factory as any
|
|
218
|
+
})()
|