@byokit/realtime 0.2.0 → 0.2.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 +6 -0
- package/README.md +18 -6
- package/dist/auth.d.ts +15 -1
- package/dist/auth.js +35 -6
- package/dist/node.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -85,28 +85,40 @@ The app's `peek` callback must also never refresh, prompt or start login.
|
|
|
85
85
|
|
|
86
86
|
```ts
|
|
87
87
|
import { realtimeEngine, toolBridge } from '@byokit/realtime/node';
|
|
88
|
-
import type { RealtimeAuth, RealtimeHostFrame, RealtimeTool } from '@byokit/realtime';
|
|
88
|
+
import type { RealtimeAuth, RealtimeHostFrame, RealtimeTool, RealtimeAuthPeekResult, RealtimeAuthResult } from '@byokit/realtime';
|
|
89
89
|
|
|
90
90
|
declare const auth: Extract<RealtimeAuth, { kind: 'plan' }>;
|
|
91
91
|
declare const tools: RealtimeTool[];
|
|
92
92
|
declare const bridge: ReturnType<typeof toolBridge>;
|
|
93
93
|
declare const emit: (frame: RealtimeHostFrame) => void;
|
|
94
|
-
declare const peekSavedSignIn: (signal: AbortSignal) => Promise<
|
|
94
|
+
declare const peekSavedSignIn: (signal: AbortSignal) => Promise<RealtimeAuthPeekResult>;
|
|
95
95
|
declare const updateSettingsBadge: (status: 'ready' | 'signed-out' | 'unknown') => void;
|
|
96
|
+
declare const showSignInMessage: (result: RealtimeAuthResult) => void;
|
|
96
97
|
const engine = realtimeEngine({
|
|
97
98
|
engine: 'chatgpt', auth,
|
|
98
99
|
tools, bridge, emit,
|
|
99
|
-
authCheck: { peek: peekSavedSignIn, onStatus: status => updateSettingsBadge(status) },
|
|
100
|
+
authCheck: { peek: peekSavedSignIn, onStatus: status => updateSettingsBadge(status), onResult: showSignInMessage },
|
|
100
101
|
});
|
|
101
102
|
```
|
|
102
103
|
|
|
103
104
|
`ready` means the app reports a saved sign-in, not provider entitlement.
|
|
104
|
-
`signed-out` means the lookup returned false
|
|
105
|
-
`unknown`; the default timeout is one second, capped at ten seconds. Close
|
|
105
|
+
`signed-out` means the lookup returned false or found an unusable saved sign-in.
|
|
106
|
+
Unrecognized errors or a timeout produce `unknown`; the default timeout is one second, capped at ten seconds. Close
|
|
106
107
|
cancels pending checks and suppresses late notifications. Normal session
|
|
107
108
|
authentication still needs the credential from `auth.access`.
|
|
108
109
|
For a standalone settings lookup, `realtimeAuthCheck({ peek })` is also exported
|
|
109
|
-
from the portable entry and returns `{ result, close }`.
|
|
110
|
+
from the portable entry and returns `{ result, details, close }`. `result` still
|
|
111
|
+
resolves to the status string; `details` resolves to `{ state, reason?, message? }`,
|
|
112
|
+
and engine `onResult` receives the same diagnostic object. A boolean or
|
|
113
|
+
`{ state }` peek keeps the previous behavior without adding a reason.
|
|
114
|
+
|
|
115
|
+
To report an expired saved sign-in, the read-only peek can return
|
|
116
|
+
`{ state: 'signed-out', reason: 'login-expired' }`. Built-in reasons are
|
|
117
|
+
`credential-permissions`, `login-expired`, `missing` and `unknown`; apps can
|
|
118
|
+
supply additional reason strings. Messages use fixed plain wording and never
|
|
119
|
+
copy exception text or credential paths. Missing-file errors map to `missing`,
|
|
120
|
+
permission errors to `credential-permissions`, and expired-login errors to
|
|
121
|
+
`login-expired`; unrecognized errors and timeouts report `unknown`. Do not await this check
|
|
110
122
|
before opening a voice session or show a login prompt from its result.
|
|
111
123
|
|
|
112
124
|
## Lifecycle announcements and semantic app tools
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
export type RealtimeAuthStatus = 'ready' | 'signed-out' | 'unknown';
|
|
2
|
+
/** Additional app-defined reasons are allowed without changing the state contract. */
|
|
3
|
+
export type RealtimeAuthReason = 'credential-permissions' | 'login-expired' | 'missing' | 'unknown' | (string & {});
|
|
4
|
+
export type RealtimeAuthResult = {
|
|
5
|
+
state: RealtimeAuthStatus;
|
|
6
|
+
reason?: RealtimeAuthReason;
|
|
7
|
+
message?: string;
|
|
8
|
+
};
|
|
9
|
+
export type RealtimeAuthPeekResult = boolean | {
|
|
10
|
+
state: RealtimeAuthStatus;
|
|
11
|
+
reason?: RealtimeAuthReason;
|
|
12
|
+
};
|
|
2
13
|
export type RealtimeAuthCheckOptions = {
|
|
3
14
|
/** Read-only saved-sign-in check. Must never start login, refresh access or prompt. */
|
|
4
|
-
peek(signal: AbortSignal): Promise<
|
|
15
|
+
peek(signal: AbortSignal): Promise<RealtimeAuthPeekResult>;
|
|
16
|
+
/** Diagnostic observer, also available through the standalone details promise. */
|
|
17
|
+
onResult?(result: RealtimeAuthResult): void;
|
|
5
18
|
onStatus?(status: RealtimeAuthStatus): void;
|
|
6
19
|
/** Defaults to 1 second; capped at 10 seconds. */
|
|
7
20
|
timeoutMs?: number;
|
|
@@ -9,5 +22,6 @@ export type RealtimeAuthCheckOptions = {
|
|
|
9
22
|
/** Advisory only: session startup must proceed independently of this check. */
|
|
10
23
|
export declare function realtimeAuthCheck(options: RealtimeAuthCheckOptions): {
|
|
11
24
|
result: Promise<RealtimeAuthStatus>;
|
|
25
|
+
details: Promise<RealtimeAuthResult>;
|
|
12
26
|
close(): void;
|
|
13
27
|
};
|
package/dist/auth.js
CHANGED
|
@@ -1,29 +1,58 @@
|
|
|
1
|
+
function describe(state, reason) {
|
|
2
|
+
if (!reason)
|
|
3
|
+
return { state };
|
|
4
|
+
const message = reason === 'credential-permissions' ? 'Your saved sign-in must be readable only by you.'
|
|
5
|
+
: reason === 'login-expired' ? 'Your saved sign-in has expired. Sign in again in settings.'
|
|
6
|
+
: reason === 'missing' ? 'No saved sign-in was found. Sign in in settings.'
|
|
7
|
+
: 'Your saved sign-in could not be checked.';
|
|
8
|
+
return { state, reason, message };
|
|
9
|
+
}
|
|
10
|
+
function failure(error) {
|
|
11
|
+
const code = error && typeof error === 'object' && 'code' in error ? error.code : undefined;
|
|
12
|
+
const reason = error && typeof error === 'object' && 'reason' in error ? error.reason : undefined;
|
|
13
|
+
if (reason === 'credential-permissions' || reason === 'login-expired' || reason === 'missing' || reason === 'unknown')
|
|
14
|
+
return describe(reason === 'unknown' ? 'unknown' : 'signed-out', reason);
|
|
15
|
+
if (code === 'ENOENT')
|
|
16
|
+
return describe('signed-out', 'missing');
|
|
17
|
+
if (code === 'EACCES' || code === 'EPERM' || code === 'credential-permissions' || (error instanceof Error && /owner.only|readable only by|permissions.*(?:credential|sign-in)|(?:credential|sign-in).*permissions/i.test(error.message)))
|
|
18
|
+
return describe('signed-out', 'credential-permissions');
|
|
19
|
+
if (code === 'login-expired' || code === 'TOKEN_EXPIRED' || (error instanceof Error && /\b(?:login|sign-in|token|credential)\b.*\bexpired\b/i.test(error.message)))
|
|
20
|
+
return describe('signed-out', 'login-expired');
|
|
21
|
+
return describe('unknown', 'unknown');
|
|
22
|
+
}
|
|
1
23
|
/** Advisory only: session startup must proceed independently of this check. */
|
|
2
24
|
export function realtimeAuthCheck(options) {
|
|
3
25
|
const controller = new AbortController();
|
|
4
26
|
let settled = false;
|
|
5
27
|
let resolve;
|
|
6
28
|
const result = new Promise(done => { resolve = done; });
|
|
7
|
-
|
|
29
|
+
let resolveDetails;
|
|
30
|
+
const details = new Promise(done => { resolveDetails = done; });
|
|
31
|
+
const finish = (value, notify = true) => {
|
|
8
32
|
if (settled)
|
|
9
33
|
return;
|
|
10
34
|
settled = true;
|
|
11
35
|
clearTimeout(timer);
|
|
12
36
|
controller.abort();
|
|
13
|
-
resolve(
|
|
37
|
+
resolve(value.state);
|
|
38
|
+
resolveDetails(value);
|
|
14
39
|
// A settings observer cannot fail a call or create an unhandled rejection.
|
|
15
40
|
if (notify) {
|
|
16
41
|
try {
|
|
17
|
-
options.onStatus?.(
|
|
42
|
+
options.onStatus?.(value.state);
|
|
43
|
+
}
|
|
44
|
+
catch { }
|
|
45
|
+
try {
|
|
46
|
+
options.onResult?.(value);
|
|
18
47
|
}
|
|
19
48
|
catch { }
|
|
20
49
|
}
|
|
21
50
|
};
|
|
22
51
|
const timeout = options.timeoutMs ?? 1000;
|
|
23
|
-
const timer = setTimeout(() => finish('unknown'), Number.isFinite(timeout) ? Math.min(10000, Math.max(1, timeout)) : 1000);
|
|
52
|
+
const timer = setTimeout(() => finish(describe('unknown', 'unknown')), Number.isFinite(timeout) ? Math.min(10000, Math.max(1, timeout)) : 1000);
|
|
24
53
|
void Promise.resolve().then(() => {
|
|
25
54
|
if (!settled)
|
|
26
55
|
return options.peek(controller.signal);
|
|
27
|
-
}).then(value => finish(value === true ? 'ready' : value === false ? 'signed-out' : 'unknown'),
|
|
28
|
-
return { result, close() { finish('unknown', false); } };
|
|
56
|
+
}).then(value => finish(typeof value === 'object' && value !== null ? describe(value.state, value.reason) : describe(value === true ? 'ready' : value === false ? 'signed-out' : 'unknown')), error => finish(failure(error)));
|
|
57
|
+
return { result, details, close() { finish(describe('unknown', 'unknown'), false); } };
|
|
29
58
|
}
|
package/dist/node.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { toolBridge } from './tools.ts';
|
|
|
5
5
|
import type { RealtimeAuthCheckOptions } from './auth.ts';
|
|
6
6
|
export { toolBridge, appBridge, delegationHandler } from './tools.ts';
|
|
7
7
|
export { realtimeAuthCheck } from './auth.ts';
|
|
8
|
-
export type { RealtimeAuthCheckOptions, RealtimeAuthStatus } from './auth.ts';
|
|
8
|
+
export type { RealtimeAuthCheckOptions, RealtimeAuthStatus, RealtimeAuthReason, RealtimeAuthResult, RealtimeAuthPeekResult } from './auth.ts';
|
|
9
9
|
export type { ToolHandler, ToolBridgeOptions } from './tools.ts';
|
|
10
10
|
export type { RealtimeSignalingIdentity } from './adapter.ts';
|
|
11
11
|
export type RealtimeEngineOptions = RealtimeConfig & {
|