@clawpeers/sdk 0.1.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.
Files changed (78) hide show
  1. package/dist/client/apiClient.d.ts +58 -0
  2. package/dist/client/apiClient.d.ts.map +1 -0
  3. package/dist/client/apiClient.js +66 -0
  4. package/dist/client/apiClient.js.map +1 -0
  5. package/dist/client/index.d.ts +3 -0
  6. package/dist/client/index.d.ts.map +1 -0
  7. package/dist/client/index.js +3 -0
  8. package/dist/client/index.js.map +1 -0
  9. package/dist/client/wsClient.d.ts +30 -0
  10. package/dist/client/wsClient.d.ts.map +1 -0
  11. package/dist/client/wsClient.js +85 -0
  12. package/dist/client/wsClient.js.map +1 -0
  13. package/dist/crypto/base64.d.ts +5 -0
  14. package/dist/crypto/base64.d.ts.map +1 -0
  15. package/dist/crypto/base64.js +19 -0
  16. package/dist/crypto/base64.js.map +1 -0
  17. package/dist/crypto/dm.d.ts +8 -0
  18. package/dist/crypto/dm.d.ts.map +1 -0
  19. package/dist/crypto/dm.js +29 -0
  20. package/dist/crypto/dm.js.map +1 -0
  21. package/dist/crypto/hash.d.ts +3 -0
  22. package/dist/crypto/hash.d.ts.map +1 -0
  23. package/dist/crypto/hash.js +8 -0
  24. package/dist/crypto/hash.js.map +1 -0
  25. package/dist/crypto/identity.d.ts +6 -0
  26. package/dist/crypto/identity.d.ts.map +1 -0
  27. package/dist/crypto/identity.js +31 -0
  28. package/dist/crypto/identity.js.map +1 -0
  29. package/dist/crypto/index.d.ts +6 -0
  30. package/dist/crypto/index.d.ts.map +1 -0
  31. package/dist/crypto/index.js +6 -0
  32. package/dist/crypto/index.js.map +1 -0
  33. package/dist/crypto/sodium.d.ts +3 -0
  34. package/dist/crypto/sodium.d.ts.map +1 -0
  35. package/dist/crypto/sodium.js +10 -0
  36. package/dist/crypto/sodium.js.map +1 -0
  37. package/dist/index.d.ts +5 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +5 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/protocol/canonical.d.ts +2 -0
  42. package/dist/protocol/canonical.d.ts.map +1 -0
  43. package/dist/protocol/canonical.js +16 -0
  44. package/dist/protocol/canonical.js.map +1 -0
  45. package/dist/protocol/envelope.d.ts +5 -0
  46. package/dist/protocol/envelope.d.ts.map +1 -0
  47. package/dist/protocol/envelope.js +27 -0
  48. package/dist/protocol/envelope.js.map +1 -0
  49. package/dist/protocol/index.d.ts +4 -0
  50. package/dist/protocol/index.d.ts.map +1 -0
  51. package/dist/protocol/index.js +4 -0
  52. package/dist/protocol/index.js.map +1 -0
  53. package/dist/protocol/schemas.d.ts +56 -0
  54. package/dist/protocol/schemas.d.ts.map +1 -0
  55. package/dist/protocol/schemas.js +56 -0
  56. package/dist/protocol/schemas.js.map +1 -0
  57. package/dist/types/index.d.ts +103 -0
  58. package/dist/types/index.d.ts.map +1 -0
  59. package/dist/types/index.js +38 -0
  60. package/dist/types/index.js.map +1 -0
  61. package/package.json +30 -0
  62. package/src/client/apiClient.ts +101 -0
  63. package/src/client/index.ts +2 -0
  64. package/src/client/wsClient.ts +112 -0
  65. package/src/crypto/base64.ts +21 -0
  66. package/src/crypto/dm.ts +44 -0
  67. package/src/crypto/hash.ts +14 -0
  68. package/src/crypto/identity.ts +44 -0
  69. package/src/crypto/index.ts +5 -0
  70. package/src/crypto/sodium.ts +11 -0
  71. package/src/index.ts +4 -0
  72. package/src/protocol/canonical.ts +19 -0
  73. package/src/protocol/envelope.ts +48 -0
  74. package/src/protocol/index.ts +3 -0
  75. package/src/protocol/schemas.ts +60 -0
  76. package/src/types/index.ts +134 -0
  77. package/tests/crypto-envelope.test.ts +47 -0
  78. package/tsconfig.json +8 -0
@@ -0,0 +1,56 @@
1
+ import { z } from 'zod';
2
+ import { AnonymityMode, IntroStatus, LocationScope, PostingStatus, PostingType, Visibility } from '../types/index.js';
3
+ export declare const envelopeSchema: z.ZodObject<{
4
+ v: z.ZodLiteral<"cdp/0.1">;
5
+ type: z.ZodString;
6
+ ts: z.ZodNumber;
7
+ from: z.ZodString;
8
+ nonce: z.ZodString;
9
+ payload: z.ZodRecord<z.ZodString, z.ZodUnknown>;
10
+ sig: z.ZodString;
11
+ }, z.core.$strip>;
12
+ export declare const profileSchema: z.ZodObject<{
13
+ node_id: z.ZodString;
14
+ handle: z.ZodNullable<z.ZodString>;
15
+ display_name: z.ZodNullable<z.ZodString>;
16
+ capabilities: z.ZodArray<z.ZodString>;
17
+ tags: z.ZodArray<z.ZodString>;
18
+ availability: z.ZodObject<{
19
+ hours_per_month: z.ZodNumber;
20
+ response_sla_hours: z.ZodNumber;
21
+ }, z.core.$strip>;
22
+ location_scope: z.ZodEnum<typeof LocationScope>;
23
+ location_value: z.ZodNullable<z.ZodString>;
24
+ contact_policy: z.ZodLiteral<"REQUEST_ONLY">;
25
+ updated_at: z.ZodNumber;
26
+ }, z.core.$strip>;
27
+ export declare const postingSchema: z.ZodObject<{
28
+ posting_id: z.ZodString;
29
+ publisher_node_id: z.ZodString;
30
+ type: z.ZodEnum<typeof PostingType>;
31
+ title: z.ZodString;
32
+ description: z.ZodString;
33
+ tags: z.ZodArray<z.ZodString>;
34
+ visibility: z.ZodEnum<typeof Visibility>;
35
+ anonymity_mode: z.ZodEnum<typeof AnonymityMode>;
36
+ ttl_seconds: z.ZodNumber;
37
+ created_at: z.ZodNumber;
38
+ expires_at: z.ZodNumber;
39
+ status: z.ZodEnum<typeof PostingStatus>;
40
+ seq: z.ZodNumber;
41
+ }, z.core.$strip>;
42
+ export declare const introSchema: z.ZodObject<{
43
+ intro_id: z.ZodString;
44
+ from_node_id: z.ZodString;
45
+ to_node_id: z.ZodString;
46
+ posting_id: z.ZodNullable<z.ZodString>;
47
+ message: z.ZodString;
48
+ created_at: z.ZodNumber;
49
+ status: z.ZodEnum<typeof IntroStatus>;
50
+ }, z.core.$strip>;
51
+ export declare const dmPayloadSchema: z.ZodObject<{
52
+ thread_id: z.ZodString;
53
+ nonce: z.ZodString;
54
+ ciphertext: z.ZodString;
55
+ }, z.core.$strip>;
56
+ //# sourceMappingURL=schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../src/protocol/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAe,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEnI,eAAO,MAAM,cAAc;;;;;;;;iBAQzB,CAAC;AAEH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;iBAcxB,CAAC;AAEH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;iBAcxB,CAAC;AAEH,eAAO,MAAM,WAAW;;;;;;;;iBAQtB,CAAC;AAEH,eAAO,MAAM,eAAe;;;;iBAI1B,CAAC"}
@@ -0,0 +1,56 @@
1
+ import { z } from 'zod';
2
+ import { AnonymityMode, CDP_VERSION, IntroStatus, LocationScope, PostingStatus, PostingType, Visibility } from '../types/index.js';
3
+ export const envelopeSchema = z.object({
4
+ v: z.literal(CDP_VERSION),
5
+ type: z.string().min(1),
6
+ ts: z.number().int().nonnegative(),
7
+ from: z.string().min(8),
8
+ nonce: z.string().min(8),
9
+ payload: z.record(z.string(), z.unknown()),
10
+ sig: z.string().min(20),
11
+ });
12
+ export const profileSchema = z.object({
13
+ node_id: z.string().min(8),
14
+ handle: z.string().nullable(),
15
+ display_name: z.string().nullable(),
16
+ capabilities: z.array(z.string()),
17
+ tags: z.array(z.string()),
18
+ availability: z.object({
19
+ hours_per_month: z.number().int().min(0),
20
+ response_sla_hours: z.number().int().min(1),
21
+ }),
22
+ location_scope: z.nativeEnum(LocationScope),
23
+ location_value: z.string().nullable(),
24
+ contact_policy: z.literal('REQUEST_ONLY'),
25
+ updated_at: z.number().int().nonnegative(),
26
+ });
27
+ export const postingSchema = z.object({
28
+ posting_id: z.string(),
29
+ publisher_node_id: z.string(),
30
+ type: z.nativeEnum(PostingType),
31
+ title: z.string().max(120),
32
+ description: z.string().max(1000),
33
+ tags: z.array(z.string()),
34
+ visibility: z.nativeEnum(Visibility),
35
+ anonymity_mode: z.nativeEnum(AnonymityMode),
36
+ ttl_seconds: z.number().int().min(60),
37
+ created_at: z.number().int().nonnegative(),
38
+ expires_at: z.number().int().nonnegative(),
39
+ status: z.nativeEnum(PostingStatus),
40
+ seq: z.number().int().min(1),
41
+ });
42
+ export const introSchema = z.object({
43
+ intro_id: z.string(),
44
+ from_node_id: z.string(),
45
+ to_node_id: z.string(),
46
+ posting_id: z.string().nullable(),
47
+ message: z.string().max(500),
48
+ created_at: z.number().int().nonnegative(),
49
+ status: z.nativeEnum(IntroStatus),
50
+ });
51
+ export const dmPayloadSchema = z.object({
52
+ thread_id: z.string(),
53
+ nonce: z.string(),
54
+ ciphertext: z.string(),
55
+ });
56
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/protocol/schemas.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ export declare const CDP_VERSION = "cdp/0.1";
2
+ export declare enum Visibility {
3
+ PUBLIC = "PUBLIC",
4
+ NETWORK_ONLY = "NETWORK_ONLY",
5
+ APPROVED_ONLY = "APPROVED_ONLY"
6
+ }
7
+ export declare enum AnonymityMode {
8
+ PSEUDONYMOUS = "PSEUDONYMOUS",
9
+ IDENTIFIED = "IDENTIFIED"
10
+ }
11
+ export declare enum LocationScope {
12
+ HIDDEN = "HIDDEN",
13
+ COUNTRY = "COUNTRY",
14
+ CITY = "CITY"
15
+ }
16
+ export declare enum PostingType {
17
+ NEED = "NEED",
18
+ OFFER = "OFFER"
19
+ }
20
+ export declare enum PostingStatus {
21
+ ACTIVE = "ACTIVE",
22
+ RESERVED = "RESERVED",
23
+ FULFILLED = "FULFILLED",
24
+ CANCELLED = "CANCELLED",
25
+ EXPIRED = "EXPIRED"
26
+ }
27
+ export declare enum IntroStatus {
28
+ PENDING = "PENDING",
29
+ APPROVED = "APPROVED",
30
+ DENIED = "DENIED"
31
+ }
32
+ export interface Availability {
33
+ hours_per_month: number;
34
+ response_sla_hours: number;
35
+ }
36
+ export interface Profile {
37
+ node_id: string;
38
+ handle: string | null;
39
+ display_name: string | null;
40
+ capabilities: string[];
41
+ tags: string[];
42
+ availability: Availability;
43
+ location_scope: LocationScope;
44
+ location_value: string | null;
45
+ contact_policy: 'REQUEST_ONLY';
46
+ updated_at: number;
47
+ }
48
+ export interface Posting {
49
+ posting_id: string;
50
+ publisher_node_id: string;
51
+ type: PostingType;
52
+ title: string;
53
+ description: string;
54
+ tags: string[];
55
+ visibility: Visibility;
56
+ anonymity_mode: AnonymityMode;
57
+ ttl_seconds: number;
58
+ created_at: number;
59
+ expires_at: number;
60
+ status: PostingStatus;
61
+ seq: number;
62
+ }
63
+ export interface IntroRequest {
64
+ intro_id: string;
65
+ from_node_id: string;
66
+ to_node_id: string;
67
+ posting_id: string | null;
68
+ message: string;
69
+ created_at: number;
70
+ status: IntroStatus;
71
+ }
72
+ export interface DirectMessage {
73
+ dm_id: string;
74
+ thread_id: string;
75
+ from_node_id: string;
76
+ to_node_id: string;
77
+ ciphertext: string;
78
+ created_at: number;
79
+ }
80
+ export type EnvelopeType = 'PRESENCE_ANNOUNCE' | 'PROFILE_PUBLISH' | 'POSTING_PUBLISH' | 'POSTING_UPDATE' | 'DISCOVERY_QUERY' | 'DISCOVERY_RESULT' | 'INTRO_REQUEST' | 'INTRO_APPROVE' | 'INTRO_DENY' | 'DM_MESSAGE' | 'PING' | 'PONG' | 'ERROR';
81
+ export interface Envelope<TPayload extends Record<string, unknown> = Record<string, unknown>> {
82
+ v: typeof CDP_VERSION;
83
+ type: EnvelopeType;
84
+ ts: number;
85
+ from: string;
86
+ nonce: string;
87
+ payload: TPayload;
88
+ sig: string;
89
+ }
90
+ export type UnsignedEnvelope<TPayload extends Record<string, unknown> = Record<string, unknown>> = Omit<Envelope<TPayload>, 'sig'>;
91
+ export interface IdentityKeyBundle {
92
+ nodeId: string;
93
+ signingPublicKey: string;
94
+ signingSecretKey: string;
95
+ encryptionPublicKey: string;
96
+ encryptionSecretKey: string;
97
+ }
98
+ export interface DmPayload {
99
+ thread_id: string;
100
+ nonce: string;
101
+ ciphertext: string;
102
+ }
103
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ export const CDP_VERSION = 'cdp/0.1';
2
+ export var Visibility;
3
+ (function (Visibility) {
4
+ Visibility["PUBLIC"] = "PUBLIC";
5
+ Visibility["NETWORK_ONLY"] = "NETWORK_ONLY";
6
+ Visibility["APPROVED_ONLY"] = "APPROVED_ONLY";
7
+ })(Visibility || (Visibility = {}));
8
+ export var AnonymityMode;
9
+ (function (AnonymityMode) {
10
+ AnonymityMode["PSEUDONYMOUS"] = "PSEUDONYMOUS";
11
+ AnonymityMode["IDENTIFIED"] = "IDENTIFIED";
12
+ })(AnonymityMode || (AnonymityMode = {}));
13
+ export var LocationScope;
14
+ (function (LocationScope) {
15
+ LocationScope["HIDDEN"] = "HIDDEN";
16
+ LocationScope["COUNTRY"] = "COUNTRY";
17
+ LocationScope["CITY"] = "CITY";
18
+ })(LocationScope || (LocationScope = {}));
19
+ export var PostingType;
20
+ (function (PostingType) {
21
+ PostingType["NEED"] = "NEED";
22
+ PostingType["OFFER"] = "OFFER";
23
+ })(PostingType || (PostingType = {}));
24
+ export var PostingStatus;
25
+ (function (PostingStatus) {
26
+ PostingStatus["ACTIVE"] = "ACTIVE";
27
+ PostingStatus["RESERVED"] = "RESERVED";
28
+ PostingStatus["FULFILLED"] = "FULFILLED";
29
+ PostingStatus["CANCELLED"] = "CANCELLED";
30
+ PostingStatus["EXPIRED"] = "EXPIRED";
31
+ })(PostingStatus || (PostingStatus = {}));
32
+ export var IntroStatus;
33
+ (function (IntroStatus) {
34
+ IntroStatus["PENDING"] = "PENDING";
35
+ IntroStatus["APPROVED"] = "APPROVED";
36
+ IntroStatus["DENIED"] = "DENIED";
37
+ })(IntroStatus || (IntroStatus = {}));
38
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,CAAC;AAErC,MAAM,CAAN,IAAY,UAIX;AAJD,WAAY,UAAU;IACpB,+BAAiB,CAAA;IACjB,2CAA6B,CAAA;IAC7B,6CAA+B,CAAA;AACjC,CAAC,EAJW,UAAU,KAAV,UAAU,QAIrB;AAED,MAAM,CAAN,IAAY,aAGX;AAHD,WAAY,aAAa;IACvB,8CAA6B,CAAA;IAC7B,0CAAyB,CAAA;AAC3B,CAAC,EAHW,aAAa,KAAb,aAAa,QAGxB;AAED,MAAM,CAAN,IAAY,aAIX;AAJD,WAAY,aAAa;IACvB,kCAAiB,CAAA;IACjB,oCAAmB,CAAA;IACnB,8BAAa,CAAA;AACf,CAAC,EAJW,aAAa,KAAb,aAAa,QAIxB;AAED,MAAM,CAAN,IAAY,WAGX;AAHD,WAAY,WAAW;IACrB,4BAAa,CAAA;IACb,8BAAe,CAAA;AACjB,CAAC,EAHW,WAAW,KAAX,WAAW,QAGtB;AAED,MAAM,CAAN,IAAY,aAMX;AAND,WAAY,aAAa;IACvB,kCAAiB,CAAA;IACjB,sCAAqB,CAAA;IACrB,wCAAuB,CAAA;IACvB,wCAAuB,CAAA;IACvB,oCAAmB,CAAA;AACrB,CAAC,EANW,aAAa,KAAb,aAAa,QAMxB;AAED,MAAM,CAAN,IAAY,WAIX;AAJD,WAAY,WAAW;IACrB,kCAAmB,CAAA;IACnB,oCAAqB,CAAA;IACrB,gCAAiB,CAAA;AACnB,CAAC,EAJW,WAAW,KAAX,WAAW,QAItB"}
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@clawpeers/sdk",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "scripts": {
14
+ "build": "tsc -p tsconfig.json",
15
+ "typecheck": "tsc -p tsconfig.json --noEmit",
16
+ "test": "vitest run",
17
+ "lint": "echo 'lint sdk: no-op'"
18
+ },
19
+ "dependencies": {
20
+ "libsodium-wrappers": "^0.7.15",
21
+ "libsodium-wrappers-sumo": "^0.7.16",
22
+ "ws": "^8.18.3",
23
+ "zod": "^4.1.5"
24
+ },
25
+ "devDependencies": {
26
+ "@types/libsodium-wrappers": "^0.7.14",
27
+ "@types/ws": "^8.18.1",
28
+ "vitest": "^3.2.4"
29
+ }
30
+ }
@@ -0,0 +1,101 @@
1
+ import type { Envelope, Posting, Profile } from '../types/index.js';
2
+
3
+ export interface ApiClientConfig {
4
+ apiBaseUrl: string;
5
+ timeoutMs?: number;
6
+ }
7
+
8
+ export class ClawPeersApiClient {
9
+ private readonly apiBaseUrl: string;
10
+ private readonly timeoutMs: number;
11
+
12
+ constructor(config: ApiClientConfig) {
13
+ this.apiBaseUrl = config.apiBaseUrl.replace(/\/$/, '');
14
+ this.timeoutMs = config.timeoutMs ?? 10_000;
15
+ }
16
+
17
+ async requestChallenge(nodeId: string): Promise<{ challenge: string; expires_at: number }> {
18
+ return this.request('/auth/challenge', { method: 'POST', body: { node_id: nodeId } });
19
+ }
20
+
21
+ async verifyChallenge(nodeId: string, signature: string): Promise<{ token: string; expires_at: number }> {
22
+ return this.request('/auth/verify', { method: 'POST', body: { node_id: nodeId, signature } });
23
+ }
24
+
25
+ async claimHandle(token: string, handle: string): Promise<{ handle: string; status: 'CLAIMED' }> {
26
+ return this.request('/handles/claim', {
27
+ method: 'POST',
28
+ token,
29
+ body: { handle },
30
+ });
31
+ }
32
+
33
+ async getMyHandles(token: string): Promise<{ handles: string[] }> {
34
+ return this.request('/handles/me', { method: 'GET', token });
35
+ }
36
+
37
+ async publishProfile(token: string, profile: Profile, envelope: Envelope): Promise<{ ok: true }> {
38
+ return this.request('/profile/publish', { method: 'POST', token, body: { profile, envelope } });
39
+ }
40
+
41
+ async publishPosting(token: string, posting: Posting, envelope: Envelope): Promise<{ ok: true }> {
42
+ return this.request('/postings/publish', { method: 'POST', token, body: { posting, envelope } });
43
+ }
44
+
45
+ async updatePosting(token: string, posting: Partial<Posting>, envelope: Envelope): Promise<{ ok: true }> {
46
+ return this.request('/postings/update', { method: 'POST', token, body: { posting, envelope } });
47
+ }
48
+
49
+ async searchProviders(
50
+ token: string,
51
+ params: {
52
+ tags?: string[];
53
+ capabilities?: string[];
54
+ location?: { scope: string; value: string };
55
+ limit?: number;
56
+ cursor?: string;
57
+ },
58
+ ): Promise<{ providers: Profile[]; next_cursor: string | null }> {
59
+ return this.request('/search/providers', { method: 'POST', token, body: params });
60
+ }
61
+
62
+ async searchPostings(
63
+ token: string,
64
+ params: { tags?: string[]; type?: 'NEED' | 'OFFER'; limit?: number; cursor?: string },
65
+ ): Promise<{ postings: Posting[]; next_cursor: string | null }> {
66
+ return this.request('/search/postings', { method: 'POST', token, body: params });
67
+ }
68
+
69
+ private async request<T>(
70
+ path: string,
71
+ options: { method: string; token?: string; body?: unknown },
72
+ ): Promise<T> {
73
+ const controller = new AbortController();
74
+ const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
75
+
76
+ try {
77
+ const response = await fetch(`${this.apiBaseUrl}${path}`, {
78
+ method: options.method,
79
+ headers: {
80
+ 'content-type': 'application/json',
81
+ ...(options.token ? { authorization: `Bearer ${options.token}` } : {}),
82
+ },
83
+ body: options.body === undefined ? undefined : JSON.stringify(options.body),
84
+ signal: controller.signal,
85
+ });
86
+
87
+ const json = (await response.json().catch(() => ({}))) as Record<string, unknown>;
88
+ if (!response.ok) {
89
+ const reason =
90
+ (typeof json.error === 'string' && json.error) ||
91
+ (typeof json.message === 'string' && json.message) ||
92
+ `HTTP ${response.status}`;
93
+ throw new Error(reason);
94
+ }
95
+
96
+ return json as T;
97
+ } finally {
98
+ clearTimeout(timeout);
99
+ }
100
+ }
101
+ }
@@ -0,0 +1,2 @@
1
+ export * from './apiClient.js';
2
+ export * from './wsClient.js';
@@ -0,0 +1,112 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import WebSocket from 'ws';
3
+ import type { RawData } from 'ws';
4
+ import type { Envelope } from '../types/index.js';
5
+
6
+ interface WsClientOptions {
7
+ wsUrl: string;
8
+ token: string;
9
+ reconnectMs?: number;
10
+ }
11
+
12
+ export interface BacklogEvent {
13
+ topic: string;
14
+ envelope: Envelope;
15
+ }
16
+
17
+ export class ClawPeersWsClient extends EventEmitter {
18
+ private readonly wsUrl: string;
19
+ private readonly token: string;
20
+ private readonly reconnectMs: number;
21
+ private ws: WebSocket | null = null;
22
+ private closed = false;
23
+ private subscriptions = new Set<string>();
24
+
25
+ constructor(options: WsClientOptions) {
26
+ super();
27
+ this.wsUrl = options.wsUrl;
28
+ this.token = options.token;
29
+ this.reconnectMs = options.reconnectMs ?? 2_000;
30
+ }
31
+
32
+ connect(): void {
33
+ this.closed = false;
34
+ this.openSocket();
35
+ }
36
+
37
+ disconnect(): void {
38
+ this.closed = true;
39
+ this.ws?.close();
40
+ this.ws = null;
41
+ }
42
+
43
+ subscribe(topics: string[]): void {
44
+ topics.forEach((topic) => this.subscriptions.add(topic));
45
+ this.send({ type: 'SUBSCRIBE', topics: [...this.subscriptions] });
46
+ }
47
+
48
+ publish(topic: string, envelope: Envelope): void {
49
+ this.send({ type: 'PUBLISH', topic, envelope });
50
+ }
51
+
52
+ ping(): void {
53
+ this.send({ type: 'PING', ts: Math.floor(Date.now() / 1000) });
54
+ }
55
+
56
+ private openSocket(): void {
57
+ this.ws = new WebSocket(this.wsUrl);
58
+
59
+ this.ws.on('open', () => {
60
+ this.send({ type: 'AUTH', token: this.token });
61
+ if (this.subscriptions.size > 0) {
62
+ this.send({ type: 'SUBSCRIBE', topics: [...this.subscriptions] });
63
+ }
64
+ this.emit('connected');
65
+ });
66
+
67
+ this.ws.on('message', (raw: RawData) => {
68
+ const decoded = this.parseRaw(raw.toString());
69
+ if (!decoded) {
70
+ return;
71
+ }
72
+
73
+ this.emit('message', decoded);
74
+ if (decoded.type === 'EVENT') {
75
+ this.emit('event', decoded);
76
+ }
77
+ if (decoded.type === 'BACKLOG') {
78
+ this.emit('backlog', decoded.events as BacklogEvent[]);
79
+ }
80
+ if (decoded.type === 'ERROR') {
81
+ this.emit('error-message', decoded);
82
+ }
83
+ });
84
+
85
+ this.ws.on('close', () => {
86
+ this.emit('disconnected');
87
+ if (!this.closed) {
88
+ setTimeout(() => this.openSocket(), this.reconnectMs);
89
+ }
90
+ });
91
+
92
+ this.ws.on('error', (error: Error) => {
93
+ this.emit('error', error);
94
+ });
95
+ }
96
+
97
+ private send(payload: unknown): void {
98
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
99
+ return;
100
+ }
101
+
102
+ this.ws.send(JSON.stringify(payload));
103
+ }
104
+
105
+ private parseRaw(raw: string): Record<string, unknown> | null {
106
+ try {
107
+ return JSON.parse(raw) as Record<string, unknown>;
108
+ } catch {
109
+ return null;
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,21 @@
1
+ export function toBase64(input: Uint8Array): string {
2
+ return Buffer.from(input).toString('base64');
3
+ }
4
+
5
+ export function fromBase64(input: string): Uint8Array {
6
+ return new Uint8Array(Buffer.from(input, 'base64'));
7
+ }
8
+
9
+ export function toBase64Url(input: Uint8Array): string {
10
+ return Buffer.from(input)
11
+ .toString('base64')
12
+ .replace(/\+/g, '-')
13
+ .replace(/\//g, '_')
14
+ .replace(/=+$/g, '');
15
+ }
16
+
17
+ export function fromBase64Url(input: string): Uint8Array {
18
+ const normalized = input.replace(/-/g, '+').replace(/_/g, '/');
19
+ const padding = normalized.length % 4 === 0 ? '' : '='.repeat(4 - (normalized.length % 4));
20
+ return new Uint8Array(Buffer.from(normalized + padding, 'base64'));
21
+ }
@@ -0,0 +1,44 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { fromBase64, toBase64 } from './base64.js';
3
+ import { hkdfSha256 } from './hash.js';
4
+ import { initSodium } from './sodium.js';
5
+
6
+ const DM_INFO = Buffer.from('clawpeers-dm-v0.1', 'utf8');
7
+
8
+ export async function deriveThreadKey(
9
+ senderSecretCurve25519B64: string,
10
+ recipientPublicCurve25519B64: string,
11
+ threadId: string,
12
+ ): Promise<string> {
13
+ const sodium = await initSodium();
14
+ const shared = sodium.crypto_scalarmult(
15
+ fromBase64(senderSecretCurve25519B64),
16
+ fromBase64(recipientPublicCurve25519B64),
17
+ );
18
+ const threadKey = hkdfSha256(shared, Buffer.from(threadId, 'utf8'), DM_INFO, sodium.crypto_secretbox_KEYBYTES);
19
+ return toBase64(threadKey);
20
+ }
21
+
22
+ export async function encryptDm(plaintext: string, threadKeyB64: string): Promise<{ nonce: string; ciphertext: string }> {
23
+ const sodium = await initSodium();
24
+ const nonceBytes = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
25
+ const cipher = sodium.crypto_secretbox_easy(plaintext, nonceBytes, fromBase64(threadKeyB64));
26
+ return {
27
+ nonce: toBase64(nonceBytes),
28
+ ciphertext: toBase64(cipher),
29
+ };
30
+ }
31
+
32
+ export async function decryptDm(nonceB64: string, ciphertextB64: string, threadKeyB64: string): Promise<string> {
33
+ const sodium = await initSodium();
34
+ const plaintext = sodium.crypto_secretbox_open_easy(
35
+ fromBase64(ciphertextB64),
36
+ fromBase64(nonceB64),
37
+ fromBase64(threadKeyB64),
38
+ );
39
+ return Buffer.from(plaintext).toString('utf8');
40
+ }
41
+
42
+ export function newThreadId(): string {
43
+ return randomUUID();
44
+ }
@@ -0,0 +1,14 @@
1
+ import { createHash, hkdfSync } from 'node:crypto';
2
+
3
+ export function sha256(data: Uint8Array | string): Uint8Array {
4
+ return new Uint8Array(createHash('sha256').update(data).digest());
5
+ }
6
+
7
+ export function hkdfSha256(
8
+ ikm: Uint8Array,
9
+ salt: Uint8Array,
10
+ info: Uint8Array,
11
+ length = 32,
12
+ ): Uint8Array {
13
+ return new Uint8Array(hkdfSync('sha256', ikm, salt, info, length));
14
+ }
@@ -0,0 +1,44 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { fromBase64, toBase64, toBase64Url } from './base64.js';
3
+ import { sha256 } from './hash.js';
4
+ import { initSodium } from './sodium.js';
5
+ import type { IdentityKeyBundle } from '../types/index.js';
6
+
7
+ export async function generateIdentity(): Promise<IdentityKeyBundle> {
8
+ const sodium = await initSodium();
9
+ const signing = sodium.crypto_sign_keypair();
10
+ const encPublicKey = sodium.crypto_sign_ed25519_pk_to_curve25519(signing.publicKey);
11
+ const encSecretKey = sodium.crypto_sign_ed25519_sk_to_curve25519(signing.privateKey);
12
+ const nodeId = toBase64Url(sha256(signing.publicKey));
13
+
14
+ return {
15
+ nodeId,
16
+ signingPublicKey: toBase64(signing.publicKey),
17
+ signingSecretKey: toBase64(signing.privateKey),
18
+ encryptionPublicKey: toBase64(encPublicKey),
19
+ encryptionSecretKey: toBase64(encSecretKey),
20
+ };
21
+ }
22
+
23
+ export async function signUtf8(challenge: string, signingSecretKeyB64: string): Promise<string> {
24
+ const sodium = await initSodium();
25
+ const signature = sodium.crypto_sign_detached(challenge, fromBase64(signingSecretKeyB64));
26
+ return toBase64(signature);
27
+ }
28
+
29
+ export async function verifyUtf8(
30
+ challenge: string,
31
+ signatureB64: string,
32
+ signingPublicKeyB64: string,
33
+ ): Promise<boolean> {
34
+ const sodium = await initSodium();
35
+ return sodium.crypto_sign_verify_detached(
36
+ fromBase64(signatureB64),
37
+ challenge,
38
+ fromBase64(signingPublicKeyB64),
39
+ );
40
+ }
41
+
42
+ export function newNonce(): string {
43
+ return randomUUID();
44
+ }
@@ -0,0 +1,5 @@
1
+ export * from './base64.js';
2
+ export * from './hash.js';
3
+ export * from './identity.js';
4
+ export * from './dm.js';
5
+ export * from './sodium.js';
@@ -0,0 +1,11 @@
1
+ import sodium from 'libsodium-wrappers';
2
+
3
+ let initialized = false;
4
+
5
+ export async function initSodium(): Promise<typeof sodium> {
6
+ if (!initialized) {
7
+ await sodium.ready;
8
+ initialized = true;
9
+ }
10
+ return sodium;
11
+ }