@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.
Files changed (68) hide show
  1. package/dist/database-lifecycle/index.d.ts +7 -0
  2. package/dist/database-lifecycle/index.d.ts.map +1 -1
  3. package/dist/database-lifecycle/index.js.map +1 -1
  4. package/dist/dbfs/types.d.ts +33 -0
  5. package/dist/dbfs/types.d.ts.map +1 -1
  6. package/dist/socket/protocol.d.ts +2 -1
  7. package/dist/socket/protocol.d.ts.map +1 -1
  8. package/dist/socket/protocol.js.map +1 -1
  9. package/dist/socket/socket.test.js +37 -0
  10. package/dist/socket/socket.test.js.map +1 -1
  11. package/dist/socket/socketWithClientAuth.d.ts +1 -0
  12. package/dist/socket/socketWithClientAuth.d.ts.map +1 -1
  13. package/dist/socket/socketWithClientAuth.js +6 -1
  14. package/dist/socket/socketWithClientAuth.js.map +1 -1
  15. package/dist/socket/util.d.ts +22 -1
  16. package/dist/socket/util.d.ts.map +1 -1
  17. package/dist/socket/util.js +25 -3
  18. package/dist/socket/util.js.map +1 -1
  19. package/dist/socket/util.test.d.ts +2 -0
  20. package/dist/socket/util.test.d.ts.map +1 -0
  21. package/dist/socket/util.test.js +53 -0
  22. package/dist/socket/util.test.js.map +1 -0
  23. package/dist/types/git/index.d.ts.map +1 -1
  24. package/dist/types/git/index.js +2 -0
  25. package/dist/types/git/index.js.map +1 -1
  26. package/dist/types/git/index.test.d.ts +2 -0
  27. package/dist/types/git/index.test.d.ts.map +1 -0
  28. package/dist/types/git/index.test.js +13 -0
  29. package/dist/types/git/index.test.js.map +1 -0
  30. package/dist-esm/database-lifecycle/index.d.ts +7 -0
  31. package/dist-esm/database-lifecycle/index.d.ts.map +1 -1
  32. package/dist-esm/database-lifecycle/index.js.map +1 -1
  33. package/dist-esm/dbfs/types.d.ts +33 -0
  34. package/dist-esm/dbfs/types.d.ts.map +1 -1
  35. package/dist-esm/socket/protocol.d.ts +2 -1
  36. package/dist-esm/socket/protocol.d.ts.map +1 -1
  37. package/dist-esm/socket/protocol.js.map +1 -1
  38. package/dist-esm/socket/socket.test.js +37 -0
  39. package/dist-esm/socket/socket.test.js.map +1 -1
  40. package/dist-esm/socket/socketWithClientAuth.d.ts +1 -0
  41. package/dist-esm/socket/socketWithClientAuth.d.ts.map +1 -1
  42. package/dist-esm/socket/socketWithClientAuth.js +6 -1
  43. package/dist-esm/socket/socketWithClientAuth.js.map +1 -1
  44. package/dist-esm/socket/util.d.ts +22 -1
  45. package/dist-esm/socket/util.d.ts.map +1 -1
  46. package/dist-esm/socket/util.js +23 -2
  47. package/dist-esm/socket/util.js.map +1 -1
  48. package/dist-esm/socket/util.test.d.ts +2 -0
  49. package/dist-esm/socket/util.test.d.ts.map +1 -0
  50. package/dist-esm/socket/util.test.js +51 -0
  51. package/dist-esm/socket/util.test.js.map +1 -0
  52. package/dist-esm/types/git/index.d.ts.map +1 -1
  53. package/dist-esm/types/git/index.js +2 -0
  54. package/dist-esm/types/git/index.js.map +1 -1
  55. package/dist-esm/types/git/index.test.d.ts +2 -0
  56. package/dist-esm/types/git/index.test.d.ts.map +1 -0
  57. package/dist-esm/types/git/index.test.js +11 -0
  58. package/dist-esm/types/git/index.test.js.map +1 -0
  59. package/package.json +2 -2
  60. package/src/database-lifecycle/index.ts +8 -0
  61. package/src/dbfs/types.ts +39 -0
  62. package/src/socket/protocol.ts +8 -1
  63. package/src/socket/socket.test.ts +43 -0
  64. package/src/socket/socketWithClientAuth.ts +6 -1
  65. package/src/socket/util.test.ts +71 -0
  66. package/src/socket/util.ts +32 -2
  67. package/src/types/git/index.test.ts +15 -0
  68. 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.hasSentAuth = true;
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
+ });
@@ -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, protocol?: string | string[], timeout = 30_000): Promise<WebSocket> {
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, { maxPayload: MAX_SOCKET_PAYLOAD_BYTES });
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
+ });
@@ -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