@superblocksteam/shared 0.9597.1 → 0.9598.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/dist/database-lifecycle/index.d.ts +7 -0
- package/dist/database-lifecycle/index.d.ts.map +1 -1
- package/dist/database-lifecycle/index.js.map +1 -1
- package/dist/dbfs/types.d.ts +33 -0
- package/dist/dbfs/types.d.ts.map +1 -1
- package/dist/socket/protocol.d.ts +2 -1
- package/dist/socket/protocol.d.ts.map +1 -1
- package/dist/socket/protocol.js.map +1 -1
- package/dist/socket/socket.test.js +37 -0
- package/dist/socket/socket.test.js.map +1 -1
- package/dist/socket/socketWithClientAuth.d.ts +1 -0
- package/dist/socket/socketWithClientAuth.d.ts.map +1 -1
- package/dist/socket/socketWithClientAuth.js +6 -1
- package/dist/socket/socketWithClientAuth.js.map +1 -1
- package/dist/socket/util.d.ts +22 -1
- package/dist/socket/util.d.ts.map +1 -1
- package/dist/socket/util.js +25 -3
- package/dist/socket/util.js.map +1 -1
- package/dist/socket/util.test.d.ts +2 -0
- package/dist/socket/util.test.d.ts.map +1 -0
- package/dist/socket/util.test.js +53 -0
- package/dist/socket/util.test.js.map +1 -0
- package/dist/types/git/index.d.ts.map +1 -1
- package/dist/types/git/index.js +2 -0
- package/dist/types/git/index.js.map +1 -1
- package/dist/types/git/index.test.d.ts +2 -0
- package/dist/types/git/index.test.d.ts.map +1 -0
- package/dist/types/git/index.test.js +13 -0
- package/dist/types/git/index.test.js.map +1 -0
- package/dist-esm/database-lifecycle/index.d.ts +7 -0
- package/dist-esm/database-lifecycle/index.d.ts.map +1 -1
- package/dist-esm/database-lifecycle/index.js.map +1 -1
- package/dist-esm/dbfs/types.d.ts +33 -0
- package/dist-esm/dbfs/types.d.ts.map +1 -1
- package/dist-esm/socket/protocol.d.ts +2 -1
- package/dist-esm/socket/protocol.d.ts.map +1 -1
- package/dist-esm/socket/protocol.js.map +1 -1
- package/dist-esm/socket/socket.test.js +37 -0
- package/dist-esm/socket/socket.test.js.map +1 -1
- package/dist-esm/socket/socketWithClientAuth.d.ts +1 -0
- package/dist-esm/socket/socketWithClientAuth.d.ts.map +1 -1
- package/dist-esm/socket/socketWithClientAuth.js +6 -1
- package/dist-esm/socket/socketWithClientAuth.js.map +1 -1
- package/dist-esm/socket/util.d.ts +22 -1
- package/dist-esm/socket/util.d.ts.map +1 -1
- package/dist-esm/socket/util.js +23 -2
- package/dist-esm/socket/util.js.map +1 -1
- package/dist-esm/socket/util.test.d.ts +2 -0
- package/dist-esm/socket/util.test.d.ts.map +1 -0
- package/dist-esm/socket/util.test.js +51 -0
- package/dist-esm/socket/util.test.js.map +1 -0
- package/dist-esm/types/git/index.d.ts.map +1 -1
- package/dist-esm/types/git/index.js +2 -0
- package/dist-esm/types/git/index.js.map +1 -1
- package/dist-esm/types/git/index.test.d.ts +2 -0
- package/dist-esm/types/git/index.test.d.ts.map +1 -0
- package/dist-esm/types/git/index.test.js +11 -0
- package/dist-esm/types/git/index.test.js.map +1 -0
- package/package.json +2 -2
- package/src/database-lifecycle/index.ts +8 -0
- package/src/dbfs/types.ts +39 -0
- package/src/socket/protocol.ts +8 -1
- package/src/socket/socket.test.ts +43 -0
- package/src/socket/socketWithClientAuth.ts +6 -1
- package/src/socket/util.test.ts +71 -0
- package/src/socket/util.ts +32 -2
- package/src/types/git/index.test.ts +15 -0
- package/src/types/git/index.ts +2 -0
|
@@ -14,6 +14,7 @@ export class ISocketWithClientAuth<ImplementedMethods, CallableMethods, RequestC
|
|
|
14
14
|
RequestContext
|
|
15
15
|
> {
|
|
16
16
|
private authorization?: string;
|
|
17
|
+
private authorizationVersion = 0;
|
|
17
18
|
private hasSentAuth = false;
|
|
18
19
|
|
|
19
20
|
constructor(
|
|
@@ -35,9 +36,12 @@ export class ISocketWithClientAuth<ImplementedMethods, CallableMethods, RequestC
|
|
|
35
36
|
// override `request` from the base class to send `authorization` when appropriate
|
|
36
37
|
async request<Params, Result>(method: string, params: Params): Promise<Result> {
|
|
37
38
|
// only send `authorization` on the first request
|
|
39
|
+
const authorizationVersion = this.authorizationVersion;
|
|
38
40
|
const authorization = this.hasSentAuth ? undefined : this.authorization;
|
|
39
41
|
const result = await super.request<Params, Result>(method, params, authorization);
|
|
40
|
-
this.
|
|
42
|
+
if (this.authorizationVersion === authorizationVersion) {
|
|
43
|
+
this.hasSentAuth = true;
|
|
44
|
+
}
|
|
41
45
|
return result;
|
|
42
46
|
}
|
|
43
47
|
|
|
@@ -49,6 +53,7 @@ export class ISocketWithClientAuth<ImplementedMethods, CallableMethods, RequestC
|
|
|
49
53
|
*/
|
|
50
54
|
setAuthorization(newAuthorization: string | undefined): void {
|
|
51
55
|
this.authorization = newAuthorization;
|
|
56
|
+
this.authorizationVersion++;
|
|
52
57
|
this.hasSentAuth = false;
|
|
53
58
|
}
|
|
54
59
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { buildClientUserAgent, connectWebSocket, DEFAULT_WEBSOCKET_USER_AGENT, MAX_SOCKET_PAYLOAD_BYTES } from './util.js';
|
|
4
|
+
|
|
5
|
+
const constructorSpy = vi.fn();
|
|
6
|
+
|
|
7
|
+
// isomorphic-ws resolves to the `ws` package on Node; capture its constructor args.
|
|
8
|
+
// vitest hoists this mock above the static import above, so `util.ts` picks it up.
|
|
9
|
+
vi.mock('isomorphic-ws', () => ({
|
|
10
|
+
default: class MockWebSocket {
|
|
11
|
+
constructor(url: string, protocol: unknown, options: unknown) {
|
|
12
|
+
constructorSpy(url, protocol, options);
|
|
13
|
+
}
|
|
14
|
+
addEventListener(): void {
|
|
15
|
+
// no-op; the connect promise stays pending, which is fine for these assertions
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
describe('connectWebSocket User-Agent', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
vi.useFakeTimers();
|
|
23
|
+
constructorSpy.mockClear();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
vi.clearAllTimers();
|
|
28
|
+
vi.useRealTimers();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('sends a default User-Agent and preserves maxPayload on the handshake', () => {
|
|
32
|
+
void connectWebSocket('wss://example.com/api/v1/rpc-ws');
|
|
33
|
+
|
|
34
|
+
expect(constructorSpy).toHaveBeenCalledWith(
|
|
35
|
+
'wss://example.com/api/v1/rpc-ws',
|
|
36
|
+
undefined,
|
|
37
|
+
expect.objectContaining({
|
|
38
|
+
maxPayload: MAX_SOCKET_PAYLOAD_BYTES,
|
|
39
|
+
headers: expect.objectContaining({ 'User-Agent': DEFAULT_WEBSOCKET_USER_AGENT })
|
|
40
|
+
})
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('lets callers override the User-Agent via options.headers', () => {
|
|
45
|
+
void connectWebSocket('wss://example.com/api/v1/rpc-ws', { headers: { 'User-Agent': 'superblocks-cli/9.9.9' } });
|
|
46
|
+
|
|
47
|
+
expect(constructorSpy).toHaveBeenCalledWith(
|
|
48
|
+
'wss://example.com/api/v1/rpc-ws',
|
|
49
|
+
undefined,
|
|
50
|
+
expect.objectContaining({
|
|
51
|
+
headers: expect.objectContaining({ 'User-Agent': 'superblocks-cli/9.9.9' })
|
|
52
|
+
})
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('forwards options.protocol as the WebSocket subprotocol', () => {
|
|
57
|
+
void connectWebSocket('wss://example.com/api/v1/rpc-ws', { protocol: 'editor' });
|
|
58
|
+
|
|
59
|
+
expect(constructorSpy).toHaveBeenCalledWith('wss://example.com/api/v1/rpc-ws', 'editor', expect.anything());
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('buildClientUserAgent', () => {
|
|
64
|
+
it('joins component and version with a slash', () => {
|
|
65
|
+
expect(buildClientUserAgent('superblocks-cli', '2.0.0-next.1')).toBe('superblocks-cli/2.0.0-next.1');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('returns the bare component when no version is given', () => {
|
|
69
|
+
expect(buildClientUserAgent('superblocks-dev-server')).toBe('superblocks-dev-server');
|
|
70
|
+
});
|
|
71
|
+
});
|
package/src/socket/util.ts
CHANGED
|
@@ -12,13 +12,43 @@ import WebSocket from 'isomorphic-ws';
|
|
|
12
12
|
*/
|
|
13
13
|
export const MAX_SOCKET_PAYLOAD_BYTES = 100 * 1024 * 1024;
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Default User-Agent for Node-originated WebSocket handshakes. The `ws` client
|
|
17
|
+
* sends no User-Agent by default (unlike browsers, axios, or undici's fetch),
|
|
18
|
+
* and AWS WAF's managed common rule set blocks UA-less requests
|
|
19
|
+
* (`NoUserAgent_HEADER`) with a 403 at the ALB. A non-empty UA keeps the
|
|
20
|
+
* handshake from being rejected; callers may override with a more specific,
|
|
21
|
+
* versioned identity. Ignored by browsers (native WebSocket sets its own UA).
|
|
22
|
+
*/
|
|
23
|
+
export const DEFAULT_WEBSOCKET_USER_AGENT = 'superblocks-client';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Build a client User-Agent of the form `component/version`, or bare `component`
|
|
27
|
+
* when no version is supplied. Single source of truth for the identity strings
|
|
28
|
+
* Node WebSocket clients (CLI, dev server, ...) send on the handshake.
|
|
29
|
+
*/
|
|
30
|
+
export function buildClientUserAgent(component: string, version?: string): string {
|
|
31
|
+
return version ? `${component}/${version}` : component;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ConnectWebSocketOptions {
|
|
35
|
+
protocol?: string | string[];
|
|
36
|
+
timeout?: number;
|
|
37
|
+
/** Extra handshake headers (Node only; ignored by browsers). Overrides the default User-Agent. */
|
|
38
|
+
headers?: Record<string, string>;
|
|
39
|
+
}
|
|
40
|
+
|
|
15
41
|
/**
|
|
16
42
|
* If the connection is not established within the timeout, the promise will be rejected.
|
|
17
43
|
* This can happen with bad network conditions, but is very rare.
|
|
18
44
|
*/
|
|
19
|
-
export function connectWebSocket(wsUrl: string,
|
|
45
|
+
export function connectWebSocket(wsUrl: string, options: ConnectWebSocketOptions = {}): Promise<WebSocket> {
|
|
46
|
+
const { protocol, timeout = 30_000, headers } = options;
|
|
20
47
|
return new Promise((resolve, reject) => {
|
|
21
|
-
const ws = new WebSocket(wsUrl, protocol, {
|
|
48
|
+
const ws = new WebSocket(wsUrl, protocol, {
|
|
49
|
+
maxPayload: MAX_SOCKET_PAYLOAD_BYTES,
|
|
50
|
+
headers: { 'User-Agent': DEFAULT_WEBSOCKET_USER_AGENT, ...headers }
|
|
51
|
+
});
|
|
22
52
|
|
|
23
53
|
const timeoutId = setTimeout(() => {
|
|
24
54
|
reject(new Error('[internal] WebSocket connection timeout'));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { isGitAuthErrorMessage } from './index.js';
|
|
4
|
+
|
|
5
|
+
describe('isGitAuthErrorMessage', () => {
|
|
6
|
+
it('matches fine-grained GitHub invalid username or token errors', () => {
|
|
7
|
+
expect(
|
|
8
|
+
isGitAuthErrorMessage(new Error('remote: Invalid username or token. Password authentication is not supported for Git operations.'))
|
|
9
|
+
).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('matches password authentication is not supported errors', () => {
|
|
13
|
+
expect(isGitAuthErrorMessage(new Error('fatal: Password authentication is not supported for Git operations.'))).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
});
|
package/src/types/git/index.ts
CHANGED
|
@@ -18,7 +18,9 @@ export const GIT_AUTH_ERROR_PATTERNS: readonly string[] = [
|
|
|
18
18
|
'authentication failed',
|
|
19
19
|
'could not read username',
|
|
20
20
|
'invalid credentials',
|
|
21
|
+
'invalid username or token',
|
|
21
22
|
'bad credentials',
|
|
23
|
+
'password authentication is not supported',
|
|
22
24
|
'401'
|
|
23
25
|
];
|
|
24
26
|
|