@pickleball/expo-sdk 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +69 -0
- package/dist/advanced-BXDrDl1h.d.ts +316 -0
- package/dist/advanced-BdAa2Erv.d.cts +316 -0
- package/dist/advanced.cjs +35 -0
- package/dist/advanced.d.cts +3 -0
- package/dist/advanced.d.ts +3 -0
- package/dist/advanced.js +35 -0
- package/dist/chunk-2JHULJZ7.js +265 -0
- package/dist/chunk-6JOMESNT.js +9 -0
- package/dist/chunk-7OX2DSKI.cjs +33 -0
- package/dist/chunk-DHDX3ZOO.js +800 -0
- package/dist/chunk-FODE2W2H.js +1953 -0
- package/dist/chunk-KFQGP6VL.js +33 -0
- package/dist/chunk-LCWGNGEN.cjs +265 -0
- package/dist/chunk-TBPR2QND.cjs +1953 -0
- package/dist/chunk-TRLASNPQ.cjs +9 -0
- package/dist/chunk-YGSQ25UA.cjs +800 -0
- package/dist/device-agent/index.cjs +498 -0
- package/dist/device-agent/index.d.cts +157 -0
- package/dist/device-agent/index.d.ts +157 -0
- package/dist/device-agent/index.js +498 -0
- package/dist/engine-CCSSqPbZ.d.cts +516 -0
- package/dist/engine-CCSSqPbZ.d.ts +516 -0
- package/dist/index.cjs +339 -5569
- package/dist/index.d.cts +22 -1406
- package/dist/index.d.ts +22 -1406
- package/dist/index.js +269 -5401
- package/dist/match/index.cjs +2456 -0
- package/dist/match/index.d.cts +253 -0
- package/dist/match/index.d.ts +253 -0
- package/dist/match/index.js +2456 -0
- package/dist/match/remote/index.cjs +21 -0
- package/dist/match/remote/index.d.cts +67 -0
- package/dist/match/remote/index.d.ts +67 -0
- package/dist/match/remote/index.js +21 -0
- package/dist/match/voice/index.cjs +23 -0
- package/dist/match/voice/index.d.cts +114 -0
- package/dist/match/voice/index.d.ts +114 -0
- package/dist/match/voice/index.js +23 -0
- package/dist/plugin.cjs +53 -70
- package/dist/plugin.d.cts +13 -0
- package/dist/plugin.d.ts +13 -0
- package/dist/plugin.js +20 -15
- package/dist/types-CrmcX408.d.cts +176 -0
- package/dist/types-CrmcX408.d.ts +176 -0
- package/dist/types-DiKWjwvN.d.cts +96 -0
- package/dist/types-DiKWjwvN.d.ts +96 -0
- package/dist/version.cjs +4 -32
- package/dist/version.js +2 -1
- package/package.json +68 -4
- package/src/advanced.ts +47 -0
- package/src/device-agent/agent.ts +443 -0
- package/src/device-agent/component.tsx +49 -0
- package/src/device-agent/convex-control-channel.ts +82 -0
- package/src/device-agent/http-transport.ts +143 -0
- package/src/device-agent/index.ts +4 -0
- package/src/engine.ts +21 -5
- package/src/errors.ts +16 -0
- package/src/http-session-provider.ts +19 -13
- package/src/index.ts +38 -14
- package/src/livekit-room-adapter.ts +39 -2
- package/src/livestream-screen.tsx +26 -4
- package/src/match/index.ts +29 -0
- package/src/match/provider.tsx +4 -2
- package/src/match/remote/hid-gesture.ts +10 -2
- package/src/match/remote/types.ts +6 -4
- package/src/match/voice/scheduler.ts +6 -1
- package/src/match/voice/service.ts +16 -4
- package/src/native-runtime.ts +54 -4
- package/src/plugin.ts +35 -14
- package/src/provider.tsx +35 -4
- package/src/rtmp-room-adapter.ts +196 -0
- package/dist/chunk-VHLUJQG5.js +0 -17
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class;
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
var _chunkTBPR2QNDcjs = require('../chunk-TBPR2QND.cjs');
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
var _chunk7OX2DSKIcjs = require('../chunk-7OX2DSKI.cjs');
|
|
17
|
+
|
|
18
|
+
// src/device-agent/convex-control-channel.ts
|
|
19
|
+
var convex_control_channel_exports = {};
|
|
20
|
+
_chunk7OX2DSKIcjs.__export.call(void 0, convex_control_channel_exports, {
|
|
21
|
+
createConvexControlChannel: () => createConvexControlChannel
|
|
22
|
+
});
|
|
23
|
+
var _browser = require('convex/browser');
|
|
24
|
+
var _server = require('convex/server');
|
|
25
|
+
function createConvexControlChannel(convexUrl) {
|
|
26
|
+
const client = new (0, _browser.ConvexClient)(convexUrl);
|
|
27
|
+
const deviceControl = _server.anyApi.deviceControl;
|
|
28
|
+
function toCommands(value) {
|
|
29
|
+
if (!Array.isArray(value)) return [];
|
|
30
|
+
const commands = [];
|
|
31
|
+
for (const item of value) {
|
|
32
|
+
if (item === null || typeof item !== "object") continue;
|
|
33
|
+
const record = item;
|
|
34
|
+
if (typeof record.id !== "string" || record.type !== "start_livestream" && record.type !== "stop_livestream" || typeof record.expiresAt !== "number") {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const payload = record.payload !== null && typeof record.payload === "object" && !Array.isArray(record.payload) ? record.payload : {};
|
|
38
|
+
commands.push({
|
|
39
|
+
id: record.id,
|
|
40
|
+
type: record.type,
|
|
41
|
+
payload: {
|
|
42
|
+
...typeof payload.title === "string" ? { title: payload.title } : {},
|
|
43
|
+
...payload.visibility === "public" || payload.visibility === "private" ? { visibility: payload.visibility } : {}
|
|
44
|
+
},
|
|
45
|
+
expiresAt: record.expiresAt
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return commands;
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
async attach(controlSessionId, token) {
|
|
52
|
+
await client.mutation(deviceControl.deviceAttach, { controlSessionId, token });
|
|
53
|
+
},
|
|
54
|
+
onCommands(controlSessionId, token, callback) {
|
|
55
|
+
return client.onUpdate(
|
|
56
|
+
deviceControl.pendingCommands,
|
|
57
|
+
{ controlSessionId, token },
|
|
58
|
+
(value) => callback(toCommands(value))
|
|
59
|
+
);
|
|
60
|
+
},
|
|
61
|
+
onState(controlSessionId, token, callback) {
|
|
62
|
+
return client.onUpdate(
|
|
63
|
+
deviceControl.controlSessionState,
|
|
64
|
+
{ controlSessionId, token },
|
|
65
|
+
(value) => {
|
|
66
|
+
const status = value !== null && typeof value === "object" && typeof value.status === "string" ? value.status : "ended";
|
|
67
|
+
callback({ status });
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
},
|
|
71
|
+
async ack(input) {
|
|
72
|
+
await client.mutation(deviceControl.ackCommand, { ...input });
|
|
73
|
+
},
|
|
74
|
+
close() {
|
|
75
|
+
void client.close();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
var init_convex_control_channel = _chunk7OX2DSKIcjs.__esm.call(void 0, {
|
|
80
|
+
"src/device-agent/convex-control-channel.ts"() {
|
|
81
|
+
"use strict";
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// src/device-agent/agent.ts
|
|
86
|
+
var DEFAULT_HEARTBEAT_INTERVAL_MS = 12e3;
|
|
87
|
+
var REGISTER_RETRY_BASE_MS = 2e3;
|
|
88
|
+
var REGISTER_RETRY_MAX_MS = 6e4;
|
|
89
|
+
var METRIC_FIELDS = [
|
|
90
|
+
"ramFreeMb",
|
|
91
|
+
"ramTotalMb",
|
|
92
|
+
"storageFreeGb",
|
|
93
|
+
"batteryPct",
|
|
94
|
+
"batteryTempC",
|
|
95
|
+
"thermal",
|
|
96
|
+
"netType",
|
|
97
|
+
"linkMbps",
|
|
98
|
+
"rssi",
|
|
99
|
+
"connQuality"
|
|
100
|
+
];
|
|
101
|
+
function defaultSubscribeAppState(listener) {
|
|
102
|
+
const { AppState } = _chunk7OX2DSKIcjs.__require.call(void 0, "react-native");
|
|
103
|
+
const subscription = AppState.addEventListener("change", (state) => {
|
|
104
|
+
listener(
|
|
105
|
+
state === "active" ? "active" : state === "background" ? "background" : "inactive"
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
return () => subscription.remove();
|
|
109
|
+
}
|
|
110
|
+
function defaultCreateControlChannel(convexUrl) {
|
|
111
|
+
const { createConvexControlChannel: createConvexControlChannel2 } = (init_convex_control_channel(), _chunk7OX2DSKIcjs.__toCommonJS.call(void 0, convex_control_channel_exports));
|
|
112
|
+
return createConvexControlChannel2(convexUrl);
|
|
113
|
+
}
|
|
114
|
+
function pickMetrics(sample) {
|
|
115
|
+
if (!sample) return void 0;
|
|
116
|
+
const metrics = {};
|
|
117
|
+
for (const field of METRIC_FIELDS) {
|
|
118
|
+
const value = sample[field];
|
|
119
|
+
if (typeof value === "number" && Number.isFinite(value)) metrics[field] = value;
|
|
120
|
+
else if (typeof value === "string" && value.trim() !== "") metrics[field] = value;
|
|
121
|
+
}
|
|
122
|
+
return Object.keys(metrics).length > 0 ? metrics : void 0;
|
|
123
|
+
}
|
|
124
|
+
var DeviceAgent = (_class = class {
|
|
125
|
+
|
|
126
|
+
__init() {this.running = false}
|
|
127
|
+
__init2() {this.registered = false}
|
|
128
|
+
__init3() {this.foreground = true}
|
|
129
|
+
__init4() {this.heartbeatIntervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS}
|
|
130
|
+
__init5() {this.timer = null}
|
|
131
|
+
__init6() {this.beatInFlight = false}
|
|
132
|
+
__init7() {this.registerAttempt = 0}
|
|
133
|
+
__init8() {this.unsubscribeAppState = null}
|
|
134
|
+
__init9() {this.heartbeatFailures = 0}
|
|
135
|
+
__init10() {this.controlChannel = null}
|
|
136
|
+
__init11() {this.controlSessionId = null}
|
|
137
|
+
__init12() {this.controlUnsubscribers = []}
|
|
138
|
+
__init13() {this.executing = false}
|
|
139
|
+
__init14() {this.handledCommandIds = /* @__PURE__ */ new Set()}
|
|
140
|
+
constructor(options) {;_class.prototype.__init.call(this);_class.prototype.__init2.call(this);_class.prototype.__init3.call(this);_class.prototype.__init4.call(this);_class.prototype.__init5.call(this);_class.prototype.__init6.call(this);_class.prototype.__init7.call(this);_class.prototype.__init8.call(this);_class.prototype.__init9.call(this);_class.prototype.__init10.call(this);_class.prototype.__init11.call(this);_class.prototype.__init12.call(this);_class.prototype.__init13.call(this);_class.prototype.__init14.call(this);
|
|
141
|
+
this.options = options;
|
|
142
|
+
}
|
|
143
|
+
start() {
|
|
144
|
+
if (this.running) return;
|
|
145
|
+
this.running = true;
|
|
146
|
+
this.unsubscribeAppState = (_nullishCoalesce(this.options.subscribeAppState, () => ( defaultSubscribeAppState)))((state) => this.handleAppState(state));
|
|
147
|
+
this.schedule(0);
|
|
148
|
+
}
|
|
149
|
+
stop() {
|
|
150
|
+
this.running = false;
|
|
151
|
+
if (this.timer) clearTimeout(this.timer);
|
|
152
|
+
this.timer = null;
|
|
153
|
+
_optionalChain([this, 'access', _ => _.unsubscribeAppState, 'optionalCall', _2 => _2()]);
|
|
154
|
+
this.unsubscribeAppState = null;
|
|
155
|
+
this.closeControlChannel();
|
|
156
|
+
}
|
|
157
|
+
handleAppState(state) {
|
|
158
|
+
const wasForeground = this.foreground;
|
|
159
|
+
this.foreground = state === "active";
|
|
160
|
+
if (!this.running) return;
|
|
161
|
+
if (this.foreground && !wasForeground) {
|
|
162
|
+
this.schedule(0);
|
|
163
|
+
}
|
|
164
|
+
if (!this.foreground) {
|
|
165
|
+
if (this.timer) clearTimeout(this.timer);
|
|
166
|
+
this.timer = null;
|
|
167
|
+
this.closeControlChannel();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
schedule(delayMs) {
|
|
171
|
+
if (!this.running || !this.foreground) return;
|
|
172
|
+
if (this.timer) clearTimeout(this.timer);
|
|
173
|
+
this.timer = setTimeout(() => {
|
|
174
|
+
void this.beat();
|
|
175
|
+
}, delayMs);
|
|
176
|
+
}
|
|
177
|
+
async beat() {
|
|
178
|
+
if (!this.running || !this.foreground || this.beatInFlight) return;
|
|
179
|
+
this.beatInFlight = true;
|
|
180
|
+
try {
|
|
181
|
+
if (!this.registered) {
|
|
182
|
+
await this.register();
|
|
183
|
+
if (!this.registered) return;
|
|
184
|
+
}
|
|
185
|
+
await this.heartbeat();
|
|
186
|
+
this.schedule(this.heartbeatIntervalMs);
|
|
187
|
+
} finally {
|
|
188
|
+
this.beatInFlight = false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
async register() {
|
|
192
|
+
try {
|
|
193
|
+
const result = await this.options.transport.register({
|
|
194
|
+
...this.options.bootstrapInput,
|
|
195
|
+
...this.options.deviceInfo
|
|
196
|
+
});
|
|
197
|
+
if (result.heartbeatIntervalMs > 0) {
|
|
198
|
+
this.heartbeatIntervalMs = result.heartbeatIntervalMs;
|
|
199
|
+
}
|
|
200
|
+
this.registered = true;
|
|
201
|
+
this.registerAttempt = 0;
|
|
202
|
+
} catch (e) {
|
|
203
|
+
const delay = Math.min(
|
|
204
|
+
REGISTER_RETRY_BASE_MS * 2 ** this.registerAttempt,
|
|
205
|
+
REGISTER_RETRY_MAX_MS
|
|
206
|
+
);
|
|
207
|
+
this.registerAttempt += 1;
|
|
208
|
+
this.schedule(delay);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async heartbeat() {
|
|
212
|
+
const snapshot = this.options.engine.getSnapshot();
|
|
213
|
+
let metrics;
|
|
214
|
+
try {
|
|
215
|
+
metrics = pickMetrics(await _asyncNullishCoalesce(await _optionalChain([this, 'access', _3 => _3.options, 'access', _4 => _4.deviceMetrics, 'optionalCall', _5 => _5()]), async () => ( null)));
|
|
216
|
+
} catch (e2) {
|
|
217
|
+
metrics = void 0;
|
|
218
|
+
}
|
|
219
|
+
let result;
|
|
220
|
+
try {
|
|
221
|
+
result = await this.options.transport.heartbeat({
|
|
222
|
+
...this.options.bootstrapInput,
|
|
223
|
+
agentState: snapshot.state,
|
|
224
|
+
currentSessionId: snapshot.sessionId,
|
|
225
|
+
..._optionalChain([this, 'access', _6 => _6.options, 'access', _7 => _7.deviceInfo, 'optionalAccess', _8 => _8.appVersion]) ? { appVersion: this.options.deviceInfo.appVersion } : {},
|
|
226
|
+
...metrics ? { metrics } : {}
|
|
227
|
+
});
|
|
228
|
+
} catch (e3) {
|
|
229
|
+
this.heartbeatFailures += 1;
|
|
230
|
+
if (this.heartbeatFailures >= 3) {
|
|
231
|
+
this.registered = false;
|
|
232
|
+
this.heartbeatFailures = 0;
|
|
233
|
+
}
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
this.heartbeatFailures = 0;
|
|
237
|
+
if (result.controlSession && !this.controlChannel) {
|
|
238
|
+
this.openControlChannel(result.controlSession);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
openControlChannel(grant) {
|
|
242
|
+
let channel;
|
|
243
|
+
try {
|
|
244
|
+
channel = (_nullishCoalesce(this.options.createControlChannel, () => ( defaultCreateControlChannel)))(
|
|
245
|
+
grant.convexUrl
|
|
246
|
+
);
|
|
247
|
+
} catch (e4) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
this.controlChannel = channel;
|
|
251
|
+
this.controlSessionId = grant.id;
|
|
252
|
+
void channel.attach(grant.id, grant.token).then(() => {
|
|
253
|
+
if (this.controlChannel !== channel) return;
|
|
254
|
+
this.controlUnsubscribers.push(
|
|
255
|
+
channel.onCommands(grant.id, grant.token, (commands) => {
|
|
256
|
+
void this.executeCommands(channel, grant, commands);
|
|
257
|
+
}),
|
|
258
|
+
channel.onState(grant.id, grant.token, (state) => {
|
|
259
|
+
if (state.status === "ended" || state.status === "expired") {
|
|
260
|
+
this.closeControlChannel();
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
);
|
|
264
|
+
}).catch(() => {
|
|
265
|
+
if (this.controlChannel === channel) this.closeControlChannel();
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
closeControlChannel() {
|
|
269
|
+
for (const unsubscribe of this.controlUnsubscribers) {
|
|
270
|
+
try {
|
|
271
|
+
unsubscribe();
|
|
272
|
+
} catch (e5) {
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
this.controlUnsubscribers = [];
|
|
276
|
+
try {
|
|
277
|
+
_optionalChain([this, 'access', _9 => _9.controlChannel, 'optionalAccess', _10 => _10.close, 'call', _11 => _11()]);
|
|
278
|
+
} catch (e6) {
|
|
279
|
+
}
|
|
280
|
+
this.controlChannel = null;
|
|
281
|
+
this.controlSessionId = null;
|
|
282
|
+
this.handledCommandIds.clear();
|
|
283
|
+
}
|
|
284
|
+
async executeCommands(channel, grant, commands) {
|
|
285
|
+
for (const command of commands) {
|
|
286
|
+
if (this.controlChannel !== channel) return;
|
|
287
|
+
if (this.handledCommandIds.has(command.id)) continue;
|
|
288
|
+
this.handledCommandIds.add(command.id);
|
|
289
|
+
if (this.executing) {
|
|
290
|
+
await this.safeAck(channel, grant, command.id, "failed", "Thi\u1EBFt b\u1ECB \u0111ang b\u1EADn");
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
this.executing = true;
|
|
294
|
+
try {
|
|
295
|
+
await this.executeCommand(channel, grant, command);
|
|
296
|
+
} finally {
|
|
297
|
+
this.executing = false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async executeCommand(channel, grant, command) {
|
|
302
|
+
if (command.expiresAt <= Date.now()) {
|
|
303
|
+
await this.safeAck(channel, grant, command.id, "failed", "L\u1EC7nh \u0111\xE3 h\u1EBFt h\u1EA1n");
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
await this.safeAck(channel, grant, command.id, "acked");
|
|
307
|
+
try {
|
|
308
|
+
if (command.type === "start_livestream") {
|
|
309
|
+
await this.runStartLivestream(command);
|
|
310
|
+
const sessionId = this.options.engine.getSnapshot().sessionId;
|
|
311
|
+
await this.safeAck(
|
|
312
|
+
channel,
|
|
313
|
+
grant,
|
|
314
|
+
command.id,
|
|
315
|
+
"completed",
|
|
316
|
+
void 0,
|
|
317
|
+
_nullishCoalesce(sessionId, () => ( void 0))
|
|
318
|
+
);
|
|
319
|
+
if (sessionId) _optionalChain([this, 'access', _12 => _12.options, 'access', _13 => _13.onRemoteSessionStarted, 'optionalCall', _14 => _14(sessionId)]);
|
|
320
|
+
} else {
|
|
321
|
+
await this.runStopLivestream();
|
|
322
|
+
await this.safeAck(channel, grant, command.id, "completed");
|
|
323
|
+
}
|
|
324
|
+
} catch (error) {
|
|
325
|
+
await this.safeAck(
|
|
326
|
+
channel,
|
|
327
|
+
grant,
|
|
328
|
+
command.id,
|
|
329
|
+
"failed",
|
|
330
|
+
_chunkTBPR2QNDcjs.normalizeError.call(void 0, error).message
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async runStartLivestream(command) {
|
|
335
|
+
const engine = this.options.engine;
|
|
336
|
+
const state = engine.getSnapshot().state;
|
|
337
|
+
if (state !== "idle" && state !== "ended" && state !== "preview") {
|
|
338
|
+
throw new Error(`Thi\u1EBFt b\u1ECB kh\xF4ng s\u1EB5n s\xE0ng (tr\u1EA1ng th\xE1i ${state})`);
|
|
339
|
+
}
|
|
340
|
+
if (state !== "preview") {
|
|
341
|
+
await engine.prepare();
|
|
342
|
+
}
|
|
343
|
+
const remote = this.options.remoteStart;
|
|
344
|
+
await engine.start(
|
|
345
|
+
{
|
|
346
|
+
externalSessionId: `remote-${command.id}`,
|
|
347
|
+
title: _optionalChain([command, 'access', _15 => _15.payload, 'access', _16 => _16.title, 'optionalAccess', _17 => _17.trim, 'call', _18 => _18()]) || remote.defaultTitle || "Livestream",
|
|
348
|
+
visibility: _nullishCoalesce(_nullishCoalesce(command.payload.visibility, () => ( remote.visibility)), () => ( "public")),
|
|
349
|
+
consentVersion: remote.consentVersion,
|
|
350
|
+
metadata: { ...remote.metadata, deviceCommandId: command.id },
|
|
351
|
+
standby: true
|
|
352
|
+
},
|
|
353
|
+
{ autoPublish: false }
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
async runStopLivestream() {
|
|
357
|
+
const engine = this.options.engine;
|
|
358
|
+
if (engine.getSnapshot().sessionId) {
|
|
359
|
+
await engine.stop("user");
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
async safeAck(channel, grant, commandId, status, errorMessage, resultSessionId) {
|
|
363
|
+
try {
|
|
364
|
+
await channel.ack({
|
|
365
|
+
controlSessionId: grant.id,
|
|
366
|
+
token: grant.token,
|
|
367
|
+
commandId,
|
|
368
|
+
status,
|
|
369
|
+
...errorMessage !== void 0 ? { errorMessage } : {},
|
|
370
|
+
...resultSessionId !== void 0 ? { resultSessionId } : {}
|
|
371
|
+
});
|
|
372
|
+
} catch (e7) {
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}, _class);
|
|
376
|
+
|
|
377
|
+
// src/device-agent/http-transport.ts
|
|
378
|
+
var defaultPaths = {
|
|
379
|
+
register: "/devices/register",
|
|
380
|
+
heartbeat: "/devices/heartbeat"
|
|
381
|
+
};
|
|
382
|
+
function createHttpDeviceAgentTransport(options) {
|
|
383
|
+
const baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
384
|
+
if (baseUrl === "") {
|
|
385
|
+
throw new (0, _chunkTBPR2QNDcjs.ExpoSdkConfigurationError)("baseUrl must be a non-empty string");
|
|
386
|
+
}
|
|
387
|
+
const fetchImplementation = _nullishCoalesce(options.fetch, () => ( globalThis.fetch));
|
|
388
|
+
if (typeof fetchImplementation !== "function") {
|
|
389
|
+
throw new (0, _chunkTBPR2QNDcjs.ExpoSdkConfigurationError)("A Fetch API implementation is required");
|
|
390
|
+
}
|
|
391
|
+
const paths = { ...defaultPaths, ...options.paths };
|
|
392
|
+
async function post(path, body) {
|
|
393
|
+
const controller = new AbortController();
|
|
394
|
+
const timeout = setTimeout(() => controller.abort(), _nullishCoalesce(options.timeoutMs, () => ( 1e4)));
|
|
395
|
+
try {
|
|
396
|
+
const extraHeaders = typeof options.headers === "function" ? await options.headers() : _nullishCoalesce(options.headers, () => ( {}));
|
|
397
|
+
const response = await fetchImplementation(
|
|
398
|
+
`${baseUrl}/${path.replace(/^\/+/, "")}`,
|
|
399
|
+
{
|
|
400
|
+
method: "POST",
|
|
401
|
+
headers: { "Content-Type": "application/json", ...extraHeaders },
|
|
402
|
+
body: JSON.stringify(body),
|
|
403
|
+
signal: controller.signal,
|
|
404
|
+
redirect: "error"
|
|
405
|
+
}
|
|
406
|
+
);
|
|
407
|
+
const envelope = await response.json().catch(() => {
|
|
408
|
+
throw new (0, _chunkTBPR2QNDcjs.InvalidResponseError)();
|
|
409
|
+
});
|
|
410
|
+
if (!response.ok) throw parseProxyError(envelope);
|
|
411
|
+
if (!isRecord(envelope) || !("data" in envelope)) {
|
|
412
|
+
throw new (0, _chunkTBPR2QNDcjs.InvalidResponseError)();
|
|
413
|
+
}
|
|
414
|
+
return envelope.data;
|
|
415
|
+
} catch (error) {
|
|
416
|
+
if (controller.signal.aborted) throw new (0, _chunkTBPR2QNDcjs.ExpoSdkTimeoutError)();
|
|
417
|
+
if (error instanceof _chunkTBPR2QNDcjs.PickleballExpoError) throw error;
|
|
418
|
+
if (error instanceof TypeError) {
|
|
419
|
+
throw new (0, _chunkTBPR2QNDcjs.NetworkUnavailableError)(void 0, { cause: error });
|
|
420
|
+
}
|
|
421
|
+
throw error;
|
|
422
|
+
} finally {
|
|
423
|
+
clearTimeout(timeout);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
async register(input) {
|
|
428
|
+
const data = await post(paths.register, input);
|
|
429
|
+
if (!isRecord(data) || typeof data.deviceId !== "string" || typeof data.heartbeatIntervalMs !== "number" || !Number.isFinite(data.heartbeatIntervalMs)) {
|
|
430
|
+
throw new (0, _chunkTBPR2QNDcjs.InvalidResponseError)();
|
|
431
|
+
}
|
|
432
|
+
return {
|
|
433
|
+
deviceId: data.deviceId,
|
|
434
|
+
heartbeatIntervalMs: data.heartbeatIntervalMs
|
|
435
|
+
};
|
|
436
|
+
},
|
|
437
|
+
async heartbeat(input) {
|
|
438
|
+
const data = await post(paths.heartbeat, input);
|
|
439
|
+
if (!isRecord(data)) throw new (0, _chunkTBPR2QNDcjs.InvalidResponseError)();
|
|
440
|
+
const grant = data.controlSession;
|
|
441
|
+
if (grant === null || grant === void 0) return { controlSession: null };
|
|
442
|
+
if (!isRecord(grant) || typeof grant.id !== "string" || typeof grant.token !== "string" || typeof grant.convexUrl !== "string" || grant.convexUrl === "") {
|
|
443
|
+
throw new (0, _chunkTBPR2QNDcjs.InvalidResponseError)();
|
|
444
|
+
}
|
|
445
|
+
return {
|
|
446
|
+
controlSession: {
|
|
447
|
+
id: grant.id,
|
|
448
|
+
token: grant.token,
|
|
449
|
+
convexUrl: grant.convexUrl
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
function parseProxyError(envelope) {
|
|
456
|
+
if (isRecord(envelope) && isRecord(envelope.error) && typeof envelope.error.code === "string") {
|
|
457
|
+
return _chunkTBPR2QNDcjs.errorFromCode.call(void 0,
|
|
458
|
+
envelope.error.code,
|
|
459
|
+
typeof envelope.error.message === "string" ? envelope.error.message : void 0
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
return new (0, _chunkTBPR2QNDcjs.InvalidResponseError)();
|
|
463
|
+
}
|
|
464
|
+
function isRecord(value) {
|
|
465
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// src/device-agent/index.ts
|
|
469
|
+
init_convex_control_channel();
|
|
470
|
+
|
|
471
|
+
// src/device-agent/component.tsx
|
|
472
|
+
var _react = require('react');
|
|
473
|
+
function PickleballDeviceAgent(props) {
|
|
474
|
+
const host = _chunkTBPR2QNDcjs.useLivestreamHost.call(void 0, );
|
|
475
|
+
const propsRef = _react.useRef.call(void 0, props);
|
|
476
|
+
propsRef.current = props;
|
|
477
|
+
_react.useEffect.call(void 0, () => {
|
|
478
|
+
const agent = new DeviceAgent({
|
|
479
|
+
engine: host.engine,
|
|
480
|
+
bootstrapInput: host.bootstrapInput,
|
|
481
|
+
deviceMetrics: host.deviceMetrics,
|
|
482
|
+
transport: propsRef.current.transport,
|
|
483
|
+
remoteStart: propsRef.current.remoteStart,
|
|
484
|
+
deviceInfo: propsRef.current.deviceInfo,
|
|
485
|
+
createControlChannel: propsRef.current.createControlChannel,
|
|
486
|
+
onRemoteSessionStarted: (sessionId) => _optionalChain([propsRef, 'access', _19 => _19.current, 'access', _20 => _20.onRemoteSessionStarted, 'optionalCall', _21 => _21(sessionId)])
|
|
487
|
+
});
|
|
488
|
+
agent.start();
|
|
489
|
+
return () => agent.stop();
|
|
490
|
+
}, [host]);
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
exports.DeviceAgent = DeviceAgent; exports.PickleballDeviceAgent = PickleballDeviceAgent; exports.createConvexControlChannel = createConvexControlChannel; exports.createHttpDeviceAgentTransport = createHttpDeviceAgentTransport;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Q as LivestreamEngine, B as BootstrapInput, D as DeviceMetricsSample } from '../engine-CCSSqPbZ.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DeviceAgent — đăng ký thiết bị với backend khi app mở và giữ heartbeat
|
|
5
|
+
* presence (12s/lần khi foreground) qua proxy. Khi dashboard yêu cầu "Kết nối",
|
|
6
|
+
* heartbeat trả về connect-token → agent mở kênh điều khiển realtime (Convex
|
|
7
|
+
* websocket) để nhận lệnh start/stop livestream tức thì. Publish/unpublish sau
|
|
8
|
+
* khi vào standby vẫn đi đường LiveKit data channel sẵn có của engine.
|
|
9
|
+
*/
|
|
10
|
+
interface DeviceAgentDeviceInfo {
|
|
11
|
+
appVersion?: string;
|
|
12
|
+
deviceName?: string;
|
|
13
|
+
deviceModel?: string;
|
|
14
|
+
osVersion?: string;
|
|
15
|
+
}
|
|
16
|
+
interface DeviceCommand {
|
|
17
|
+
id: string;
|
|
18
|
+
type: "start_livestream" | "stop_livestream";
|
|
19
|
+
payload: {
|
|
20
|
+
title?: string;
|
|
21
|
+
visibility?: "public" | "private";
|
|
22
|
+
};
|
|
23
|
+
expiresAt: number;
|
|
24
|
+
}
|
|
25
|
+
interface DeviceControlSessionGrant {
|
|
26
|
+
id: string;
|
|
27
|
+
token: string;
|
|
28
|
+
convexUrl: string;
|
|
29
|
+
}
|
|
30
|
+
interface DeviceHeartbeatBody {
|
|
31
|
+
agentState?: string;
|
|
32
|
+
currentSessionId?: string | null;
|
|
33
|
+
appVersion?: string;
|
|
34
|
+
metrics?: Record<string, number | string>;
|
|
35
|
+
}
|
|
36
|
+
interface DeviceAgentTransport {
|
|
37
|
+
register(input: BootstrapInput & DeviceAgentDeviceInfo): Promise<{
|
|
38
|
+
deviceId: string;
|
|
39
|
+
heartbeatIntervalMs: number;
|
|
40
|
+
}>;
|
|
41
|
+
heartbeat(input: BootstrapInput & DeviceHeartbeatBody): Promise<{
|
|
42
|
+
controlSession: DeviceControlSessionGrant | null;
|
|
43
|
+
}>;
|
|
44
|
+
}
|
|
45
|
+
/** Kênh điều khiển realtime — mặc định là Convex websocket (convex-control-channel). */
|
|
46
|
+
interface DeviceControlChannel {
|
|
47
|
+
attach(controlSessionId: string, token: string): Promise<void>;
|
|
48
|
+
/** Subscribe lệnh pending — callback gọi lại mỗi khi danh sách đổi. */
|
|
49
|
+
onCommands(controlSessionId: string, token: string, callback: (commands: DeviceCommand[]) => void): () => void;
|
|
50
|
+
/** Subscribe trạng thái kênh — "ended"/"expired" thì agent tự đóng. */
|
|
51
|
+
onState(controlSessionId: string, token: string, callback: (state: {
|
|
52
|
+
status: string;
|
|
53
|
+
}) => void): () => void;
|
|
54
|
+
ack(input: {
|
|
55
|
+
controlSessionId: string;
|
|
56
|
+
token: string;
|
|
57
|
+
commandId: string;
|
|
58
|
+
status: "acked" | "completed" | "failed";
|
|
59
|
+
errorMessage?: string;
|
|
60
|
+
resultSessionId?: string;
|
|
61
|
+
}): Promise<void>;
|
|
62
|
+
close(): void;
|
|
63
|
+
}
|
|
64
|
+
interface RemoteStartConfig {
|
|
65
|
+
consentVersion: string;
|
|
66
|
+
/** Tiêu đề mặc định khi dashboard không gửi title. */
|
|
67
|
+
defaultTitle?: string;
|
|
68
|
+
visibility?: "public" | "private";
|
|
69
|
+
metadata?: Record<string, string>;
|
|
70
|
+
}
|
|
71
|
+
type AppStateListener = (state: "active" | "background" | "inactive") => void;
|
|
72
|
+
interface DeviceAgentOptions {
|
|
73
|
+
engine: LivestreamEngine;
|
|
74
|
+
bootstrapInput: BootstrapInput;
|
|
75
|
+
transport: DeviceAgentTransport;
|
|
76
|
+
remoteStart: RemoteStartConfig;
|
|
77
|
+
deviceInfo?: DeviceAgentDeviceInfo;
|
|
78
|
+
onRemoteSessionStarted?: (sessionId: string) => void;
|
|
79
|
+
deviceMetrics?: () => Promise<DeviceMetricsSample | null>;
|
|
80
|
+
/** Factory kênh điều khiển — test truyền fake; mặc định Convex websocket. */
|
|
81
|
+
createControlChannel?: (convexUrl: string) => DeviceControlChannel;
|
|
82
|
+
/** Test hook: nguồn app-state; mặc định react-native AppState. */
|
|
83
|
+
subscribeAppState?: (listener: AppStateListener) => () => void;
|
|
84
|
+
}
|
|
85
|
+
declare class DeviceAgent {
|
|
86
|
+
private readonly options;
|
|
87
|
+
private running;
|
|
88
|
+
private registered;
|
|
89
|
+
private foreground;
|
|
90
|
+
private heartbeatIntervalMs;
|
|
91
|
+
private timer;
|
|
92
|
+
private beatInFlight;
|
|
93
|
+
private registerAttempt;
|
|
94
|
+
private unsubscribeAppState;
|
|
95
|
+
private heartbeatFailures;
|
|
96
|
+
private controlChannel;
|
|
97
|
+
private controlSessionId;
|
|
98
|
+
private controlUnsubscribers;
|
|
99
|
+
private executing;
|
|
100
|
+
private readonly handledCommandIds;
|
|
101
|
+
constructor(options: DeviceAgentOptions);
|
|
102
|
+
start(): void;
|
|
103
|
+
stop(): void;
|
|
104
|
+
private handleAppState;
|
|
105
|
+
private schedule;
|
|
106
|
+
private beat;
|
|
107
|
+
private register;
|
|
108
|
+
private heartbeat;
|
|
109
|
+
private openControlChannel;
|
|
110
|
+
private closeControlChannel;
|
|
111
|
+
private executeCommands;
|
|
112
|
+
private executeCommand;
|
|
113
|
+
private runStartLivestream;
|
|
114
|
+
private runStopLivestream;
|
|
115
|
+
private safeAck;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
type Fetch = typeof globalThis.fetch;
|
|
119
|
+
type HeadersInput = Record<string, string>;
|
|
120
|
+
interface HttpDeviceAgentTransportOptions {
|
|
121
|
+
/** Base URL của proxy đối tác (cùng proxy với session provider). */
|
|
122
|
+
baseUrl: string;
|
|
123
|
+
fetch?: Fetch;
|
|
124
|
+
headers?: HeadersInput | (() => HeadersInput | Promise<HeadersInput>);
|
|
125
|
+
timeoutMs?: number;
|
|
126
|
+
paths?: Partial<{
|
|
127
|
+
register: string;
|
|
128
|
+
heartbeat: string;
|
|
129
|
+
}>;
|
|
130
|
+
}
|
|
131
|
+
/** Transport HTTP cho DeviceAgent — mirror createHttpSessionProvider. */
|
|
132
|
+
declare function createHttpDeviceAgentTransport(options: HttpDeviceAgentTransportOptions): DeviceAgentTransport;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Kênh điều khiển realtime mặc định: Convex websocket subscribe các function
|
|
136
|
+
* deviceControl (auth bằng connect-token nhận qua heartbeat — thiết bị không
|
|
137
|
+
* có account, pattern pairingCode). Chỉ mở khi dashboard yêu cầu "Kết nối",
|
|
138
|
+
* đóng ngay khi kênh kết thúc → không giữ websocket lúc idle.
|
|
139
|
+
*/
|
|
140
|
+
declare function createConvexControlChannel(convexUrl: string): DeviceControlChannel;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Bật device-agent: đăng ký thiết bị + heartbeat presence khi app mở, nhận
|
|
144
|
+
* lệnh start/stop livestream từ dashboard qua kênh điều khiển on-demand.
|
|
145
|
+
* Render bên trong PickleballLiveProvider (thường là child đầu tiên).
|
|
146
|
+
* Tách khỏi provider để app không dùng tính năng này không bundle convex.
|
|
147
|
+
*/
|
|
148
|
+
interface PickleballDeviceAgentProps {
|
|
149
|
+
transport: DeviceAgentTransport;
|
|
150
|
+
remoteStart: RemoteStartConfig;
|
|
151
|
+
deviceInfo?: DeviceAgentDeviceInfo;
|
|
152
|
+
onRemoteSessionStarted?: (sessionId: string) => void;
|
|
153
|
+
createControlChannel?: (convexUrl: string) => DeviceControlChannel;
|
|
154
|
+
}
|
|
155
|
+
declare function PickleballDeviceAgent(props: PickleballDeviceAgentProps): null;
|
|
156
|
+
|
|
157
|
+
export { DeviceAgent, type DeviceAgentDeviceInfo, type DeviceAgentOptions, type DeviceAgentTransport, type DeviceCommand, type DeviceControlChannel, type DeviceControlSessionGrant, type DeviceHeartbeatBody, type HttpDeviceAgentTransportOptions, PickleballDeviceAgent, type PickleballDeviceAgentProps, type RemoteStartConfig, createConvexControlChannel, createHttpDeviceAgentTransport };
|