@pantheon-systems/css-client 0.4.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/dist/auth.d.ts +54 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +72 -0
- package/dist/auth.js.map +1 -0
- package/dist/broker.d.ts +41 -0
- package/dist/broker.d.ts.map +1 -0
- package/dist/broker.js +187 -0
- package/dist/broker.js.map +1 -0
- package/dist/client.d.ts +138 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +130 -0
- package/dist/client.js.map +1 -0
- package/dist/content.d.ts +45 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +65 -0
- package/dist/content.js.map +1 -0
- package/dist/endpoints/agent-edit.d.ts +79 -0
- package/dist/endpoints/agent-edit.d.ts.map +1 -0
- package/dist/endpoints/agent-edit.js +125 -0
- package/dist/endpoints/agent-edit.js.map +1 -0
- package/dist/endpoints/agent-registry.d.ts +59 -0
- package/dist/endpoints/agent-registry.d.ts.map +1 -0
- package/dist/endpoints/agent-registry.js +96 -0
- package/dist/endpoints/agent-registry.js.map +1 -0
- package/dist/endpoints/base.d.ts +49 -0
- package/dist/endpoints/base.d.ts.map +1 -0
- package/dist/endpoints/base.js +150 -0
- package/dist/endpoints/base.js.map +1 -0
- package/dist/endpoints/branches.d.ts +32 -0
- package/dist/endpoints/branches.d.ts.map +1 -0
- package/dist/endpoints/branches.js +69 -0
- package/dist/endpoints/branches.js.map +1 -0
- package/dist/endpoints/checkpoints.d.ts +36 -0
- package/dist/endpoints/checkpoints.d.ts.map +1 -0
- package/dist/endpoints/checkpoints.js +76 -0
- package/dist/endpoints/checkpoints.js.map +1 -0
- package/dist/endpoints/documents.d.ts +46 -0
- package/dist/endpoints/documents.d.ts.map +1 -0
- package/dist/endpoints/documents.js +101 -0
- package/dist/endpoints/documents.js.map +1 -0
- package/dist/endpoints/index.d.ts +15 -0
- package/dist/endpoints/index.d.ts.map +1 -0
- package/dist/endpoints/index.js +16 -0
- package/dist/endpoints/index.js.map +1 -0
- package/dist/endpoints/merge.d.ts +51 -0
- package/dist/endpoints/merge.d.ts.map +1 -0
- package/dist/endpoints/merge.js +104 -0
- package/dist/endpoints/merge.js.map +1 -0
- package/dist/endpoints/presence.d.ts +55 -0
- package/dist/endpoints/presence.d.ts.map +1 -0
- package/dist/endpoints/presence.js +72 -0
- package/dist/endpoints/presence.js.map +1 -0
- package/dist/endpoints/sites.d.ts +20 -0
- package/dist/endpoints/sites.d.ts.map +1 -0
- package/dist/endpoints/sites.js +38 -0
- package/dist/endpoints/sites.js.map +1 -0
- package/dist/endpoints/versions.d.ts +37 -0
- package/dist/endpoints/versions.d.ts.map +1 -0
- package/dist/endpoints/versions.js +64 -0
- package/dist/endpoints/versions.js.map +1 -0
- package/dist/errors.d.ts +61 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +87 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/jwt-utils.d.ts +13 -0
- package/dist/jwt-utils.d.ts.map +1 -0
- package/dist/jwt-utils.js +45 -0
- package/dist/jwt-utils.js.map +1 -0
- package/dist/oauth.d.ts +64 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +62 -0
- package/dist/oauth.js.map +1 -0
- package/dist/realtime.d.ts +314 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +615 -0
- package/dist/realtime.js.map +1 -0
- package/dist/types.d.ts +703 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +3 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +17 -0
- package/dist/utils.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt-utils.d.ts","sourceRoot":"","sources":["../src/jwt-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAEhD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAU7E;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAI3D;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAK/D;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CASnE"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT Utility Functions
|
|
3
|
+
*
|
|
4
|
+
* Shared helpers for parsing JWT payloads and checking expiry.
|
|
5
|
+
* These decode JWTs for display/routing only — no cryptographic verification.
|
|
6
|
+
*/
|
|
7
|
+
export const TOKEN_REFRESH_BUFFER_SECONDS = 300;
|
|
8
|
+
export function parseJwtPayload(token) {
|
|
9
|
+
try {
|
|
10
|
+
const parts = token.split('.');
|
|
11
|
+
const payload = parts[1];
|
|
12
|
+
if (!payload)
|
|
13
|
+
return null;
|
|
14
|
+
const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
|
|
15
|
+
return JSON.parse(decoded);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function getTokenExpiry(token) {
|
|
22
|
+
const payload = parseJwtPayload(token);
|
|
23
|
+
if (!payload || typeof payload.exp !== 'number')
|
|
24
|
+
return null;
|
|
25
|
+
return payload.exp;
|
|
26
|
+
}
|
|
27
|
+
export function isTokenExpiredOrExpiring(token) {
|
|
28
|
+
const exp = getTokenExpiry(token);
|
|
29
|
+
if (exp === null)
|
|
30
|
+
return false;
|
|
31
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
32
|
+
return nowSeconds >= exp - TOKEN_REFRESH_BUFFER_SECONDS;
|
|
33
|
+
}
|
|
34
|
+
export function extractUserInfo(token) {
|
|
35
|
+
const payload = parseJwtPayload(token);
|
|
36
|
+
if (!payload || typeof payload.sub !== 'string')
|
|
37
|
+
return null;
|
|
38
|
+
return {
|
|
39
|
+
id: payload.sub,
|
|
40
|
+
email: payload.email,
|
|
41
|
+
name: payload.name,
|
|
42
|
+
picture: payload.picture,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=jwt-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt-utils.js","sourceRoot":"","sources":["../src/jwt-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,CAAC,MAAM,4BAA4B,GAAG,GAAG,CAAC;AAEhD,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC;QAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;QACpE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAA4B,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC7D,OAAO,OAAO,CAAC,GAAG,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,KAAa;IACpD,MAAM,GAAG,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC/B,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IACjD,OAAO,UAAU,IAAI,GAAG,GAAG,4BAA4B,CAAC;AAC1D,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC7D,OAAO;QACL,EAAE,EAAE,OAAO,CAAC,GAAG;QACf,KAAK,EAAE,OAAO,CAAC,KAA2B;QAC1C,IAAI,EAAE,OAAO,CAAC,IAA0B;QACxC,OAAO,EAAE,OAAO,CAAC,OAA6B;KAC/C,CAAC;AACJ,CAAC"}
|
package/dist/oauth.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { AuthProvider } from './auth.js';
|
|
2
|
+
/** User info returned from OAuth providers */
|
|
3
|
+
export interface OAuthUserInfo {
|
|
4
|
+
id: string;
|
|
5
|
+
email?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
picture?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface OAuthSession {
|
|
10
|
+
provider: 'broker';
|
|
11
|
+
login(): Promise<void>;
|
|
12
|
+
logout(): Promise<void>;
|
|
13
|
+
isAuthenticated(): boolean;
|
|
14
|
+
getUserInfo(): OAuthUserInfo | null;
|
|
15
|
+
getToken(): Promise<string | null>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Create an AuthProvider from an OAuthSession.
|
|
19
|
+
* The returned AuthProvider is compatible with P1Client's authProvider config option.
|
|
20
|
+
*
|
|
21
|
+
* @param session - The OAuth session to derive the auth provider from
|
|
22
|
+
* @returns AuthProvider function that returns `Bearer <token>`
|
|
23
|
+
*/
|
|
24
|
+
export declare function createOAuthAuthProvider(session: OAuthSession): AuthProvider;
|
|
25
|
+
export interface AuthMeResponse {
|
|
26
|
+
id: string;
|
|
27
|
+
type: string;
|
|
28
|
+
email?: string;
|
|
29
|
+
name?: string;
|
|
30
|
+
avatarUrl?: string;
|
|
31
|
+
authProvider?: string;
|
|
32
|
+
tokenExpiry?: string;
|
|
33
|
+
providerSubjectId?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Validate a token against the P1 backend's /api/auth/me endpoint.
|
|
37
|
+
* Framework-agnostic — works in any JS environment with fetch().
|
|
38
|
+
*
|
|
39
|
+
* @param baseUrl - P1 backend base URL (e.g., "http://localhost:8787")
|
|
40
|
+
* @param token - Bearer token to validate
|
|
41
|
+
* @returns The authenticated user info, or null if the token is invalid
|
|
42
|
+
*/
|
|
43
|
+
export declare function validateToken(baseUrl: string, token: string): Promise<AuthMeResponse | null>;
|
|
44
|
+
/**
|
|
45
|
+
* Login as a mock/demo user via POST /api/auth/token.
|
|
46
|
+
* Framework-agnostic — works in any JS environment with fetch().
|
|
47
|
+
*
|
|
48
|
+
* @internal This is a local-development helper. The backend only enables
|
|
49
|
+
* `/api/auth/token` when `ENVIRONMENT === 'local'`; mock tokens are rejected
|
|
50
|
+
* by all other environments. Do not use in production deployments.
|
|
51
|
+
*
|
|
52
|
+
* @param baseUrl - P1 backend base URL
|
|
53
|
+
* @param userId - The mock user ID to log in as
|
|
54
|
+
* @returns Token and user info
|
|
55
|
+
*/
|
|
56
|
+
export declare function loginMockUser(baseUrl: string, userId: string): Promise<{
|
|
57
|
+
token: string;
|
|
58
|
+
user: {
|
|
59
|
+
id: string;
|
|
60
|
+
name: string;
|
|
61
|
+
email: string;
|
|
62
|
+
};
|
|
63
|
+
}>;
|
|
64
|
+
//# sourceMappingURL=oauth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAG9C,8CAA8C;AAC9C,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,eAAe,IAAI,OAAO,CAAC;IAC3B,WAAW,IAAI,aAAa,GAAG,IAAI,CAAC;IACpC,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACpC;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,YAAY,GAAG,YAAY,CAQ3E;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAUhC;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC,CAgB/E"}
|
package/dist/oauth.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create an AuthProvider from an OAuthSession.
|
|
3
|
+
* The returned AuthProvider is compatible with P1Client's authProvider config option.
|
|
4
|
+
*
|
|
5
|
+
* @param session - The OAuth session to derive the auth provider from
|
|
6
|
+
* @returns AuthProvider function that returns `Bearer <token>`
|
|
7
|
+
*/
|
|
8
|
+
export function createOAuthAuthProvider(session) {
|
|
9
|
+
return async () => {
|
|
10
|
+
const token = await session.getToken();
|
|
11
|
+
if (!token) {
|
|
12
|
+
throw new Error('No OAuth token available. Please log in first.');
|
|
13
|
+
}
|
|
14
|
+
return `Bearer ${token}`;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Validate a token against the P1 backend's /api/auth/me endpoint.
|
|
19
|
+
* Framework-agnostic — works in any JS environment with fetch().
|
|
20
|
+
*
|
|
21
|
+
* @param baseUrl - P1 backend base URL (e.g., "http://localhost:8787")
|
|
22
|
+
* @param token - Bearer token to validate
|
|
23
|
+
* @returns The authenticated user info, or null if the token is invalid
|
|
24
|
+
*/
|
|
25
|
+
export async function validateToken(baseUrl, token) {
|
|
26
|
+
try {
|
|
27
|
+
const response = await fetch(`${baseUrl}/api/auth/me`, {
|
|
28
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
29
|
+
});
|
|
30
|
+
if (!response.ok)
|
|
31
|
+
return null;
|
|
32
|
+
return (await response.json());
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Login as a mock/demo user via POST /api/auth/token.
|
|
40
|
+
* Framework-agnostic — works in any JS environment with fetch().
|
|
41
|
+
*
|
|
42
|
+
* @internal This is a local-development helper. The backend only enables
|
|
43
|
+
* `/api/auth/token` when `ENVIRONMENT === 'local'`; mock tokens are rejected
|
|
44
|
+
* by all other environments. Do not use in production deployments.
|
|
45
|
+
*
|
|
46
|
+
* @param baseUrl - P1 backend base URL
|
|
47
|
+
* @param userId - The mock user ID to log in as
|
|
48
|
+
* @returns Token and user info
|
|
49
|
+
*/
|
|
50
|
+
export async function loginMockUser(baseUrl, userId) {
|
|
51
|
+
const response = await fetch(`${baseUrl}/api/auth/token`, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: { 'Content-Type': 'application/json' },
|
|
54
|
+
body: JSON.stringify({ userId }),
|
|
55
|
+
});
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const error = await response.json().catch(() => ({ error: 'Login failed' }));
|
|
58
|
+
throw new Error(error.error ?? 'Login failed');
|
|
59
|
+
}
|
|
60
|
+
return response.json();
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../src/oauth.ts"],"names":[],"mappings":"AAoBA;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAqB;IAC3D,OAAO,KAAK,IAAI,EAAE;QAChB,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC;QACvC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACpE,CAAC;QACD,OAAO,UAAU,KAAK,EAAE,CAAC;IAC3B,CAAC,CAAC;AACJ,CAAC;AAaD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAe,EACf,KAAa;IAEb,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,cAAc,EAAE;YACrD,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE;SAC9C,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QAC9B,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAmB,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAe,EACf,MAAc;IAEd,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,iBAAiB,EAAE;QACxD,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;KACjC,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;QAC7E,MAAM,IAAI,KAAK,CAAE,KAA4B,CAAC,KAAK,IAAI,cAAc,CAAC,CAAC;IACzE,CAAC;IAED,OAAO,QAAQ,CAAC,IAAI,EAGlB,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 2.1: RealtimeClient
|
|
3
|
+
*
|
|
4
|
+
* WebSocket-based real-time collaboration client using Yjs CRDT.
|
|
5
|
+
* Provides bidirectional sync between client and DocumentSession Durable Object.
|
|
6
|
+
* Uses ReconnectingWebSocket from partysocket for automatic reconnection with exponential backoff.
|
|
7
|
+
*/
|
|
8
|
+
import * as Y from 'yjs';
|
|
9
|
+
import type { ActorPresence, ActorState, PublishResult } from './types';
|
|
10
|
+
/**
|
|
11
|
+
* Configuration for reconnection behavior
|
|
12
|
+
*/
|
|
13
|
+
export interface ReconnectionConfig {
|
|
14
|
+
/**
|
|
15
|
+
* Maximum number of reconnection attempts.
|
|
16
|
+
* Set to Infinity for unlimited retries.
|
|
17
|
+
* @default Infinity
|
|
18
|
+
*/
|
|
19
|
+
maxRetries?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Minimum delay between reconnection attempts in milliseconds.
|
|
22
|
+
* @default 1000
|
|
23
|
+
*/
|
|
24
|
+
minReconnectionDelay?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Maximum delay between reconnection attempts in milliseconds.
|
|
27
|
+
* @default 30000
|
|
28
|
+
*/
|
|
29
|
+
maxReconnectionDelay?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Factor by which to multiply the delay for each retry (exponential backoff).
|
|
32
|
+
* @default 1.5
|
|
33
|
+
*/
|
|
34
|
+
reconnectionDelayGrowFactor?: number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Configuration for the RealtimeClient
|
|
38
|
+
*/
|
|
39
|
+
export interface RealtimeClientConfig {
|
|
40
|
+
/**
|
|
41
|
+
* Base URL for WebSocket connections.
|
|
42
|
+
* Can be ws:// or wss:// protocol.
|
|
43
|
+
* Example: "wss://api.example.com" or "ws://localhost:8787"
|
|
44
|
+
*/
|
|
45
|
+
baseUrl: string;
|
|
46
|
+
/**
|
|
47
|
+
* API key for authentication.
|
|
48
|
+
* Will be passed as a query parameter since browsers can't send
|
|
49
|
+
* custom headers with WebSocket upgrade requests.
|
|
50
|
+
*/
|
|
51
|
+
apiKey?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Callback when document state is updated (from remote changes)
|
|
54
|
+
*/
|
|
55
|
+
onUpdate?: (snapshot: Record<string, unknown>) => void;
|
|
56
|
+
/**
|
|
57
|
+
* Callback when WebSocket connection is established
|
|
58
|
+
*/
|
|
59
|
+
onConnect?: () => void;
|
|
60
|
+
/**
|
|
61
|
+
* Callback when WebSocket connection is closed
|
|
62
|
+
*/
|
|
63
|
+
onDisconnect?: () => void;
|
|
64
|
+
/**
|
|
65
|
+
* Callback when an error occurs
|
|
66
|
+
*/
|
|
67
|
+
onError?: (error: Error) => void;
|
|
68
|
+
/**
|
|
69
|
+
* Callback when attempting to reconnect after a connection loss.
|
|
70
|
+
* Called with the current retry attempt number.
|
|
71
|
+
*/
|
|
72
|
+
onReconnecting?: (attempt: number) => void;
|
|
73
|
+
/**
|
|
74
|
+
* Callback when authorization fails (WebSocket close code 4401 or 4403).
|
|
75
|
+
* This indicates the session is invalid or the agent lacks permission.
|
|
76
|
+
* When this is called, the client will NOT attempt to reconnect.
|
|
77
|
+
*/
|
|
78
|
+
onAuthorizationError?: (error: Error) => void;
|
|
79
|
+
/**
|
|
80
|
+
* Callback when presence update is received from server.
|
|
81
|
+
* Called with the full list of actors in the document.
|
|
82
|
+
*/
|
|
83
|
+
onPresenceUpdate?: (actors: ActorPresence[]) => void;
|
|
84
|
+
/**
|
|
85
|
+
* Callback when another actor updates their focus regions.
|
|
86
|
+
* Called with the actor ID and their new focus regions.
|
|
87
|
+
*/
|
|
88
|
+
onFocusRegionBroadcast?: (actorId: string, focusRegions: string[]) => void;
|
|
89
|
+
/**
|
|
90
|
+
* Configuration for automatic reconnection behavior.
|
|
91
|
+
*/
|
|
92
|
+
reconnection?: ReconnectionConfig;
|
|
93
|
+
/**
|
|
94
|
+
* Callback when the server sends a RATE_LIMITED error.
|
|
95
|
+
* The client remains connected; this is informational.
|
|
96
|
+
*/
|
|
97
|
+
onRateLimited?: () => void;
|
|
98
|
+
/**
|
|
99
|
+
* Optional token refresher for dynamic WebSocket authentication.
|
|
100
|
+
* Called when the WebSocket connection closes unexpectedly (non-intentionally).
|
|
101
|
+
* Should return a fresh token string, or null if the session cannot be refreshed.
|
|
102
|
+
* The fresh token is used in subsequent reconnection URLs.
|
|
103
|
+
*/
|
|
104
|
+
tokenRefresher?: () => Promise<string | null>;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Parameters for connecting to a document session
|
|
108
|
+
*/
|
|
109
|
+
export interface ConnectionParams {
|
|
110
|
+
/** Site ID */
|
|
111
|
+
siteId: string;
|
|
112
|
+
/** Branch ID */
|
|
113
|
+
branchId: string;
|
|
114
|
+
/** Document path (e.g., "pages/home") */
|
|
115
|
+
documentPath: string;
|
|
116
|
+
/** Actor ID (user or agent ID) */
|
|
117
|
+
actorId: string;
|
|
118
|
+
/** Actor type */
|
|
119
|
+
actorType: 'user' | 'agent';
|
|
120
|
+
/**
|
|
121
|
+
* Session ID for agent authorization.
|
|
122
|
+
* Required for agents, obtained from startEdit() response.
|
|
123
|
+
* Enables server-side enforcement of the Agent Politeness Protocol.
|
|
124
|
+
*/
|
|
125
|
+
sessionId?: string;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Real-time collaboration client using Yjs CRDT over WebSocket.
|
|
129
|
+
* Uses PartySocket for automatic reconnection with exponential backoff.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* ```typescript
|
|
133
|
+
* const client = new RealtimeClient({
|
|
134
|
+
* baseUrl: 'wss://api.example.com',
|
|
135
|
+
* onUpdate: (snapshot) => {
|
|
136
|
+
* console.log('Document updated:', snapshot);
|
|
137
|
+
* },
|
|
138
|
+
* onReconnecting: (attempt) => {
|
|
139
|
+
* console.log(`Reconnecting... attempt ${attempt}`);
|
|
140
|
+
* },
|
|
141
|
+
* reconnection: {
|
|
142
|
+
* maxRetries: 10,
|
|
143
|
+
* minReconnectionDelay: 1000,
|
|
144
|
+
* maxReconnectionDelay: 30000,
|
|
145
|
+
* },
|
|
146
|
+
* });
|
|
147
|
+
*
|
|
148
|
+
* client.connect({
|
|
149
|
+
* siteId: 'site-123',
|
|
150
|
+
* branchId: 'branch-456',
|
|
151
|
+
* documentPath: 'pages/home',
|
|
152
|
+
* actorId: 'user-789',
|
|
153
|
+
* actorType: 'user',
|
|
154
|
+
* });
|
|
155
|
+
*
|
|
156
|
+
* // When done
|
|
157
|
+
* client.disconnect();
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
export declare class RealtimeClient {
|
|
161
|
+
private readonly config;
|
|
162
|
+
private readonly ydoc;
|
|
163
|
+
private ws;
|
|
164
|
+
private connected;
|
|
165
|
+
private intentionalDisconnect;
|
|
166
|
+
private currentApiKey;
|
|
167
|
+
private tokenRefreshInFlight;
|
|
168
|
+
private hasConnectedOnce;
|
|
169
|
+
private lastReportedRetryCount;
|
|
170
|
+
private reconnectCheckInterval;
|
|
171
|
+
private visibilityHandler;
|
|
172
|
+
private sendTimestamps;
|
|
173
|
+
private pendingUpdates;
|
|
174
|
+
private flushTimer;
|
|
175
|
+
private static readonly RATE_THRESHOLD;
|
|
176
|
+
private static readonly RATE_WINDOW_MS;
|
|
177
|
+
private lastSentSnapshot;
|
|
178
|
+
private pendingDeliveryAcks;
|
|
179
|
+
private static readonly DELIVERY_ACK_TIMEOUT_MS;
|
|
180
|
+
private pendingPublishRequests;
|
|
181
|
+
private static readonly PUBLISH_TIMEOUT_MS;
|
|
182
|
+
private pendingActionMetadata;
|
|
183
|
+
constructor(config: RealtimeClientConfig);
|
|
184
|
+
/**
|
|
185
|
+
* Connect to a document session via WebSocket.
|
|
186
|
+
* Uses PartySocket for automatic reconnection on connection loss.
|
|
187
|
+
*
|
|
188
|
+
* @param params - Connection parameters including site, branch, document, and actor info
|
|
189
|
+
*/
|
|
190
|
+
connect(params: ConnectionParams): void;
|
|
191
|
+
/**
|
|
192
|
+
* Start monitoring for reconnection attempts by polling retryCount.
|
|
193
|
+
* This is needed because PartySocket doesn't expose a reconnection callback.
|
|
194
|
+
*/
|
|
195
|
+
private startReconnectMonitoring;
|
|
196
|
+
/**
|
|
197
|
+
* Stop the reconnection monitoring interval.
|
|
198
|
+
*/
|
|
199
|
+
private stopReconnectMonitoring;
|
|
200
|
+
/**
|
|
201
|
+
* Start monitoring for page visibility changes.
|
|
202
|
+
* When the page becomes visible after being hidden, check connection health
|
|
203
|
+
* and force a reconnection sync if needed.
|
|
204
|
+
*/
|
|
205
|
+
private startVisibilityMonitoring;
|
|
206
|
+
/**
|
|
207
|
+
* Stop visibility change monitoring.
|
|
208
|
+
*/
|
|
209
|
+
private stopVisibilityMonitoring;
|
|
210
|
+
/**
|
|
211
|
+
* Disconnect from the current session.
|
|
212
|
+
* This permanently closes the connection and stops any reconnection attempts.
|
|
213
|
+
*/
|
|
214
|
+
disconnect(): void;
|
|
215
|
+
/**
|
|
216
|
+
* Apply a local Yjs update.
|
|
217
|
+
* This is typically called from Puck-Yjs binding when local edits occur.
|
|
218
|
+
* Uses rate-aware sending to avoid exceeding the server's rate limit.
|
|
219
|
+
*
|
|
220
|
+
* @param update - Raw Yjs update bytes
|
|
221
|
+
*/
|
|
222
|
+
applyLocalUpdate(update: Uint8Array): void;
|
|
223
|
+
/**
|
|
224
|
+
* Set pending action metadata to be sent after the next CRDT update.
|
|
225
|
+
* The metadata is best-effort — if the WebSocket is not open or the
|
|
226
|
+
* send fails, the CRDT update still goes through without metadata.
|
|
227
|
+
*
|
|
228
|
+
* @param meta - Action type and metadata from Puck's onAction callback
|
|
229
|
+
*/
|
|
230
|
+
setActionMetadata(meta: {
|
|
231
|
+
actionType: string;
|
|
232
|
+
actionMetadata: Record<string, unknown>;
|
|
233
|
+
} | null): void;
|
|
234
|
+
/**
|
|
235
|
+
* Send any pending action metadata as a text message and clear it.
|
|
236
|
+
* Called after a CRDT update is sent to associate the metadata with
|
|
237
|
+
* the most recent edit.
|
|
238
|
+
*/
|
|
239
|
+
sendPendingActionMetadata(): void;
|
|
240
|
+
/**
|
|
241
|
+
* Send a Yjs update with rate awareness.
|
|
242
|
+
* Sends immediately when under the threshold (40 msgs/sec).
|
|
243
|
+
* Buffers and coalesces updates when approaching the server's 50 msg/sec limit.
|
|
244
|
+
*/
|
|
245
|
+
private rateLimitedSend;
|
|
246
|
+
/**
|
|
247
|
+
* Schedule a flush of buffered updates after the rate window resets.
|
|
248
|
+
*/
|
|
249
|
+
private scheduleFlush;
|
|
250
|
+
/**
|
|
251
|
+
* Flush all buffered updates as a single coalesced message.
|
|
252
|
+
*/
|
|
253
|
+
private flushPendingUpdates;
|
|
254
|
+
/**
|
|
255
|
+
* Get the current document snapshot as JSON.
|
|
256
|
+
*/
|
|
257
|
+
getSnapshot(): Record<string, unknown>;
|
|
258
|
+
/**
|
|
259
|
+
* Get the underlying Y.Doc for direct manipulation.
|
|
260
|
+
* Use with caution - prefer using the Puck-Yjs binding utilities.
|
|
261
|
+
*/
|
|
262
|
+
getYDoc(): Y.Doc;
|
|
263
|
+
/**
|
|
264
|
+
* Check if currently connected.
|
|
265
|
+
*/
|
|
266
|
+
isConnected(): boolean;
|
|
267
|
+
/**
|
|
268
|
+
* Check if presence updates are available via WebSocket.
|
|
269
|
+
* Returns true when connected and able to send/receive presence messages.
|
|
270
|
+
*/
|
|
271
|
+
get presenceViaWebSocket(): boolean;
|
|
272
|
+
/**
|
|
273
|
+
* Send focus regions update to the server.
|
|
274
|
+
* @param focusRegions - JSON paths the actor is focused on
|
|
275
|
+
* @returns true if message was sent, false if not connected
|
|
276
|
+
*/
|
|
277
|
+
sendFocusRegions(focusRegions: string[]): boolean;
|
|
278
|
+
/**
|
|
279
|
+
* Send a presence heartbeat to keep the connection alive.
|
|
280
|
+
* @param state - Optional state update (active, idle, editing)
|
|
281
|
+
*/
|
|
282
|
+
sendHeartbeat(state?: ActorState): void;
|
|
283
|
+
/**
|
|
284
|
+
* Wait for the server to acknowledge that all preceding WebSocket messages
|
|
285
|
+
* have been processed. This uses TCP ordering guarantees: a text frame sent
|
|
286
|
+
* after binary CRDT updates is guaranteed to arrive after those updates.
|
|
287
|
+
* The server echoes back a delivery_ack with the matching requestId.
|
|
288
|
+
*
|
|
289
|
+
* Used before publish to ensure the Durable Object has received and applied
|
|
290
|
+
* the latest edits before the HTTP publish request arrives.
|
|
291
|
+
*
|
|
292
|
+
* @returns Promise that resolves when the server confirms delivery
|
|
293
|
+
* @throws Error if not connected or if timeout expires (5 seconds)
|
|
294
|
+
*/
|
|
295
|
+
waitForDelivery(): Promise<void>;
|
|
296
|
+
/**
|
|
297
|
+
* Request the server to publish the current document via WebSocket.
|
|
298
|
+
* TCP ordering guarantees all preceding binary CRDT updates have been
|
|
299
|
+
* processed before this message is handled, eliminating stale-version races.
|
|
300
|
+
*
|
|
301
|
+
* The Durable Object handles the entire flow: flush to Postgres, then
|
|
302
|
+
* call /internal/publish to create the checkpoint.
|
|
303
|
+
*
|
|
304
|
+
* @returns Promise that resolves with the publish result
|
|
305
|
+
* @throws Error if not connected or if timeout expires (30 seconds)
|
|
306
|
+
*/
|
|
307
|
+
requestPublish(): Promise<PublishResult>;
|
|
308
|
+
/**
|
|
309
|
+
* Handle incoming text (JSON) messages for presence protocol.
|
|
310
|
+
* @param data - Raw JSON string from WebSocket
|
|
311
|
+
*/
|
|
312
|
+
private handleTextMessage;
|
|
313
|
+
}
|
|
314
|
+
//# sourceMappingURL=realtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"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"}
|