@pickleball/expo-sdk 0.1.1
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 +14 -0
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/app.plugin.js +6 -0
- package/dist/chunk-VHLUJQG5.js +17 -0
- package/dist/index.cjs +6284 -0
- package/dist/index.d.cts +1436 -0
- package/dist/index.d.ts +1436 -0
- package/dist/index.js +6186 -0
- package/dist/plugin.cjs +153 -0
- package/dist/plugin.d.cts +21 -0
- package/dist/plugin.d.ts +21 -0
- package/dist/plugin.js +131 -0
- package/dist/version.cjs +35 -0
- package/dist/version.d.cts +4 -0
- package/dist/version.d.ts +4 -0
- package/dist/version.js +6 -0
- package/package.json +126 -0
- package/src/contracts.ts +211 -0
- package/src/engine.ts +1332 -0
- package/src/errors.ts +128 -0
- package/src/http-session-provider.ts +259 -0
- package/src/index.ts +16 -0
- package/src/livekit-room-adapter.ts +353 -0
- package/src/livestream-screen.tsx +980 -0
- package/src/match/contracts.ts +137 -0
- package/src/match/engine.ts +702 -0
- package/src/match/persistence/default-store.native.ts +20 -0
- package/src/match/persistence/default-store.ts +11 -0
- package/src/match/persistence/index.ts +6 -0
- package/src/match/persistence/memory-driver.ts +255 -0
- package/src/match/persistence/migrations.ts +40 -0
- package/src/match/persistence/sqlite-driver.ts +348 -0
- package/src/match/persistence/store.ts +497 -0
- package/src/match/persistence/types.ts +98 -0
- package/src/match/provider.tsx +728 -0
- package/src/match/remote/deduplicator.ts +54 -0
- package/src/match/remote/encoding.ts +12 -0
- package/src/match/remote/hid-gesture.ts +61 -0
- package/src/match/remote/index.ts +5 -0
- package/src/match/remote/native-service.ts +62 -0
- package/src/match/remote/parser.ts +134 -0
- package/src/match/remote/types.ts +116 -0
- package/src/match/replay.ts +102 -0
- package/src/match/state.ts +72 -0
- package/src/match/validation.ts +375 -0
- package/src/match/voice/assets/court-left.wav +0 -0
- package/src/match/voice/assets/court-right.wav +0 -0
- package/src/match/voice/assets/court-switch.wav +0 -0
- package/src/match/voice/assets/game-point.wav +0 -0
- package/src/match/voice/assets/game-won.wav +0 -0
- package/src/match/voice/assets/match-won.wav +0 -0
- package/src/match/voice/assets/number-0.wav +0 -0
- package/src/match/voice/assets/number-1.wav +0 -0
- package/src/match/voice/assets/number-10.wav +0 -0
- package/src/match/voice/assets/number-11.wav +0 -0
- package/src/match/voice/assets/number-12.wav +0 -0
- package/src/match/voice/assets/number-13.wav +0 -0
- package/src/match/voice/assets/number-14.wav +0 -0
- package/src/match/voice/assets/number-15.wav +0 -0
- package/src/match/voice/assets/number-16.wav +0 -0
- package/src/match/voice/assets/number-17.wav +0 -0
- package/src/match/voice/assets/number-18.wav +0 -0
- package/src/match/voice/assets/number-19.wav +0 -0
- package/src/match/voice/assets/number-2.wav +0 -0
- package/src/match/voice/assets/number-20.wav +0 -0
- package/src/match/voice/assets/number-3.wav +0 -0
- package/src/match/voice/assets/number-30.wav +0 -0
- package/src/match/voice/assets/number-4.wav +0 -0
- package/src/match/voice/assets/number-40.wav +0 -0
- package/src/match/voice/assets/number-5.wav +0 -0
- package/src/match/voice/assets/number-50.wav +0 -0
- package/src/match/voice/assets/number-6.wav +0 -0
- package/src/match/voice/assets/number-60.wav +0 -0
- package/src/match/voice/assets/number-7.wav +0 -0
- package/src/match/voice/assets/number-70.wav +0 -0
- package/src/match/voice/assets/number-8.wav +0 -0
- package/src/match/voice/assets/number-80.wav +0 -0
- package/src/match/voice/assets/number-9.wav +0 -0
- package/src/match/voice/assets/number-90.wav +0 -0
- package/src/match/voice/assets/number-lam.wav +0 -0
- package/src/match/voice/assets/number-mot.wav +0 -0
- package/src/match/voice/assets/score-deu-0.wav +0 -0
- package/src/match/voice/assets/score-deu-1.wav +0 -0
- package/src/match/voice/assets/score-deu-10.wav +0 -0
- package/src/match/voice/assets/score-deu-11.wav +0 -0
- package/src/match/voice/assets/score-deu-12.wav +0 -0
- package/src/match/voice/assets/score-deu-13.wav +0 -0
- package/src/match/voice/assets/score-deu-14.wav +0 -0
- package/src/match/voice/assets/score-deu-15.wav +0 -0
- package/src/match/voice/assets/score-deu-16.wav +0 -0
- package/src/match/voice/assets/score-deu-17.wav +0 -0
- package/src/match/voice/assets/score-deu-18.wav +0 -0
- package/src/match/voice/assets/score-deu-19.wav +0 -0
- package/src/match/voice/assets/score-deu-2.wav +0 -0
- package/src/match/voice/assets/score-deu-20.wav +0 -0
- package/src/match/voice/assets/score-deu-3.wav +0 -0
- package/src/match/voice/assets/score-deu-30.wav +0 -0
- package/src/match/voice/assets/score-deu-4.wav +0 -0
- package/src/match/voice/assets/score-deu-40.wav +0 -0
- package/src/match/voice/assets/score-deu-5.wav +0 -0
- package/src/match/voice/assets/score-deu-50.wav +0 -0
- package/src/match/voice/assets/score-deu-6.wav +0 -0
- package/src/match/voice/assets/score-deu-60.wav +0 -0
- package/src/match/voice/assets/score-deu-7.wav +0 -0
- package/src/match/voice/assets/score-deu-70.wav +0 -0
- package/src/match/voice/assets/score-deu-8.wav +0 -0
- package/src/match/voice/assets/score-deu-80.wav +0 -0
- package/src/match/voice/assets/score-deu-9.wav +0 -0
- package/src/match/voice/assets/score-deu-90.wav +0 -0
- package/src/match/voice/assets/score-deu-lam.wav +0 -0
- package/src/match/voice/assets/score-deu-mot.wav +0 -0
- package/src/match/voice/assets/server-1.wav +0 -0
- package/src/match/voice/assets/server-2.wav +0 -0
- package/src/match/voice/assets/service-change.wav +0 -0
- package/src/match/voice/assets/test-speaker.wav +0 -0
- package/src/match/voice/audio.ts +231 -0
- package/src/match/voice/bundled-assets.native.ts +47 -0
- package/src/match/voice/bundled-assets.ts +3 -0
- package/src/match/voice/gemini-engine.ts +167 -0
- package/src/match/voice/index.ts +7 -0
- package/src/match/voice/phrases.ts +185 -0
- package/src/match/voice/scheduler.ts +205 -0
- package/src/match/voice/service.ts +147 -0
- package/src/match/voice/types.ts +118 -0
- package/src/native-runtime.ts +241 -0
- package/src/plugin.ts +145 -0
- package/src/provider.tsx +176 -0
- package/src/version.ts +4 -0
package/src/errors.ts
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { SdkErrorCode } from "./contracts";
|
|
2
|
+
|
|
3
|
+
export type ExpoSdkErrorCode =
|
|
4
|
+
| SdkErrorCode
|
|
5
|
+
| "CONFIGURATION_ERROR"
|
|
6
|
+
| "UNSUPPORTED_RUNTIME"
|
|
7
|
+
| "INVALID_RESPONSE"
|
|
8
|
+
| "TIMEOUT";
|
|
9
|
+
|
|
10
|
+
export class PickleballExpoError extends Error {
|
|
11
|
+
constructor(
|
|
12
|
+
message: string,
|
|
13
|
+
readonly code: ExpoSdkErrorCode,
|
|
14
|
+
options?: ErrorOptions,
|
|
15
|
+
) {
|
|
16
|
+
super(message, options);
|
|
17
|
+
this.name = "PickleballExpoError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function namedError(
|
|
22
|
+
name: string,
|
|
23
|
+
code: ExpoSdkErrorCode,
|
|
24
|
+
fallback: string,
|
|
25
|
+
) {
|
|
26
|
+
return class extends PickleballExpoError {
|
|
27
|
+
constructor(message = fallback, options?: ErrorOptions) {
|
|
28
|
+
super(message, code, options);
|
|
29
|
+
this.name = name;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class PermissionDeniedError extends namedError(
|
|
35
|
+
"PermissionDeniedError",
|
|
36
|
+
"PERMISSION_DENIED",
|
|
37
|
+
"Camera and microphone permissions are required",
|
|
38
|
+
) {}
|
|
39
|
+
export class NetworkUnavailableError extends namedError(
|
|
40
|
+
"NetworkUnavailableError",
|
|
41
|
+
"NETWORK_UNAVAILABLE",
|
|
42
|
+
"A network connection is required",
|
|
43
|
+
) {}
|
|
44
|
+
export class TokenExpiredError extends namedError(
|
|
45
|
+
"TokenExpiredError",
|
|
46
|
+
"TOKEN_EXPIRED",
|
|
47
|
+
"The livestream token expired",
|
|
48
|
+
) {}
|
|
49
|
+
export class UnsupportedSdkError extends namedError(
|
|
50
|
+
"UnsupportedSdkError",
|
|
51
|
+
"SDK_UPGRADE_REQUIRED",
|
|
52
|
+
"This SDK version is no longer supported",
|
|
53
|
+
) {}
|
|
54
|
+
export class UnsupportedRuntimeError extends namedError(
|
|
55
|
+
"UnsupportedRuntimeError",
|
|
56
|
+
"UNSUPPORTED_RUNTIME",
|
|
57
|
+
"Pickleball Live requires an iOS or Android Expo Development Build; Expo Go and web are unsupported",
|
|
58
|
+
) {}
|
|
59
|
+
export class SessionConflictError extends namedError(
|
|
60
|
+
"SessionConflictError",
|
|
61
|
+
"SESSION_CONFLICT",
|
|
62
|
+
"This livestream session conflicts with an active session",
|
|
63
|
+
) {}
|
|
64
|
+
export class RecordingFailureError extends namedError(
|
|
65
|
+
"RecordingFailureError",
|
|
66
|
+
"RECORDING_FAILED",
|
|
67
|
+
"The server recording could not be created",
|
|
68
|
+
) {}
|
|
69
|
+
export class ConsentRequiredError extends namedError(
|
|
70
|
+
"ConsentRequiredError",
|
|
71
|
+
"CONSENT_REQUIRED",
|
|
72
|
+
"Recording consent is required",
|
|
73
|
+
) {}
|
|
74
|
+
export class SdkDisabledError extends namedError(
|
|
75
|
+
"SdkDisabledError",
|
|
76
|
+
"SDK_DISABLED",
|
|
77
|
+
"Livestreaming is disabled for this app",
|
|
78
|
+
) {}
|
|
79
|
+
export class ExpoSdkConfigurationError extends namedError(
|
|
80
|
+
"ExpoSdkConfigurationError",
|
|
81
|
+
"CONFIGURATION_ERROR",
|
|
82
|
+
"The Expo SDK is not configured correctly",
|
|
83
|
+
) {}
|
|
84
|
+
export class InvalidResponseError extends namedError(
|
|
85
|
+
"InvalidResponseError",
|
|
86
|
+
"INVALID_RESPONSE",
|
|
87
|
+
"The session server returned an invalid response",
|
|
88
|
+
) {}
|
|
89
|
+
export class ExpoSdkTimeoutError extends namedError(
|
|
90
|
+
"ExpoSdkTimeoutError",
|
|
91
|
+
"TIMEOUT",
|
|
92
|
+
"The request timed out",
|
|
93
|
+
) {}
|
|
94
|
+
export class UnknownSdkError extends namedError(
|
|
95
|
+
"UnknownSdkError",
|
|
96
|
+
"UNKNOWN",
|
|
97
|
+
"An unknown livestream error occurred",
|
|
98
|
+
) {}
|
|
99
|
+
|
|
100
|
+
const known: Record<SdkErrorCode, new (message?: string) => PickleballExpoError> = {
|
|
101
|
+
PERMISSION_DENIED: PermissionDeniedError,
|
|
102
|
+
NETWORK_UNAVAILABLE: NetworkUnavailableError,
|
|
103
|
+
TOKEN_EXPIRED: TokenExpiredError,
|
|
104
|
+
SDK_UPGRADE_REQUIRED: UnsupportedSdkError,
|
|
105
|
+
SESSION_CONFLICT: SessionConflictError,
|
|
106
|
+
RECORDING_FAILED: RecordingFailureError,
|
|
107
|
+
CONSENT_REQUIRED: ConsentRequiredError,
|
|
108
|
+
SDK_DISABLED: SdkDisabledError,
|
|
109
|
+
UNKNOWN: UnknownSdkError,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export function errorFromCode(code: string, message?: string) {
|
|
113
|
+
const Constructor = known[code as SdkErrorCode] ?? UnknownSdkError;
|
|
114
|
+
return new Constructor(message);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function normalizeError(error: unknown): PickleballExpoError {
|
|
118
|
+
if (error instanceof PickleballExpoError) return error;
|
|
119
|
+
if (error instanceof Error) {
|
|
120
|
+
const code = (error as Error & { code?: string }).code;
|
|
121
|
+
if (code) return errorFromCode(code, error.message);
|
|
122
|
+
if (error instanceof TypeError) {
|
|
123
|
+
return new NetworkUnavailableError(undefined, { cause: error });
|
|
124
|
+
}
|
|
125
|
+
return new UnknownSdkError(error.message, { cause: error });
|
|
126
|
+
}
|
|
127
|
+
return new UnknownSdkError();
|
|
128
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BootstrapInput,
|
|
3
|
+
EndLivestreamInput,
|
|
4
|
+
LivestreamSessionProvider,
|
|
5
|
+
SdkBootstrap,
|
|
6
|
+
SdkSessionGrant,
|
|
7
|
+
SdkSessionRefresh,
|
|
8
|
+
SdkTelemetryCredentials,
|
|
9
|
+
StartLivestreamInput,
|
|
10
|
+
} from "./contracts";
|
|
11
|
+
import { isSdkRemoteConfig } from "./contracts";
|
|
12
|
+
import {
|
|
13
|
+
ExpoSdkConfigurationError,
|
|
14
|
+
ExpoSdkTimeoutError,
|
|
15
|
+
InvalidResponseError,
|
|
16
|
+
NetworkUnavailableError,
|
|
17
|
+
PickleballExpoError,
|
|
18
|
+
errorFromCode,
|
|
19
|
+
} from "./errors";
|
|
20
|
+
|
|
21
|
+
type Fetch = typeof globalThis.fetch;
|
|
22
|
+
type HeadersInput = Record<string, string>;
|
|
23
|
+
|
|
24
|
+
export interface HttpSessionProviderPaths {
|
|
25
|
+
bootstrap: string;
|
|
26
|
+
sessions: string;
|
|
27
|
+
refresh(sessionId: string): string;
|
|
28
|
+
end(sessionId: string): string;
|
|
29
|
+
publish(sessionId: string): string;
|
|
30
|
+
unpublish(sessionId: string): string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface HttpSessionProviderOptions {
|
|
34
|
+
/** Base URL of the partner-owned mobile proxy. Never the Pickleball API URL. */
|
|
35
|
+
baseUrl: string;
|
|
36
|
+
fetch?: Fetch;
|
|
37
|
+
headers?: HeadersInput | (() => HeadersInput | Promise<HeadersInput>);
|
|
38
|
+
timeoutMs?: number;
|
|
39
|
+
paths?: Partial<HttpSessionProviderPaths>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const defaultPaths: HttpSessionProviderPaths = {
|
|
43
|
+
bootstrap: "/bootstrap",
|
|
44
|
+
sessions: "/sessions",
|
|
45
|
+
refresh: (sessionId) => `/sessions/${encodeURIComponent(sessionId)}/refresh`,
|
|
46
|
+
end: (sessionId) => `/sessions/${encodeURIComponent(sessionId)}/end`,
|
|
47
|
+
publish: (sessionId) => `/sessions/${encodeURIComponent(sessionId)}/publish`,
|
|
48
|
+
unpublish: (sessionId) =>
|
|
49
|
+
`/sessions/${encodeURIComponent(sessionId)}/unpublish`,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export function createHttpSessionProvider(
|
|
53
|
+
options: HttpSessionProviderOptions,
|
|
54
|
+
): LivestreamSessionProvider {
|
|
55
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
56
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
57
|
+
if (typeof fetchImplementation !== "function") {
|
|
58
|
+
throw new ExpoSdkConfigurationError("A Fetch API implementation is required");
|
|
59
|
+
}
|
|
60
|
+
const paths = { ...defaultPaths, ...options.paths };
|
|
61
|
+
|
|
62
|
+
async function post(path: string, body: unknown): Promise<unknown> {
|
|
63
|
+
const controller = new AbortController();
|
|
64
|
+
const timeout = setTimeout(
|
|
65
|
+
() => controller.abort(),
|
|
66
|
+
options.timeoutMs ?? 10_000,
|
|
67
|
+
);
|
|
68
|
+
try {
|
|
69
|
+
const extraHeaders =
|
|
70
|
+
typeof options.headers === "function"
|
|
71
|
+
? await options.headers()
|
|
72
|
+
: (options.headers ?? {});
|
|
73
|
+
const response = await fetchImplementation(joinUrl(baseUrl, path), {
|
|
74
|
+
method: "POST",
|
|
75
|
+
headers: { "Content-Type": "application/json", ...extraHeaders },
|
|
76
|
+
body: JSON.stringify(body),
|
|
77
|
+
signal: controller.signal,
|
|
78
|
+
redirect: "error",
|
|
79
|
+
});
|
|
80
|
+
const envelope = await response.json().catch(() => {
|
|
81
|
+
throw new InvalidResponseError();
|
|
82
|
+
});
|
|
83
|
+
if (!response.ok) throw parseProxyError(envelope);
|
|
84
|
+
if (!isRecord(envelope) || !("data" in envelope)) {
|
|
85
|
+
throw new InvalidResponseError();
|
|
86
|
+
}
|
|
87
|
+
return envelope.data;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (controller.signal.aborted) throw new ExpoSdkTimeoutError();
|
|
90
|
+
if (error instanceof PickleballExpoError) throw error;
|
|
91
|
+
if (error instanceof TypeError) {
|
|
92
|
+
throw new NetworkUnavailableError(undefined, { cause: error });
|
|
93
|
+
}
|
|
94
|
+
throw error;
|
|
95
|
+
} finally {
|
|
96
|
+
clearTimeout(timeout);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
async bootstrap(input: BootstrapInput) {
|
|
102
|
+
const data = await post(paths.bootstrap, input);
|
|
103
|
+
if (!isBootstrap(data)) throw new InvalidResponseError();
|
|
104
|
+
return data;
|
|
105
|
+
},
|
|
106
|
+
async start(input: StartLivestreamInput) {
|
|
107
|
+
const data = await post(paths.sessions, input);
|
|
108
|
+
if (!isSessionGrant(data)) throw new InvalidResponseError();
|
|
109
|
+
return data;
|
|
110
|
+
},
|
|
111
|
+
async refresh(sessionId: string) {
|
|
112
|
+
const data = await post(paths.refresh(sessionId), {});
|
|
113
|
+
if (!isSessionRefresh(data)) throw new InvalidResponseError();
|
|
114
|
+
return data;
|
|
115
|
+
},
|
|
116
|
+
async end(input: EndLivestreamInput) {
|
|
117
|
+
const data = await post(paths.end(input.sessionId), input);
|
|
118
|
+
if (data !== null && data !== undefined) throw new InvalidResponseError();
|
|
119
|
+
},
|
|
120
|
+
async publish(sessionId: string) {
|
|
121
|
+
const data = await post(paths.publish(sessionId), { sessionId });
|
|
122
|
+
if (!isSessionStatus(data)) throw new InvalidResponseError();
|
|
123
|
+
},
|
|
124
|
+
async unpublish(sessionId: string) {
|
|
125
|
+
const data = await post(paths.unpublish(sessionId), { sessionId });
|
|
126
|
+
if (!isSessionStatus(data)) throw new InvalidResponseError();
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
declare const __DEV__: boolean | undefined;
|
|
132
|
+
|
|
133
|
+
// RFC 1918 private ranges: 10/8, 172.16/12, 192.168/16.
|
|
134
|
+
const PRIVATE_LAN_HOST =
|
|
135
|
+
/^(?:10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3})$/;
|
|
136
|
+
|
|
137
|
+
function normalizeBaseUrl(value: string) {
|
|
138
|
+
try {
|
|
139
|
+
const url = new URL(value);
|
|
140
|
+
const loopbackHosts = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
141
|
+
const secure = url.protocol === "https:";
|
|
142
|
+
const localDevelopment =
|
|
143
|
+
url.protocol === "http:" && loopbackHosts.has(url.hostname);
|
|
144
|
+
// Dev bundle only (Metro/dev client): a device on the same network must
|
|
145
|
+
// reach the dev proxy over plain HTTP at a private LAN address. Release
|
|
146
|
+
// bundles compile __DEV__ to false, so production keeps the HTTPS rule.
|
|
147
|
+
const devPrivateLan =
|
|
148
|
+
typeof __DEV__ !== "undefined" &&
|
|
149
|
+
__DEV__ === true &&
|
|
150
|
+
url.protocol === "http:" &&
|
|
151
|
+
PRIVATE_LAN_HOST.test(url.hostname);
|
|
152
|
+
if (
|
|
153
|
+
(!secure && !localDevelopment && !devPrivateLan) ||
|
|
154
|
+
url.username ||
|
|
155
|
+
url.password
|
|
156
|
+
) {
|
|
157
|
+
throw new Error();
|
|
158
|
+
}
|
|
159
|
+
return value.replace(/\/+$/, "");
|
|
160
|
+
} catch {
|
|
161
|
+
throw new ExpoSdkConfigurationError(
|
|
162
|
+
"baseUrl must use HTTPS (HTTP is allowed only for localhost loopback)",
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function joinUrl(base: string, path: string) {
|
|
168
|
+
return `${base}/${path.replace(/^\/+/, "")}`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function parseProxyError(envelope: unknown) {
|
|
172
|
+
if (
|
|
173
|
+
isRecord(envelope) &&
|
|
174
|
+
isRecord(envelope.error) &&
|
|
175
|
+
typeof envelope.error.code === "string"
|
|
176
|
+
) {
|
|
177
|
+
return errorFromCode(
|
|
178
|
+
envelope.error.code,
|
|
179
|
+
typeof envelope.error.message === "string"
|
|
180
|
+
? envelope.error.message
|
|
181
|
+
: undefined,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return new InvalidResponseError();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
188
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function isFiniteNumber(value: unknown): value is number {
|
|
192
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function isBootstrap(value: unknown): value is SdkBootstrap {
|
|
196
|
+
return (
|
|
197
|
+
isRecord(value) &&
|
|
198
|
+
isSdkRemoteConfig(value.config) &&
|
|
199
|
+
isFiniteNumber(value.serverTime) &&
|
|
200
|
+
typeof value.rolloutEnabled === "boolean" &&
|
|
201
|
+
(value.message === undefined || typeof value.message === "string")
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function isTelemetry(value: unknown): value is SdkTelemetryCredentials {
|
|
206
|
+
return (
|
|
207
|
+
isRecord(value) &&
|
|
208
|
+
typeof value.endpoint === "string" &&
|
|
209
|
+
/^https:\/\//.test(value.endpoint) &&
|
|
210
|
+
typeof value.token === "string" &&
|
|
211
|
+
value.token.length > 0 &&
|
|
212
|
+
isFiniteNumber(value.expiresAt)
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function isSessionGrant(value: unknown): value is SdkSessionGrant {
|
|
217
|
+
return (
|
|
218
|
+
isRecord(value) &&
|
|
219
|
+
typeof value.sessionId === "string" &&
|
|
220
|
+
value.sessionId.length > 0 &&
|
|
221
|
+
typeof value.serverUrl === "string" &&
|
|
222
|
+
/^wss:\/\//.test(value.serverUrl) &&
|
|
223
|
+
typeof value.participantToken === "string" &&
|
|
224
|
+
value.participantToken.length > 0 &&
|
|
225
|
+
(value.playbackUrl === null || typeof value.playbackUrl === "string") &&
|
|
226
|
+
isFiniteNumber(value.tokenExpiresAt) &&
|
|
227
|
+
isTelemetry(value.telemetry) &&
|
|
228
|
+
isSdkRemoteConfig(value.config)
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function isSessionStatus(
|
|
233
|
+
value: unknown,
|
|
234
|
+
): value is { sessionId: string; status: string } {
|
|
235
|
+
return (
|
|
236
|
+
isRecord(value) &&
|
|
237
|
+
typeof value.sessionId === "string" &&
|
|
238
|
+
value.sessionId.length > 0 &&
|
|
239
|
+
typeof value.status === "string" &&
|
|
240
|
+
value.status.length > 0
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function isSessionRefresh(value: unknown): value is SdkSessionRefresh {
|
|
245
|
+
if (!isRecord(value) || !isSdkRemoteConfig(value.config)) return false;
|
|
246
|
+
if (
|
|
247
|
+
value.participantToken !== undefined &&
|
|
248
|
+
(typeof value.participantToken !== "string" || !value.participantToken)
|
|
249
|
+
) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
if (
|
|
253
|
+
value.tokenExpiresAt !== undefined &&
|
|
254
|
+
!isFiniteNumber(value.tokenExpiresAt)
|
|
255
|
+
) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
return value.telemetry === undefined || isTelemetry(value.telemetry);
|
|
259
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from "./contracts";
|
|
2
|
+
export * from "./match/contracts";
|
|
3
|
+
export * from "./match/engine";
|
|
4
|
+
export { replayMatchEvents, undoLastMatchEvent } from "./match/replay";
|
|
5
|
+
export * from "./match/persistence";
|
|
6
|
+
export * from "./match/provider";
|
|
7
|
+
export * from "./match/remote";
|
|
8
|
+
export * from "./match/voice";
|
|
9
|
+
export * from "./errors";
|
|
10
|
+
export * from "./engine";
|
|
11
|
+
export * from "./http-session-provider";
|
|
12
|
+
export * from "./livekit-room-adapter";
|
|
13
|
+
export * from "./native-runtime";
|
|
14
|
+
export * from "./provider";
|
|
15
|
+
export * from "./livestream-screen";
|
|
16
|
+
export * from "./version";
|