@swiftbrowser/native 0.18.0 → 0.18.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.
- package/README.md +73 -8
- package/dist/bridge.d.ts +64 -2
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js.map +1 -1
- package/dist/geolocation.d.ts +73 -0
- package/dist/geolocation.d.ts.map +1 -0
- package/dist/geolocation.js +109 -0
- package/dist/geolocation.js.map +1 -0
- package/dist/hostRequests.d.ts +3 -1
- package/dist/hostRequests.d.ts.map +1 -1
- package/dist/hostRequests.js +46 -5
- package/dist/hostRequests.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/nativeFunctions.d.ts +35 -0
- package/dist/nativeFunctions.d.ts.map +1 -0
- package/dist/nativeFunctions.js +90 -0
- package/dist/nativeFunctions.js.map +1 -0
- package/dist/reachability.d.ts +24 -0
- package/dist/reachability.d.ts.map +1 -0
- package/dist/reachability.js +41 -0
- package/dist/reachability.js.map +1 -0
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +3 -1
- package/dist/run.js.map +1 -1
- package/dist/runtime.d.ts +9 -2
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +62 -5
- package/dist/runtime.js.map +1 -1
- package/dist/speech.d.ts +148 -0
- package/dist/speech.d.ts.map +1 -0
- package/dist/speech.js +243 -0
- package/dist/speech.js.map +1 -0
- package/dist/wakeLock.d.ts +32 -0
- package/dist/wakeLock.d.ts.map +1 -0
- package/dist/wakeLock.js +60 -0
- package/dist/wakeLock.js.map +1 -0
- package/package.json +3 -3
- package/src/bridge.ts +70 -2
- package/src/geolocation.ts +160 -0
- package/src/hostRequests.ts +47 -7
- package/src/index.ts +1 -0
- package/src/nativeFunctions.ts +94 -0
- package/src/reachability.ts +49 -0
- package/src/run.ts +5 -3
- package/src/runtime.ts +58 -5
- package/src/speech.ts +312 -0
- package/src/wakeLock.ts +63 -0
- package/swift/Sources/SwiftBrowserNative/Host/AppleSignInBridge.swift +139 -0
- package/swift/Sources/SwiftBrowserNative/Host/AudioRecorderBridge.swift +5 -0
- package/swift/Sources/SwiftBrowserNative/Host/FilesBridge.swift +83 -12
- package/swift/Sources/SwiftBrowserNative/Host/GeolocationBridge.swift +150 -0
- package/swift/Sources/SwiftBrowserNative/Host/HostBridge.swift +131 -2
- package/swift/Sources/SwiftBrowserNative/Host/HostRequestBridge.swift +81 -11
- package/swift/Sources/SwiftBrowserNative/Host/ICloudBridge.swift +112 -0
- package/swift/Sources/SwiftBrowserNative/Host/ReachabilityBridge.swift +47 -0
- package/swift/Sources/SwiftBrowserNative/Host/SpeechBridge.swift +106 -0
- package/swift/Sources/SwiftBrowserNative/Host/SpeechRecognitionBridge.swift +198 -0
- package/swift/Sources/SwiftBrowserNative/Host/WorkerHost.swift +14 -5
- package/swift/Sources/SwiftBrowserNative/Runtime/NativeFunctions.swift +134 -0
- package/swift/Sources/SwiftBrowserNative/Views/ElementView.swift +1 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Controls.swift +2 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/TextInputHints.swift +105 -0
- package/swift/Sources/SwiftBrowserNative/Views/Kinds/Touches.swift +102 -0
- package/swift/Sources/SwiftBrowserProtocol/Props.swift +13 -0
- package/swift/Sources/SwiftBrowserProtocol/RendererEvent.swift +9 -0
- package/wasm/SwiftBrowserNative.wasm +2 -2
package/dist/speech.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Web Speech API over the host: `speechSynthesis` and
|
|
3
|
+
* `SpeechSynthesisUtterance` on `AVSpeechSynthesizer` (the bridge's
|
|
4
|
+
* `speak(id, json)` and `speechControl(action)`, events at
|
|
5
|
+
* `__sbSpeechEvent(id, json)`), and `SpeechRecognition` on
|
|
6
|
+
* `SFSpeechRecognizer` (`recognize(id, action, json)`, events at
|
|
7
|
+
* `__sbRecognition(id, json)`; the app's Info.plist names
|
|
8
|
+
* `NSSpeechRecognitionUsageDescription` and `NSMicrophoneUsageDescription`).
|
|
9
|
+
* The shapes apps use: an utterance's `text`, `lang`, `rate`, `pitch`,
|
|
10
|
+
* `volume` and its `start`, `end`, `error`, `pause`, `resume` and `boundary`
|
|
11
|
+
* events; a recognition's `lang`, `continuous`, `interimResults`, `start()`,
|
|
12
|
+
* `stop()`, `abort()` and its `start`, `result` (with `results[i][0].transcript`
|
|
13
|
+
* and `isFinal`), `error` (`error`: `not-allowed`, `audio-capture`,
|
|
14
|
+
* `no-speech`, `network`, `aborted`) and `end` events.
|
|
15
|
+
*/
|
|
16
|
+
/** Handlers and listeners by event type, as the DOM's targets have them. */
|
|
17
|
+
class Target {
|
|
18
|
+
handlers;
|
|
19
|
+
listeners = new Map();
|
|
20
|
+
constructor(handlers) {
|
|
21
|
+
this.handlers = handlers;
|
|
22
|
+
}
|
|
23
|
+
add(type, listener) {
|
|
24
|
+
if (typeof listener !== 'function')
|
|
25
|
+
return;
|
|
26
|
+
this.listeners.set(type, (this.listeners.get(type) ?? new Set()).add(listener));
|
|
27
|
+
}
|
|
28
|
+
remove(type, listener) {
|
|
29
|
+
this.listeners.get(type)?.delete(listener);
|
|
30
|
+
}
|
|
31
|
+
fire(event) {
|
|
32
|
+
const handler = this.handlers[`on${event.type}`];
|
|
33
|
+
if (typeof handler === 'function')
|
|
34
|
+
handler(event);
|
|
35
|
+
for (const listener of [...(this.listeners.get(event.type) ?? [])])
|
|
36
|
+
listener(event);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export class SpeechSynthesisUtterance {
|
|
40
|
+
text;
|
|
41
|
+
lang = '';
|
|
42
|
+
voice = null;
|
|
43
|
+
volume = 1;
|
|
44
|
+
rate = 1;
|
|
45
|
+
pitch = 1;
|
|
46
|
+
onstart = null;
|
|
47
|
+
onend = null;
|
|
48
|
+
onerror = null;
|
|
49
|
+
onpause = null;
|
|
50
|
+
onresume = null;
|
|
51
|
+
onboundary = null;
|
|
52
|
+
onmark = null;
|
|
53
|
+
/** @internal */
|
|
54
|
+
target = new Target(this);
|
|
55
|
+
constructor(text = '') {
|
|
56
|
+
this.text = String(text);
|
|
57
|
+
}
|
|
58
|
+
addEventListener(type, listener) {
|
|
59
|
+
this.target.add(type, listener);
|
|
60
|
+
}
|
|
61
|
+
removeEventListener(type, listener) {
|
|
62
|
+
this.target.remove(type, listener);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function createSpeechSynthesis(native) {
|
|
66
|
+
/** The utterances handed to the host, by id, in order: the first is speaking once it started. */
|
|
67
|
+
const queue = new Map();
|
|
68
|
+
let nextId = 1;
|
|
69
|
+
let speakingId = null;
|
|
70
|
+
let paused = false;
|
|
71
|
+
const started = new Map();
|
|
72
|
+
const event = (type, utterance, id, extra = {}) => ({
|
|
73
|
+
type,
|
|
74
|
+
utterance,
|
|
75
|
+
charIndex: 0,
|
|
76
|
+
charLength: 0,
|
|
77
|
+
elapsedTime: (native.now() - (started.get(id) ?? native.now())) / 1000,
|
|
78
|
+
name: '',
|
|
79
|
+
...extra,
|
|
80
|
+
});
|
|
81
|
+
const synthesis = {
|
|
82
|
+
get speaking() {
|
|
83
|
+
return speakingId !== null;
|
|
84
|
+
},
|
|
85
|
+
get pending() {
|
|
86
|
+
return queue.size > (speakingId === null ? 0 : 1);
|
|
87
|
+
},
|
|
88
|
+
get paused() {
|
|
89
|
+
return paused;
|
|
90
|
+
},
|
|
91
|
+
onvoiceschanged: null,
|
|
92
|
+
speak(utterance) {
|
|
93
|
+
if (!(utterance instanceof SpeechSynthesisUtterance))
|
|
94
|
+
throw new TypeError('speechSynthesis.speak: not a SpeechSynthesisUtterance');
|
|
95
|
+
const id = nextId++;
|
|
96
|
+
queue.set(id, utterance);
|
|
97
|
+
native.speak(id, JSON.stringify({ text: utterance.text, lang: utterance.lang || null, voice: utterance.voice?.name ?? null, rate: utterance.rate, pitch: utterance.pitch, volume: utterance.volume }));
|
|
98
|
+
},
|
|
99
|
+
cancel() {
|
|
100
|
+
native.speechControl('cancel');
|
|
101
|
+
// The host answers each with `cancel`; the queue empties as they come.
|
|
102
|
+
},
|
|
103
|
+
pause() {
|
|
104
|
+
native.speechControl('pause');
|
|
105
|
+
},
|
|
106
|
+
resume() {
|
|
107
|
+
native.speechControl('resume');
|
|
108
|
+
},
|
|
109
|
+
getVoices() {
|
|
110
|
+
return [];
|
|
111
|
+
},
|
|
112
|
+
addEventListener() { },
|
|
113
|
+
removeEventListener() { },
|
|
114
|
+
};
|
|
115
|
+
return {
|
|
116
|
+
speechSynthesis: synthesis,
|
|
117
|
+
receive(id, json) {
|
|
118
|
+
const utterance = queue.get(id);
|
|
119
|
+
if (!utterance)
|
|
120
|
+
return;
|
|
121
|
+
const e = JSON.parse(json);
|
|
122
|
+
switch (e.type) {
|
|
123
|
+
case 'start':
|
|
124
|
+
speakingId = id;
|
|
125
|
+
paused = false;
|
|
126
|
+
started.set(id, native.now());
|
|
127
|
+
utterance.target.fire(event('start', utterance, id));
|
|
128
|
+
break;
|
|
129
|
+
case 'boundary':
|
|
130
|
+
utterance.target.fire(event('boundary', utterance, id, { name: 'word', charIndex: e.charIndex ?? 0, charLength: e.charLength ?? 0 }));
|
|
131
|
+
break;
|
|
132
|
+
case 'pause':
|
|
133
|
+
paused = true;
|
|
134
|
+
utterance.target.fire(event('pause', utterance, id, { charIndex: e.charIndex ?? 0 }));
|
|
135
|
+
break;
|
|
136
|
+
case 'resume':
|
|
137
|
+
paused = false;
|
|
138
|
+
utterance.target.fire(event('resume', utterance, id, { charIndex: e.charIndex ?? 0 }));
|
|
139
|
+
break;
|
|
140
|
+
case 'end':
|
|
141
|
+
case 'cancel':
|
|
142
|
+
case 'error': {
|
|
143
|
+
queue.delete(id);
|
|
144
|
+
if (speakingId === id)
|
|
145
|
+
speakingId = null;
|
|
146
|
+
if (queue.size === 0)
|
|
147
|
+
paused = false;
|
|
148
|
+
const done = e.type === 'end' ? event('end', utterance, id) : event('error', utterance, id, { error: e.type === 'cancel' ? 'canceled' : (e.message ?? 'synthesis-failed') });
|
|
149
|
+
started.delete(id);
|
|
150
|
+
utterance.target.fire(done);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const recognitions = new Map();
|
|
158
|
+
let recognitionBridge = null;
|
|
159
|
+
let nextRecognition = 1;
|
|
160
|
+
export class SpeechRecognition {
|
|
161
|
+
lang = '';
|
|
162
|
+
continuous = false;
|
|
163
|
+
interimResults = false;
|
|
164
|
+
maxAlternatives = 1;
|
|
165
|
+
onstart = null;
|
|
166
|
+
onaudiostart = null;
|
|
167
|
+
onresult = null;
|
|
168
|
+
onerror = null;
|
|
169
|
+
onend = null;
|
|
170
|
+
onaudioend = null;
|
|
171
|
+
onnomatch = null;
|
|
172
|
+
target = new Target(this);
|
|
173
|
+
id = null;
|
|
174
|
+
/** The final results so far in this session, then the interim one (if any) last. */
|
|
175
|
+
results = [];
|
|
176
|
+
static install(bridge) {
|
|
177
|
+
recognitionBridge = bridge;
|
|
178
|
+
}
|
|
179
|
+
start() {
|
|
180
|
+
if (!recognitionBridge)
|
|
181
|
+
throw new Error('SpeechRecognition: no host');
|
|
182
|
+
if (this.id !== null)
|
|
183
|
+
throw Object.assign(new Error('SpeechRecognition: already started'), { name: 'InvalidStateError' });
|
|
184
|
+
this.id = nextRecognition++;
|
|
185
|
+
this.results = [];
|
|
186
|
+
recognitions.set(this.id, this);
|
|
187
|
+
recognitionBridge.recognize(this.id, 'start', JSON.stringify({ lang: this.lang || null, continuous: this.continuous, interimResults: this.interimResults }));
|
|
188
|
+
}
|
|
189
|
+
/** Stops listening; what was heard is still delivered, then `end`. */
|
|
190
|
+
stop() {
|
|
191
|
+
if (this.id === null)
|
|
192
|
+
return;
|
|
193
|
+
recognitionBridge?.recognize(this.id, 'stop', '{}');
|
|
194
|
+
}
|
|
195
|
+
/** Stops listening and keeps nothing more. */
|
|
196
|
+
abort() {
|
|
197
|
+
if (this.id === null)
|
|
198
|
+
return;
|
|
199
|
+
recognitionBridge?.recognize(this.id, 'abort', '{}');
|
|
200
|
+
}
|
|
201
|
+
addEventListener(type, listener) {
|
|
202
|
+
this.target.add(type, listener);
|
|
203
|
+
}
|
|
204
|
+
removeEventListener(type, listener) {
|
|
205
|
+
this.target.remove(type, listener);
|
|
206
|
+
}
|
|
207
|
+
/** `__sbRecognition(id, json)`. */
|
|
208
|
+
static receive(id, json) {
|
|
209
|
+
recognitions.get(id)?.handle(JSON.parse(json));
|
|
210
|
+
}
|
|
211
|
+
event(type, extra = {}) {
|
|
212
|
+
return { type, resultIndex: 0, results: this.results, ...extra };
|
|
213
|
+
}
|
|
214
|
+
handle(e) {
|
|
215
|
+
switch (e.type) {
|
|
216
|
+
case 'start':
|
|
217
|
+
this.target.fire(this.event('start'));
|
|
218
|
+
this.target.fire(this.event('audiostart'));
|
|
219
|
+
break;
|
|
220
|
+
case 'result': {
|
|
221
|
+
const alternative = { transcript: e.transcript ?? '', confidence: e.confidence ?? 0 };
|
|
222
|
+
const result = { isFinal: e.isFinal === true, length: 1, 0: alternative, item: () => alternative };
|
|
223
|
+
// An interim result replaces the last interim one; a final one takes its place for good.
|
|
224
|
+
const last = this.results[this.results.length - 1];
|
|
225
|
+
const index = last && !last.isFinal ? this.results.length - 1 : this.results.length;
|
|
226
|
+
this.results = [...this.results.slice(0, index), result];
|
|
227
|
+
this.target.fire(this.event('result', { resultIndex: index }));
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
case 'error':
|
|
231
|
+
this.target.fire(this.event('error', { error: e.error ?? 'aborted', message: e.message ?? '' }));
|
|
232
|
+
break;
|
|
233
|
+
case 'end':
|
|
234
|
+
if (this.id !== null)
|
|
235
|
+
recognitions.delete(this.id);
|
|
236
|
+
this.id = null;
|
|
237
|
+
this.target.fire(this.event('audioend'));
|
|
238
|
+
this.target.fire(this.event('end'));
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=speech.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speech.js","sourceRoot":"","sources":["../src/speech.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `navigator.wakeLock` (the Screen Wake Lock API) over the host's idle timer
|
|
3
|
+
* (`wakeLock(active)` on the bridge: `UIApplication.isIdleTimerDisabled`, a
|
|
4
|
+
* power assertion on macOS). `request('screen')` resolves with a
|
|
5
|
+
* `WakeLockSentinel`; the screen stays awake while any sentinel is held and
|
|
6
|
+
* `release()` lets it go. Unlike a page's, which the browser releases when
|
|
7
|
+
* the document hides, a native sentinel stays held in the background: the
|
|
8
|
+
* system owns the screen there, and the lock holds again when the app returns.
|
|
9
|
+
*/
|
|
10
|
+
import type { NativeBridge } from './bridge';
|
|
11
|
+
type ReleaseListener = (event: {
|
|
12
|
+
type: 'release';
|
|
13
|
+
target: WakeLockSentinel;
|
|
14
|
+
}) => void;
|
|
15
|
+
export declare class WakeLockSentinel {
|
|
16
|
+
private readonly onRelease;
|
|
17
|
+
readonly type: "screen";
|
|
18
|
+
released: boolean;
|
|
19
|
+
onrelease: ReleaseListener | null;
|
|
20
|
+
private readonly listeners;
|
|
21
|
+
/** @internal */
|
|
22
|
+
constructor(onRelease: (sentinel: WakeLockSentinel) => void);
|
|
23
|
+
release(): Promise<void>;
|
|
24
|
+
addEventListener(type: string, listener: ReleaseListener): void;
|
|
25
|
+
removeEventListener(type: string, listener: ReleaseListener): void;
|
|
26
|
+
}
|
|
27
|
+
export interface WakeLock {
|
|
28
|
+
request(type?: string): Promise<WakeLockSentinel>;
|
|
29
|
+
}
|
|
30
|
+
export declare function createWakeLock(native: NativeBridge): WakeLock;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=wakeLock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wakeLock.d.ts","sourceRoot":"","sources":["../src/wakeLock.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,KAAK,eAAe,GAAG,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,KAAK,IAAI,CAAC;AAEtF,qBAAa,gBAAgB;IAMf,OAAO,CAAC,QAAQ,CAAC,SAAS;IALtC,QAAQ,CAAC,IAAI,EAAG,QAAQ,CAAU;IAClC,QAAQ,UAAS;IACjB,SAAS,EAAE,eAAe,GAAG,IAAI,CAAQ;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA8B;IACxD,gBAAgB;gBACa,SAAS,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI;IAE5E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAUxB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI;IAI/D,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI;CAGnE;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACnD;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,QAAQ,CAiB7D"}
|
package/dist/wakeLock.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `navigator.wakeLock` (the Screen Wake Lock API) over the host's idle timer
|
|
3
|
+
* (`wakeLock(active)` on the bridge: `UIApplication.isIdleTimerDisabled`, a
|
|
4
|
+
* power assertion on macOS). `request('screen')` resolves with a
|
|
5
|
+
* `WakeLockSentinel`; the screen stays awake while any sentinel is held and
|
|
6
|
+
* `release()` lets it go. Unlike a page's, which the browser releases when
|
|
7
|
+
* the document hides, a native sentinel stays held in the background: the
|
|
8
|
+
* system owns the screen there, and the lock holds again when the app returns.
|
|
9
|
+
*/
|
|
10
|
+
export class WakeLockSentinel {
|
|
11
|
+
onRelease;
|
|
12
|
+
type = 'screen';
|
|
13
|
+
released = false;
|
|
14
|
+
onrelease = null;
|
|
15
|
+
listeners = new Set();
|
|
16
|
+
/** @internal */
|
|
17
|
+
constructor(onRelease) {
|
|
18
|
+
this.onRelease = onRelease;
|
|
19
|
+
}
|
|
20
|
+
release() {
|
|
21
|
+
if (this.released)
|
|
22
|
+
return Promise.resolve();
|
|
23
|
+
this.released = true;
|
|
24
|
+
this.onRelease(this);
|
|
25
|
+
const event = { type: 'release', target: this };
|
|
26
|
+
this.onrelease?.(event);
|
|
27
|
+
for (const listener of [...this.listeners])
|
|
28
|
+
listener(event);
|
|
29
|
+
return Promise.resolve();
|
|
30
|
+
}
|
|
31
|
+
addEventListener(type, listener) {
|
|
32
|
+
if (type === 'release' && typeof listener === 'function')
|
|
33
|
+
this.listeners.add(listener);
|
|
34
|
+
}
|
|
35
|
+
removeEventListener(type, listener) {
|
|
36
|
+
if (type === 'release')
|
|
37
|
+
this.listeners.delete(listener);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function createWakeLock(native) {
|
|
41
|
+
const held = new Set();
|
|
42
|
+
const release = (sentinel) => {
|
|
43
|
+
held.delete(sentinel);
|
|
44
|
+
if (held.size === 0)
|
|
45
|
+
native.wakeLock(false);
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
request(type = 'screen') {
|
|
49
|
+
if (type !== 'screen') {
|
|
50
|
+
return Promise.reject(Object.assign(new TypeError(`wakeLock.request: '${String(type)}' is not a wake lock type ('screen')`), { name: 'TypeError' }));
|
|
51
|
+
}
|
|
52
|
+
const sentinel = new WakeLockSentinel(release);
|
|
53
|
+
if (held.size === 0)
|
|
54
|
+
native.wakeLock(true);
|
|
55
|
+
held.add(sentinel);
|
|
56
|
+
return Promise.resolve(sentinel);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=wakeLock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wakeLock.js","sourceRoot":"","sources":["../src/wakeLock.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,MAAM,OAAO,gBAAgB;IAME;IALpB,IAAI,GAAG,QAAiB,CAAC;IAClC,QAAQ,GAAG,KAAK,CAAC;IACjB,SAAS,GAA2B,IAAI,CAAC;IACxB,SAAS,GAAG,IAAI,GAAG,EAAmB,CAAC;IACxD,gBAAgB;IAChB,YAA6B,SAA+C;QAA/C,cAAS,GAAT,SAAS,CAAsC;IAAG,CAAC;IAEhF,OAAO;QACL,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC5C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACrB,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,SAAkB,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QACzD,IAAI,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC;QACxB,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC;YAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC5D,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;IAED,gBAAgB,CAAC,IAAY,EAAE,QAAyB;QACtD,IAAI,IAAI,KAAK,SAAS,IAAI,OAAO,QAAQ,KAAK,UAAU;YAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACzF,CAAC;IAED,mBAAmB,CAAC,IAAY,EAAE,QAAyB;QACzD,IAAI,IAAI,KAAK,SAAS;YAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC1D,CAAC;CACF;AAMD,MAAM,UAAU,cAAc,CAAC,MAAoB;IACjD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAoB,CAAC;IACzC,MAAM,OAAO,GAAG,CAAC,QAA0B,EAAQ,EAAE;QACnD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACtB,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;YAAE,MAAM,CAAC,QAAS,CAAC,KAAK,CAAC,CAAC;IAC/C,CAAC,CAAC;IACF,OAAO;QACL,OAAO,CAAC,IAAI,GAAG,QAAQ;YACrB,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACtB,OAAO,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC,sBAAsB,MAAM,CAAC,IAAI,CAAC,sCAAsC,CAAC,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;YACvJ,CAAC;YACD,MAAM,QAAQ,GAAG,IAAI,gBAAgB,CAAC,OAAO,CAAC,CAAC;YAC/C,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;gBAAE,MAAM,CAAC,QAAS,CAAC,IAAI,CAAC,CAAC;YAC5C,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACnB,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACnC,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swiftbrowser/native",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Run a @swiftbrowser/react app natively: the runtime a JavaScriptCore-hosted app needs (timers, console, fetch and the other web globals over the SwiftBrowser Native bridge), the `run` entry that renders into the native SwiftUI renderer, and the bundler that builds app.js for the Xcode target.",
|
|
6
6
|
"repository": {
|
|
@@ -59,8 +59,8 @@
|
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@babel/core": "^7.29.7",
|
|
62
|
-
"@swiftbrowser/react": "0.18.
|
|
63
|
-
"@swiftbrowser/web": "0.18.
|
|
62
|
+
"@swiftbrowser/react": "0.18.2",
|
|
63
|
+
"@swiftbrowser/web": "0.18.2",
|
|
64
64
|
"esbuild": "^0.28.2",
|
|
65
65
|
"react-refresh": "^0.17.0"
|
|
66
66
|
},
|
package/src/bridge.ts
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* package defines (`__sbTimerFired`, `__sbFetchResult`, `__sbImageProbed`,
|
|
6
6
|
* `__sbWorkerMessage`, `__sbOpenURL`, `__sbMediaSessionCommand`,
|
|
7
7
|
* `__sbNotificationPermission`, `__sbPushRegistered`, `__sbNotification`,
|
|
8
|
-
* `__sbWebSocketEvent`, `__sbHostResponse`, `__sbUndoCommand`
|
|
8
|
+
* `__sbWebSocketEvent`, `__sbHostResponse`, `__sbUndoCommand`, `__sbOnline`,
|
|
9
|
+
* `__sbGeolocation`, `__sbSpeechEvent`, `__sbRecognition`,
|
|
10
|
+
* `__sbFilesChanged`, `__sbNativeEvent`).
|
|
9
11
|
*/
|
|
10
12
|
export interface NativeBridge {
|
|
11
13
|
/** Applies a render pass's ops (a JSON array). Returns an error message, or nothing. */
|
|
@@ -79,14 +81,80 @@ export interface NativeBridge {
|
|
|
79
81
|
/** `navigator.vibrate`: a haptic (`impact`, `selection`, `success`, `warning`, `error`). Hosts before 0.20 lack it. */
|
|
80
82
|
haptic?(kind: string): void;
|
|
81
83
|
|
|
84
|
+
/**
|
|
85
|
+
* `navigator.onLine` (reachability.ts): whether the network path is
|
|
86
|
+
* satisfied, from `NWPathMonitor`; a change arrives at `__sbOnline(bool)`,
|
|
87
|
+
* which fires the `online` and `offline` events. Hosts before 0.19 lack it
|
|
88
|
+
* (and Linux and the round trip, which have no Network framework), and
|
|
89
|
+
* then the app is always online.
|
|
90
|
+
*/
|
|
91
|
+
online?(): boolean;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* `navigator.wakeLock` (wakeLock.ts): keep the screen awake while `active`
|
|
95
|
+
* (`UIApplication.isIdleTimerDisabled`; a power assertion on macOS). Called
|
|
96
|
+
* when the first sentinel is taken and when the last is released. Hosts
|
|
97
|
+
* before 0.19 lack it, and then there is no `navigator.wakeLock`, as in a
|
|
98
|
+
* browser without the API.
|
|
99
|
+
*/
|
|
100
|
+
wakeLock?(active: boolean): void;
|
|
101
|
+
|
|
102
|
+
// Speech (speech.ts). Hosts before 0.19 lack them, and then there is no `speechSynthesis` or `SpeechRecognition`.
|
|
103
|
+
/**
|
|
104
|
+
* `speechSynthesis.speak`: utterance `id`, `{ text, lang, voice, rate, pitch,
|
|
105
|
+
* volume }` as JSON, queued on `AVSpeechSynthesizer`; its events arrive at
|
|
106
|
+
* `__sbSpeechEvent(id, json)`: `{ type: 'start' | 'end' | 'cancel' | 'pause'
|
|
107
|
+
* | 'resume' | 'boundary' | 'error', charIndex?, charLength?, message? }`.
|
|
108
|
+
*/
|
|
109
|
+
speak?(id: number, json: string): void;
|
|
110
|
+
/** `speechSynthesis.cancel`, `pause` and `resume`, for every utterance queued. */
|
|
111
|
+
speechControl?(action: 'cancel' | 'pause' | 'resume'): void;
|
|
112
|
+
/**
|
|
113
|
+
* `SpeechRecognition`: `start` listens to the microphone through
|
|
114
|
+
* `SFSpeechRecognizer` with `{ lang, continuous, interimResults }`, `stop`
|
|
115
|
+
* ends the audio and lets the last result come, `abort` ends it all; the
|
|
116
|
+
* events arrive at `__sbRecognition(id, json)`: `{ type: 'start' }`,
|
|
117
|
+
* `{ type: 'result', transcript, isFinal, confidence }`, `{ type: 'error',
|
|
118
|
+
* error, message }` and `{ type: 'end' }`. The first start asks for speech
|
|
119
|
+
* recognition and the microphone (`NSSpeechRecognitionUsageDescription`,
|
|
120
|
+
* `NSMicrophoneUsageDescription`).
|
|
121
|
+
*/
|
|
122
|
+
recognize?(id: number, action: 'start' | 'stop' | 'abort', json: string): void;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* `navigator.geolocation` (geolocation.ts) over `CLLocationManager`:
|
|
126
|
+
* `current` asks for one fix, `watch` for every fix until `clear`, with
|
|
127
|
+
* the options `{ enableHighAccuracy, timeout, maximumAge }` as JSON. Each
|
|
128
|
+
* fix, or the failure, arrives at `__sbGeolocation(id, json)`: `{ position:
|
|
129
|
+
* { latitude, longitude, accuracy, altitude, altitudeAccuracy, heading,
|
|
130
|
+
* speed, timestamp } }` or `{ error: { code, message } }` (1 denied, 2
|
|
131
|
+
* unavailable). The first ask prompts the viewer (the app's Info.plist
|
|
132
|
+
* names `NSLocationWhenInUseUsageDescription`). Hosts before 0.19 lack it,
|
|
133
|
+
* and then there is no `navigator.geolocation`.
|
|
134
|
+
*/
|
|
135
|
+
geolocation?(id: number, action: 'current' | 'watch' | 'clear', json: string): void;
|
|
136
|
+
|
|
82
137
|
/**
|
|
83
138
|
* A host request of the web renderer's kinds (hostRequests.ts): `share`,
|
|
84
|
-
* `pickFiles`, `clipboard`, `pickColor` and `
|
|
139
|
+
* `pickFiles`, `exportFiles`, `clipboard`, `pickColor`, `webAuth`, `appleSignIn` and `files`, with the request's
|
|
85
140
|
* params as JSON; the answer arrives at `__sbHostResponse(id, { value } | { error })`.
|
|
141
|
+
* Once `files` resolved iCloud Drive's container (`action: 'iCloud'`), the
|
|
142
|
+
* URLs another device changes there arrive at `__sbFilesChanged(json)`.
|
|
86
143
|
* Hosts before 0.20 lack it.
|
|
87
144
|
*/
|
|
88
145
|
hostRequest?(id: number, kind: string, json: string): void;
|
|
89
146
|
|
|
147
|
+
/**
|
|
148
|
+
* The escape hatch's functions (nativeFunctions.ts): the names the Swift
|
|
149
|
+
* app registered with `NativeFunctions`, and a sync one run inline with its
|
|
150
|
+
* params as JSON, answering `{ value } | { error }` as JSON. The async ones
|
|
151
|
+
* go through `hostRequest` as `app:<name>`; what Swift sends with
|
|
152
|
+
* `NativeFunctions.send` arrives at `__sbNativeEvent(name, json)`. Hosts
|
|
153
|
+
* before 0.20 lack them.
|
|
154
|
+
*/
|
|
155
|
+
nativeFunctions?(): string[];
|
|
156
|
+
callNativeSync?(name: string, json: string): string;
|
|
157
|
+
|
|
90
158
|
/**
|
|
91
159
|
* The app's undo manager's state (undo.ts): `{ canUndo, canRedo,
|
|
92
160
|
* undoActionName, redoActionName }` as JSON, which the host mirrors on the
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `navigator.geolocation` over the host's location manager (`geolocation(id,
|
|
3
|
+
* action, json)` on the bridge, `CLLocationManager` on the Swift side):
|
|
4
|
+
* `getCurrentPosition` asks once (`current`), `watchPosition` keeps asking
|
|
5
|
+
* (`watch`) until `clearWatch` (`clear`). Each answer arrives at
|
|
6
|
+
* `__sbGeolocation(id, json)` as `{ position: { latitude, longitude,
|
|
7
|
+
* accuracy, altitude?, altitudeAccuracy?, heading?, speed?, timestamp } }` or
|
|
8
|
+
* `{ error: { code, message } }` with the API's codes (1 permission denied, 2
|
|
9
|
+
* position unavailable, 3 timeout). The first ask prompts the viewer; the
|
|
10
|
+
* app's Info.plist names `NSLocationWhenInUseUsageDescription`. `timeout`
|
|
11
|
+
* runs here, on the runtime's timers; `maximumAge` is left to the host, which
|
|
12
|
+
* answers with its freshest fix.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { NativeBridge } from './bridge';
|
|
16
|
+
|
|
17
|
+
export interface GeolocationCoordinates {
|
|
18
|
+
latitude: number;
|
|
19
|
+
longitude: number;
|
|
20
|
+
accuracy: number;
|
|
21
|
+
altitude: number | null;
|
|
22
|
+
altitudeAccuracy: number | null;
|
|
23
|
+
heading: number | null;
|
|
24
|
+
speed: number | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface GeolocationPosition {
|
|
28
|
+
coords: GeolocationCoordinates;
|
|
29
|
+
/** Milliseconds since the epoch. */
|
|
30
|
+
timestamp: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class GeolocationPositionError extends Error {
|
|
34
|
+
static readonly PERMISSION_DENIED = 1;
|
|
35
|
+
static readonly POSITION_UNAVAILABLE = 2;
|
|
36
|
+
static readonly TIMEOUT = 3;
|
|
37
|
+
readonly PERMISSION_DENIED = 1;
|
|
38
|
+
readonly POSITION_UNAVAILABLE = 2;
|
|
39
|
+
readonly TIMEOUT = 3;
|
|
40
|
+
constructor(
|
|
41
|
+
readonly code: number,
|
|
42
|
+
message: string,
|
|
43
|
+
) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = 'GeolocationPositionError';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface PositionOptions {
|
|
50
|
+
enableHighAccuracy?: boolean;
|
|
51
|
+
timeout?: number;
|
|
52
|
+
maximumAge?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type Success = (position: GeolocationPosition) => void;
|
|
56
|
+
type Failure = (error: GeolocationPositionError) => void;
|
|
57
|
+
|
|
58
|
+
/** The host's answer for request `id`. */
|
|
59
|
+
export interface NativeGeolocationResult {
|
|
60
|
+
position?: {
|
|
61
|
+
latitude: number;
|
|
62
|
+
longitude: number;
|
|
63
|
+
accuracy: number;
|
|
64
|
+
altitude?: number | null;
|
|
65
|
+
altitudeAccuracy?: number | null;
|
|
66
|
+
heading?: number | null;
|
|
67
|
+
speed?: number | null;
|
|
68
|
+
timestamp: number;
|
|
69
|
+
};
|
|
70
|
+
error?: { code?: number; message?: string };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface Geolocation {
|
|
74
|
+
getCurrentPosition(success: Success, failure?: Failure | null, options?: PositionOptions): void;
|
|
75
|
+
watchPosition(success: Success, failure?: Failure | null, options?: PositionOptions): number;
|
|
76
|
+
clearWatch(id: number): void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface Pending {
|
|
80
|
+
watch: boolean;
|
|
81
|
+
success: Success;
|
|
82
|
+
failure: Failure | null;
|
|
83
|
+
timer: unknown;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
type Timers = { setTimeout?: (fn: () => void, ms: number) => unknown; clearTimeout?: (id: unknown) => void };
|
|
87
|
+
|
|
88
|
+
export function createGeolocation(native: NativeBridge, target: Record<string, unknown>): { geolocation: Geolocation; receive(id: number, json: string): void } {
|
|
89
|
+
const pending = new Map<number, Pending>();
|
|
90
|
+
let nextId = 1;
|
|
91
|
+
const timers = target as Timers;
|
|
92
|
+
|
|
93
|
+
const finish = (id: number): void => {
|
|
94
|
+
const entry = pending.get(id);
|
|
95
|
+
if (!entry) return;
|
|
96
|
+
pending.delete(id);
|
|
97
|
+
if (entry.timer !== null && typeof timers.clearTimeout === 'function') timers.clearTimeout(entry.timer);
|
|
98
|
+
};
|
|
99
|
+
const ask = (watch: boolean, success: Success, failure: Failure | null | undefined, options: PositionOptions | undefined): number => {
|
|
100
|
+
if (typeof success !== 'function') throw new TypeError('geolocation: the success callback is not a function');
|
|
101
|
+
const id = nextId++;
|
|
102
|
+
const entry: Pending = { watch, success, failure: typeof failure === 'function' ? failure : null, timer: null };
|
|
103
|
+
pending.set(id, entry);
|
|
104
|
+
const timeout = options?.timeout;
|
|
105
|
+
if (typeof timeout === 'number' && timeout >= 0 && timeout !== Infinity && typeof timers.setTimeout === 'function') {
|
|
106
|
+
entry.timer = timers.setTimeout(() => {
|
|
107
|
+
// A watch times out once per fix that does not come; its wait starts over with the next answer.
|
|
108
|
+
if (!watch) {
|
|
109
|
+
finish(id);
|
|
110
|
+
native.geolocation!(id, 'clear', '{}');
|
|
111
|
+
} else entry.timer = null;
|
|
112
|
+
entry.failure?.(new GeolocationPositionError(GeolocationPositionError.TIMEOUT, 'geolocation: timed out'));
|
|
113
|
+
}, timeout);
|
|
114
|
+
}
|
|
115
|
+
native.geolocation!(id, watch ? 'watch' : 'current', JSON.stringify({ enableHighAccuracy: options?.enableHighAccuracy === true, timeout: timeout ?? null, maximumAge: options?.maximumAge ?? 0 }));
|
|
116
|
+
return id;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
geolocation: {
|
|
121
|
+
getCurrentPosition(success, failure, options) {
|
|
122
|
+
ask(false, success, failure, options);
|
|
123
|
+
},
|
|
124
|
+
watchPosition(success, failure, options) {
|
|
125
|
+
return ask(true, success, failure, options);
|
|
126
|
+
},
|
|
127
|
+
clearWatch(id) {
|
|
128
|
+
if (!pending.has(Number(id))) return;
|
|
129
|
+
finish(Number(id));
|
|
130
|
+
native.geolocation!(Number(id), 'clear', '{}');
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
receive(id, json) {
|
|
134
|
+
const entry = pending.get(id);
|
|
135
|
+
if (!entry) return;
|
|
136
|
+
const result = JSON.parse(json) as NativeGeolocationResult;
|
|
137
|
+
if (!entry.watch) finish(id);
|
|
138
|
+
if (result.position) {
|
|
139
|
+
const p = result.position;
|
|
140
|
+
entry.success({
|
|
141
|
+
coords: {
|
|
142
|
+
latitude: p.latitude,
|
|
143
|
+
longitude: p.longitude,
|
|
144
|
+
accuracy: p.accuracy,
|
|
145
|
+
altitude: p.altitude ?? null,
|
|
146
|
+
altitudeAccuracy: p.altitudeAccuracy ?? null,
|
|
147
|
+
heading: p.heading ?? null,
|
|
148
|
+
speed: p.speed ?? null,
|
|
149
|
+
},
|
|
150
|
+
timestamp: p.timestamp,
|
|
151
|
+
});
|
|
152
|
+
} else {
|
|
153
|
+
const code = result.error?.code ?? GeolocationPositionError.POSITION_UNAVAILABLE;
|
|
154
|
+
// A denial ends a watch too: the host will not answer again.
|
|
155
|
+
if (entry.watch && code === GeolocationPositionError.PERMISSION_DENIED) finish(id);
|
|
156
|
+
entry.failure?.(new GeolocationPositionError(code, result.error?.message ?? 'geolocation: the position is unavailable'));
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|