@stvor/sdk 2.4.1 → 3.0.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/facade/app.d.ts +83 -76
- package/dist/facade/app.js +330 -195
- package/dist/facade/crypto-session.cjs +29 -0
- package/dist/facade/crypto-session.d.ts +71 -0
- package/dist/facade/crypto-session.js +152 -0
- package/dist/facade/errors.d.ts +29 -12
- package/dist/facade/errors.js +49 -8
- package/dist/facade/index.d.ts +27 -8
- package/dist/facade/index.js +23 -3
- package/dist/facade/local-storage-identity-store.cjs +29 -0
- package/dist/facade/local-storage-identity-store.d.ts +50 -0
- package/dist/facade/local-storage-identity-store.js +100 -0
- package/dist/facade/metrics-attestation.cjs +29 -0
- package/dist/facade/metrics-attestation.d.ts +209 -0
- package/dist/facade/metrics-attestation.js +333 -0
- package/dist/facade/metrics-engine.cjs +29 -0
- package/dist/facade/metrics-engine.d.ts +91 -0
- package/dist/facade/metrics-engine.js +170 -0
- package/dist/facade/redis-replay-cache.cjs +29 -0
- package/dist/facade/redis-replay-cache.d.ts +88 -0
- package/dist/facade/redis-replay-cache.js +60 -0
- package/dist/facade/relay-client.d.ts +22 -23
- package/dist/facade/relay-client.js +107 -128
- package/dist/facade/replay-manager.cjs +29 -0
- package/dist/facade/replay-manager.d.ts +51 -0
- package/dist/facade/replay-manager.js +150 -0
- package/dist/facade/sodium-singleton.cjs +29 -0
- package/dist/facade/sodium-singleton.d.ts +20 -0
- package/dist/facade/sodium-singleton.js +44 -0
- package/dist/facade/tofu-manager.cjs +29 -0
- package/dist/facade/tofu-manager.d.ts +82 -0
- package/dist/facade/tofu-manager.js +166 -0
- package/dist/facade/types.d.ts +2 -0
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +7 -0
- package/dist/legacy.d.ts +31 -1
- package/dist/legacy.js +90 -2
- package/dist/ratchet/core-production.cjs +29 -0
- package/dist/ratchet/core-production.d.ts +95 -0
- package/dist/ratchet/core-production.js +286 -0
- package/dist/{facade/crypto.cjs → ratchet/index.cjs} +1 -1
- package/dist/ratchet/index.d.ts +59 -0
- package/dist/ratchet/index.js +343 -0
- package/dist/ratchet/key-recovery.cjs +29 -0
- package/dist/ratchet/key-recovery.d.ts +45 -0
- package/dist/ratchet/key-recovery.js +148 -0
- package/dist/ratchet/replay-protection.cjs +29 -0
- package/dist/ratchet/replay-protection.d.ts +21 -0
- package/dist/ratchet/replay-protection.js +50 -0
- package/dist/{mock-relay-server.cjs → ratchet/tofu.cjs} +1 -1
- package/dist/ratchet/tofu.d.ts +27 -0
- package/dist/ratchet/tofu.js +62 -0
- package/dist/src/facade/app.cjs +29 -0
- package/dist/src/facade/app.d.ts +105 -0
- package/dist/src/facade/app.js +245 -0
- package/dist/src/facade/crypto.cjs +29 -0
- package/dist/src/facade/errors.cjs +29 -0
- package/dist/src/facade/errors.d.ts +19 -0
- package/dist/src/facade/errors.js +21 -0
- package/dist/src/facade/index.cjs +29 -0
- package/dist/src/facade/index.d.ts +8 -0
- package/dist/src/facade/index.js +5 -0
- package/dist/src/facade/relay-client.cjs +29 -0
- package/dist/src/facade/relay-client.d.ts +36 -0
- package/dist/src/facade/relay-client.js +154 -0
- package/dist/src/facade/types.cjs +29 -0
- package/dist/src/facade/types.d.ts +50 -0
- package/dist/src/facade/types.js +4 -0
- package/dist/src/index.cjs +29 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +2 -0
- package/dist/src/legacy.cjs +29 -0
- package/dist/src/legacy.d.ts +0 -0
- package/dist/src/legacy.js +1 -0
- package/dist/src/mock-relay-server.cjs +29 -0
- package/package.json +16 -5
- /package/dist/{facade → src/facade}/crypto.d.ts +0 -0
- /package/dist/{facade → src/facade}/crypto.js +0 -0
- /package/dist/{mock-relay-server.d.ts → src/mock-relay-server.d.ts} +0 -0
- /package/dist/{mock-relay-server.js → src/mock-relay-server.js} +0 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* STVOR Crypto Session Manager
|
|
3
|
+
* Uses ONLY Node.js built-in crypto module — zero external dependencies
|
|
4
|
+
*
|
|
5
|
+
* Manages identity keys (IK + SPK), ECDSA signatures,
|
|
6
|
+
* X3DH session establishment, and Double Ratchet encrypt/decrypt.
|
|
7
|
+
*/
|
|
8
|
+
import { KeyPair } from '../ratchet/index.js';
|
|
9
|
+
export interface IdentityKeys {
|
|
10
|
+
identityKeyPair: KeyPair;
|
|
11
|
+
signedPreKeyPair: KeyPair;
|
|
12
|
+
signedPreKeySignature: Buffer;
|
|
13
|
+
}
|
|
14
|
+
export interface SerializedPublicKeys {
|
|
15
|
+
identityKey: string;
|
|
16
|
+
signedPreKey: string;
|
|
17
|
+
signedPreKeySignature: string;
|
|
18
|
+
oneTimePreKey: string;
|
|
19
|
+
}
|
|
20
|
+
export interface IIdentityStore {
|
|
21
|
+
saveIdentityKeys(userId: string, keys: {
|
|
22
|
+
identityKeyPair: {
|
|
23
|
+
publicKey: string;
|
|
24
|
+
privateKey: string;
|
|
25
|
+
};
|
|
26
|
+
signedPreKeyPair: {
|
|
27
|
+
publicKey: string;
|
|
28
|
+
privateKey: string;
|
|
29
|
+
};
|
|
30
|
+
signedPreKeySignature: string;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
loadIdentityKeys(userId: string): Promise<{
|
|
33
|
+
identityKeyPair: {
|
|
34
|
+
publicKey: string;
|
|
35
|
+
privateKey: string;
|
|
36
|
+
};
|
|
37
|
+
signedPreKeyPair: {
|
|
38
|
+
publicKey: string;
|
|
39
|
+
privateKey: string;
|
|
40
|
+
};
|
|
41
|
+
signedPreKeySignature: string;
|
|
42
|
+
} | null>;
|
|
43
|
+
}
|
|
44
|
+
export interface ISessionStore {
|
|
45
|
+
saveSession(userId: string, peerId: string, sessionData: Buffer): Promise<void>;
|
|
46
|
+
loadSession(userId: string, peerId: string): Promise<Buffer | null>;
|
|
47
|
+
deleteSession(userId: string, peerId: string): Promise<void>;
|
|
48
|
+
listSessions(userId: string): Promise<string[]>;
|
|
49
|
+
}
|
|
50
|
+
export declare class CryptoSessionManager {
|
|
51
|
+
private userId;
|
|
52
|
+
private identityKeys;
|
|
53
|
+
private sessions;
|
|
54
|
+
private initialized;
|
|
55
|
+
private initPromise;
|
|
56
|
+
private identityStore;
|
|
57
|
+
private sessionStore;
|
|
58
|
+
constructor(userId: string, identityStore?: IIdentityStore, sessionStore?: ISessionStore);
|
|
59
|
+
initialize(): Promise<void>;
|
|
60
|
+
private _doInit;
|
|
61
|
+
getPublicKeys(): SerializedPublicKeys;
|
|
62
|
+
establishSession(peerId: string, peerPublicKeys: SerializedPublicKeys): Promise<void>;
|
|
63
|
+
establishSessionWithPeer(peerId: string, pk: SerializedPublicKeys): Promise<void>;
|
|
64
|
+
hasSession(peerId: string): boolean;
|
|
65
|
+
encryptForPeer(peerId: string, plaintext: string): {
|
|
66
|
+
ciphertext: string;
|
|
67
|
+
header: string;
|
|
68
|
+
};
|
|
69
|
+
decryptFromPeer(peerId: string, ciphertext: string, header: string): string;
|
|
70
|
+
forceRatchet(peerId: string): Promise<void>;
|
|
71
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* STVOR Crypto Session Manager
|
|
3
|
+
* Uses ONLY Node.js built-in crypto module — zero external dependencies
|
|
4
|
+
*
|
|
5
|
+
* Manages identity keys (IK + SPK), ECDSA signatures,
|
|
6
|
+
* X3DH session establishment, and Double Ratchet encrypt/decrypt.
|
|
7
|
+
*/
|
|
8
|
+
import { generateKeyPair, encryptMessage as ratchetEncrypt, decryptMessage as ratchetDecrypt, establishSession as ratchetEstablishSession, serializeSession, initializeCrypto, ecSign, ecVerify, } from '../ratchet/index.js';
|
|
9
|
+
/* ================================================================
|
|
10
|
+
* Helpers
|
|
11
|
+
* ================================================================ */
|
|
12
|
+
function toB64(buf) { return buf.toString('base64url'); }
|
|
13
|
+
function fromB64(s) { return Buffer.from(s, 'base64url'); }
|
|
14
|
+
/* ================================================================
|
|
15
|
+
* CryptoSessionManager
|
|
16
|
+
* ================================================================ */
|
|
17
|
+
export class CryptoSessionManager {
|
|
18
|
+
constructor(userId, identityStore, sessionStore) {
|
|
19
|
+
this.identityKeys = null;
|
|
20
|
+
this.sessions = new Map();
|
|
21
|
+
this.initialized = false;
|
|
22
|
+
this.initPromise = null;
|
|
23
|
+
this.identityStore = null;
|
|
24
|
+
this.sessionStore = null;
|
|
25
|
+
this.userId = userId;
|
|
26
|
+
this.identityStore = identityStore || null;
|
|
27
|
+
this.sessionStore = sessionStore || null;
|
|
28
|
+
}
|
|
29
|
+
/* ---- Initialisation ---- */
|
|
30
|
+
async initialize() {
|
|
31
|
+
if (this.initialized && this.identityKeys)
|
|
32
|
+
return;
|
|
33
|
+
if (this.initPromise)
|
|
34
|
+
return this.initPromise;
|
|
35
|
+
this.initPromise = this._doInit();
|
|
36
|
+
return this.initPromise;
|
|
37
|
+
}
|
|
38
|
+
async _doInit() {
|
|
39
|
+
await initializeCrypto();
|
|
40
|
+
if (this.identityStore) {
|
|
41
|
+
try {
|
|
42
|
+
const stored = await this.identityStore.loadIdentityKeys(this.userId);
|
|
43
|
+
if (stored) {
|
|
44
|
+
this.identityKeys = {
|
|
45
|
+
identityKeyPair: {
|
|
46
|
+
publicKey: fromB64(stored.identityKeyPair.publicKey),
|
|
47
|
+
privateKey: fromB64(stored.identityKeyPair.privateKey),
|
|
48
|
+
},
|
|
49
|
+
signedPreKeyPair: {
|
|
50
|
+
publicKey: fromB64(stored.signedPreKeyPair.publicKey),
|
|
51
|
+
privateKey: fromB64(stored.signedPreKeyPair.privateKey),
|
|
52
|
+
},
|
|
53
|
+
signedPreKeySignature: fromB64(stored.signedPreKeySignature),
|
|
54
|
+
};
|
|
55
|
+
this.initialized = true;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
console.warn('Failed to load identity keys:', e);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const ik = generateKeyPair();
|
|
64
|
+
const spk = generateKeyPair();
|
|
65
|
+
const sig = ecSign(spk.publicKey, ik);
|
|
66
|
+
this.identityKeys = {
|
|
67
|
+
identityKeyPair: ik,
|
|
68
|
+
signedPreKeyPair: spk,
|
|
69
|
+
signedPreKeySignature: sig,
|
|
70
|
+
};
|
|
71
|
+
if (this.identityStore) {
|
|
72
|
+
try {
|
|
73
|
+
await this.identityStore.saveIdentityKeys(this.userId, {
|
|
74
|
+
identityKeyPair: { publicKey: toB64(ik.publicKey), privateKey: toB64(ik.privateKey) },
|
|
75
|
+
signedPreKeyPair: { publicKey: toB64(spk.publicKey), privateKey: toB64(spk.privateKey) },
|
|
76
|
+
signedPreKeySignature: toB64(sig),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
console.warn('Failed to save identity keys:', e);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
this.initialized = true;
|
|
84
|
+
}
|
|
85
|
+
/* ---- Public keys ---- */
|
|
86
|
+
getPublicKeys() {
|
|
87
|
+
if (!this.identityKeys)
|
|
88
|
+
throw new Error('Not initialized');
|
|
89
|
+
return {
|
|
90
|
+
identityKey: toB64(this.identityKeys.identityKeyPair.publicKey),
|
|
91
|
+
signedPreKey: toB64(this.identityKeys.signedPreKeyPair.publicKey),
|
|
92
|
+
signedPreKeySignature: toB64(this.identityKeys.signedPreKeySignature),
|
|
93
|
+
oneTimePreKey: '',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/* ---- Session establishment ---- */
|
|
97
|
+
async establishSession(peerId, peerPublicKeys) {
|
|
98
|
+
if (!this.identityKeys)
|
|
99
|
+
throw new Error('Not initialized');
|
|
100
|
+
const peerIK = fromB64(peerPublicKeys.identityKey);
|
|
101
|
+
const peerSPK = fromB64(peerPublicKeys.signedPreKey);
|
|
102
|
+
const peerSig = peerPublicKeys.signedPreKeySignature
|
|
103
|
+
? fromB64(peerPublicKeys.signedPreKeySignature)
|
|
104
|
+
: Buffer.alloc(0);
|
|
105
|
+
if (peerSig.length > 0 && !ecVerify(peerSPK, peerSig, peerIK)) {
|
|
106
|
+
throw new Error('Invalid signed pre-key signature — possible MITM attack');
|
|
107
|
+
}
|
|
108
|
+
const session = ratchetEstablishSession(this.identityKeys.identityKeyPair, this.identityKeys.signedPreKeyPair, peerIK, peerSPK);
|
|
109
|
+
this.sessions.set(peerId, session);
|
|
110
|
+
if (this.sessionStore) {
|
|
111
|
+
try {
|
|
112
|
+
await this.sessionStore.saveSession(this.userId, peerId, serializeSession(session));
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
console.warn('Failed to save session:', e);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async establishSessionWithPeer(peerId, pk) {
|
|
120
|
+
return this.establishSession(peerId, pk);
|
|
121
|
+
}
|
|
122
|
+
hasSession(peerId) {
|
|
123
|
+
return this.sessions.has(peerId);
|
|
124
|
+
}
|
|
125
|
+
/* ---- Encrypt ---- */
|
|
126
|
+
encryptForPeer(peerId, plaintext) {
|
|
127
|
+
const session = this.sessions.get(peerId);
|
|
128
|
+
if (!session)
|
|
129
|
+
throw new Error('No session with peer');
|
|
130
|
+
const { ciphertext, header } = ratchetEncrypt(session, Buffer.from(plaintext, 'utf-8'));
|
|
131
|
+
return { ciphertext: toB64(ciphertext), header: toB64(header) };
|
|
132
|
+
}
|
|
133
|
+
/* ---- Decrypt ---- */
|
|
134
|
+
decryptFromPeer(peerId, ciphertext, header) {
|
|
135
|
+
const session = this.sessions.get(peerId);
|
|
136
|
+
if (!session)
|
|
137
|
+
throw new Error('No session with peer');
|
|
138
|
+
const pt = ratchetDecrypt(session, fromB64(ciphertext), fromB64(header));
|
|
139
|
+
return pt.toString('utf-8');
|
|
140
|
+
}
|
|
141
|
+
/* ---- Post-compromise ---- */
|
|
142
|
+
async forceRatchet(peerId) {
|
|
143
|
+
const session = this.sessions.get(peerId);
|
|
144
|
+
if (session) {
|
|
145
|
+
session.myRatchetKeyPair = generateKeyPair();
|
|
146
|
+
session.sendCount = 0;
|
|
147
|
+
session.recvCount = 0;
|
|
148
|
+
session.prevSendCount = 0;
|
|
149
|
+
session.isPostCompromise = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
package/dist/facade/errors.d.ts
CHANGED
|
@@ -1,19 +1,36 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* STVOR DX Facade - Error Handling
|
|
3
|
+
*/
|
|
4
|
+
export declare const ErrorCode: {
|
|
5
|
+
readonly AUTH_FAILED: "AUTH_FAILED";
|
|
2
6
|
readonly INVALID_APP_TOKEN: "INVALID_APP_TOKEN";
|
|
3
|
-
readonly INVALID_API_KEY: "INVALID_API_KEY";
|
|
4
7
|
readonly RELAY_UNAVAILABLE: "RELAY_UNAVAILABLE";
|
|
5
|
-
readonly DELIVERY_FAILED: "DELIVERY_FAILED";
|
|
6
8
|
readonly RECIPIENT_NOT_FOUND: "RECIPIENT_NOT_FOUND";
|
|
7
9
|
readonly RECIPIENT_TIMEOUT: "RECIPIENT_TIMEOUT";
|
|
8
|
-
readonly
|
|
9
|
-
readonly
|
|
10
|
-
readonly
|
|
11
|
-
readonly
|
|
10
|
+
readonly CLIENT_NOT_READY: "CLIENT_NOT_READY";
|
|
11
|
+
readonly DELIVERY_FAILED: "DELIVERY_FAILED";
|
|
12
|
+
readonly QUOTA_EXCEEDED: "QUOTA_EXCEEDED";
|
|
13
|
+
readonly RATE_LIMITED: "RATE_LIMITED";
|
|
12
14
|
};
|
|
13
|
-
export type ErrorCode =
|
|
15
|
+
export type ErrorCode = typeof ErrorCode[keyof typeof ErrorCode];
|
|
14
16
|
export declare class StvorError extends Error {
|
|
15
|
-
code:
|
|
16
|
-
action?: string
|
|
17
|
-
retryable?: boolean
|
|
18
|
-
constructor(code:
|
|
17
|
+
code: string;
|
|
18
|
+
action?: string;
|
|
19
|
+
retryable?: boolean;
|
|
20
|
+
constructor(code: string, message: string, action?: string, retryable?: boolean);
|
|
19
21
|
}
|
|
22
|
+
export declare const Errors: {
|
|
23
|
+
authFailed(): StvorError;
|
|
24
|
+
invalidAppToken(): StvorError;
|
|
25
|
+
relayUnavailable(): StvorError;
|
|
26
|
+
recipientNotFound(userId: string): StvorError;
|
|
27
|
+
messageIntegrityFailed(): StvorError;
|
|
28
|
+
keystoreCorrupted(): StvorError;
|
|
29
|
+
deviceCompromised(): StvorError;
|
|
30
|
+
protocolMismatch(): StvorError;
|
|
31
|
+
recipientTimeout(userId: string, timeoutMs: number): StvorError;
|
|
32
|
+
clientNotReady(): StvorError;
|
|
33
|
+
deliveryFailed(recipientId: string): StvorError;
|
|
34
|
+
quotaExceeded: () => StvorError;
|
|
35
|
+
rateLimited: () => StvorError;
|
|
36
|
+
};
|
package/dist/facade/errors.js
CHANGED
|
@@ -1,21 +1,62 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* STVOR DX Facade - Error Handling
|
|
3
|
+
*/
|
|
4
|
+
export const ErrorCode = {
|
|
5
|
+
AUTH_FAILED: 'AUTH_FAILED',
|
|
2
6
|
INVALID_APP_TOKEN: 'INVALID_APP_TOKEN',
|
|
3
|
-
INVALID_API_KEY: 'INVALID_API_KEY',
|
|
4
7
|
RELAY_UNAVAILABLE: 'RELAY_UNAVAILABLE',
|
|
5
|
-
DELIVERY_FAILED: 'DELIVERY_FAILED',
|
|
6
8
|
RECIPIENT_NOT_FOUND: 'RECIPIENT_NOT_FOUND',
|
|
7
9
|
RECIPIENT_TIMEOUT: 'RECIPIENT_TIMEOUT',
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
CLIENT_NOT_READY: 'CLIENT_NOT_READY',
|
|
11
|
+
DELIVERY_FAILED: 'DELIVERY_FAILED',
|
|
12
|
+
QUOTA_EXCEEDED: 'QUOTA_EXCEEDED',
|
|
13
|
+
RATE_LIMITED: 'RATE_LIMITED',
|
|
12
14
|
};
|
|
13
15
|
export class StvorError extends Error {
|
|
14
16
|
constructor(code, message, action, retryable) {
|
|
15
17
|
super(message);
|
|
18
|
+
this.name = 'StvorError';
|
|
16
19
|
this.code = code;
|
|
17
20
|
this.action = action;
|
|
18
21
|
this.retryable = retryable;
|
|
19
|
-
this.name = 'StvorError';
|
|
20
22
|
}
|
|
21
23
|
}
|
|
24
|
+
export const Errors = {
|
|
25
|
+
authFailed() {
|
|
26
|
+
return new StvorError(ErrorCode.AUTH_FAILED, 'The AppToken is invalid or has been revoked.', 'Check your dashboard and regenerate a new AppToken.', false);
|
|
27
|
+
},
|
|
28
|
+
invalidAppToken() {
|
|
29
|
+
return new StvorError(ErrorCode.INVALID_APP_TOKEN, 'Invalid AppToken format. AppToken must start with "stvor_".', 'Get your AppToken from the developer dashboard.', false);
|
|
30
|
+
},
|
|
31
|
+
relayUnavailable() {
|
|
32
|
+
return new StvorError(ErrorCode.RELAY_UNAVAILABLE, 'Cannot connect to STVOR relay server.', 'Check your internet connection.', true);
|
|
33
|
+
},
|
|
34
|
+
recipientNotFound(userId) {
|
|
35
|
+
return new StvorError(ErrorCode.RECIPIENT_NOT_FOUND, `User "${userId}" not found. They may not have registered with STVOR.`, 'Ask the recipient to initialize STVOR first, or verify the userId is correct.', false);
|
|
36
|
+
},
|
|
37
|
+
messageIntegrityFailed() {
|
|
38
|
+
return new StvorError(ErrorCode.DELIVERY_FAILED, 'Message integrity check failed or decryption failed.', 'Request the message again from the sender.', false);
|
|
39
|
+
},
|
|
40
|
+
keystoreCorrupted() {
|
|
41
|
+
return new StvorError(ErrorCode.DELIVERY_FAILED, 'Local keystore error (not supported in v0.1).', 'Investigate local storage configuration.', false);
|
|
42
|
+
},
|
|
43
|
+
deviceCompromised() {
|
|
44
|
+
return new StvorError(ErrorCode.DELIVERY_FAILED, 'Device compromise detected (placeholder).', 'Investigate and revoke credentials.', false);
|
|
45
|
+
},
|
|
46
|
+
protocolMismatch() {
|
|
47
|
+
return new StvorError(ErrorCode.DELIVERY_FAILED, 'Protocol version mismatch.', 'Update the SDK to the latest version.', false);
|
|
48
|
+
},
|
|
49
|
+
recipientTimeout(userId, timeoutMs) {
|
|
50
|
+
return new StvorError(ErrorCode.RECIPIENT_TIMEOUT, `Timed out waiting for user "${userId}" after ${timeoutMs}ms. ` +
|
|
51
|
+
`The user may not have registered with STVOR yet.`, 'Ensure the recipient has called connect() and is online, or increase the timeout.', true);
|
|
52
|
+
},
|
|
53
|
+
clientNotReady() {
|
|
54
|
+
return new StvorError(ErrorCode.CLIENT_NOT_READY, 'Client is not ready. Call connect() first and await it.', 'Make sure to await app.connect() before sending messages.', false);
|
|
55
|
+
},
|
|
56
|
+
deliveryFailed(recipientId) {
|
|
57
|
+
return new StvorError(ErrorCode.DELIVERY_FAILED, `Failed to deliver message to ${recipientId}.`, 'Check that the recipient exists and try again.', true);
|
|
58
|
+
},
|
|
59
|
+
quotaExceeded: () => new StvorError(ErrorCode.QUOTA_EXCEEDED, 'Message quota exceeded for this AppToken.', 'UPGRADE_PLAN', false),
|
|
60
|
+
rateLimited: () => new StvorError(ErrorCode.RATE_LIMITED, 'Rate limit exceeded. Please try again later.', 'WAIT', true),
|
|
61
|
+
// receive()/timeout APIs are not part of SDK v0.1 facade; use onMessage().
|
|
62
|
+
};
|
package/dist/facade/index.d.ts
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
/**
|
|
2
|
+
* STVOR DX Facade SDK
|
|
3
|
+
* High-level developer experience layer for STVOR E2E encryption
|
|
4
|
+
*
|
|
5
|
+
* Design goals:
|
|
6
|
+
* - Minimal API surface
|
|
7
|
+
* - Zero crypto knowledge required
|
|
8
|
+
* - Secure by default
|
|
9
|
+
* - Opinionated (no configuration)
|
|
10
|
+
*/
|
|
11
|
+
export type { DecryptedMessage, SealedPayload } from './app';
|
|
12
|
+
export type { StvorAppConfig, AppToken, UserId, MessageContent } from './types';
|
|
13
|
+
export type { ErrorCode } from './errors';
|
|
14
|
+
export type { Metrics, SignedMetrics } from './metrics-engine';
|
|
15
|
+
export { StvorError } from './errors';
|
|
16
|
+
export { StvorApp, StvorFacadeClient, Stvor, init, createApp } from './app';
|
|
17
|
+
export { ErrorCode as STVOR_ERRORS } from './errors';
|
|
18
|
+
export { verifyMetricsSignature, MetricsEngine } from './metrics-engine';
|
|
19
|
+
export { CryptoSessionManager } from './crypto-session';
|
|
20
|
+
export type { IdentityKeys, SerializedPublicKeys, IIdentityStore, ISessionStore } from './crypto-session';
|
|
21
|
+
export { LocalStorageIdentityStore, LocalStorageSessionStore } from './local-storage-identity-store';
|
|
22
|
+
export { isReplay, validateMessage, validateMessageWithNonce, getCacheStats, cleanupExpiredNonces, initializeReplayProtection, startAutoCleanup, stopAutoCleanup, } from './replay-manager';
|
|
23
|
+
export type { IReplayCache } from './replay-manager';
|
|
24
|
+
export { RedisReplayCache } from './redis-replay-cache';
|
|
25
|
+
export type { RedisClient, RedisReplayCacheConfig } from './redis-replay-cache';
|
|
26
|
+
export { generateFingerprint, storeFingerprint, verifyFingerprint, isFingerprintTrusted, getFingerprint, revokeTrust, trustNewFingerprint, listTrustedFingerprints, getFingerprintInfo, initializeTofu, } from './tofu-manager';
|
|
27
|
+
export type { ITofuStore } from './tofu-manager';
|
package/dist/facade/index.js
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* STVOR DX Facade SDK
|
|
3
|
+
* High-level developer experience layer for STVOR E2E encryption
|
|
4
|
+
*
|
|
5
|
+
* Design goals:
|
|
6
|
+
* - Minimal API surface
|
|
7
|
+
* - Zero crypto knowledge required
|
|
8
|
+
* - Secure by default
|
|
9
|
+
* - Opinionated (no configuration)
|
|
10
|
+
*/
|
|
4
11
|
export { StvorError } from './errors.js';
|
|
12
|
+
// Re-export classes and functions
|
|
5
13
|
export { StvorApp, StvorFacadeClient, Stvor, init, createApp } from './app.js';
|
|
14
|
+
export { ErrorCode as STVOR_ERRORS } from './errors.js';
|
|
15
|
+
// Re-export metrics verification for Dashboard
|
|
16
|
+
export { verifyMetricsSignature, MetricsEngine } from './metrics-engine.js';
|
|
17
|
+
// Re-export crypto session management
|
|
18
|
+
export { CryptoSessionManager } from './crypto-session.js';
|
|
19
|
+
export { LocalStorageIdentityStore, LocalStorageSessionStore } from './local-storage-identity-store.js';
|
|
20
|
+
// Re-export replay protection
|
|
21
|
+
export { isReplay, validateMessage, validateMessageWithNonce, getCacheStats, cleanupExpiredNonces, initializeReplayProtection, startAutoCleanup, stopAutoCleanup, } from './replay-manager.js';
|
|
22
|
+
// Re-export Redis replay cache for production
|
|
23
|
+
export { RedisReplayCache } from './redis-replay-cache.js';
|
|
24
|
+
// Re-export TOFU management
|
|
25
|
+
export { generateFingerprint, storeFingerprint, verifyFingerprint, isFingerprintTrusted, getFingerprint, revokeTrust, trustNewFingerprint, listTrustedFingerprints, getFingerprintInfo, initializeTofu, } from './tofu-manager.js';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Auto-generated CommonJS wrapper for facade/local-storage-identity-store.js
|
|
4
|
+
// This allows `require('@stvor/sdk')` to work alongside ESM `import`.
|
|
5
|
+
|
|
6
|
+
const mod = require('module');
|
|
7
|
+
const url = require('url');
|
|
8
|
+
|
|
9
|
+
// Use dynamic import to load the ESM module
|
|
10
|
+
let _cached;
|
|
11
|
+
async function _load() {
|
|
12
|
+
if (!_cached) {
|
|
13
|
+
_cached = await import(url.pathToFileURL(__filename.replace(/\.cjs$/, '.js')).href);
|
|
14
|
+
}
|
|
15
|
+
return _cached;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// For simple CJS usage, expose a promise-based loader
|
|
19
|
+
module.exports = new Proxy({ load: _load }, {
|
|
20
|
+
get(target, prop) {
|
|
21
|
+
if (prop === '__esModule') return true;
|
|
22
|
+
if (prop === 'then') return undefined; // prevent treating as thenable
|
|
23
|
+
if (prop === 'load') return _load;
|
|
24
|
+
if (prop === 'default') {
|
|
25
|
+
return _load().then(m => m.default);
|
|
26
|
+
}
|
|
27
|
+
return _load().then(m => m[prop]);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocalStorage-based Identity Store for browser environments
|
|
3
|
+
* Implements IIdentityStore for persistent identity key storage
|
|
4
|
+
*/
|
|
5
|
+
import { IIdentityStore } from './crypto-session.js';
|
|
6
|
+
/**
|
|
7
|
+
* Browser-based identity storage using localStorage
|
|
8
|
+
* CRITICAL: Keys are stored in base64url — in production, use encrypted storage
|
|
9
|
+
*/
|
|
10
|
+
export declare class LocalStorageIdentityStore implements IIdentityStore {
|
|
11
|
+
private storageKey;
|
|
12
|
+
constructor(userId: string, storageKeyPrefix?: string);
|
|
13
|
+
saveIdentityKeys(userId: string, keys: {
|
|
14
|
+
identityKeyPair: {
|
|
15
|
+
publicKey: string;
|
|
16
|
+
privateKey: string;
|
|
17
|
+
};
|
|
18
|
+
signedPreKeyPair: {
|
|
19
|
+
publicKey: string;
|
|
20
|
+
privateKey: string;
|
|
21
|
+
};
|
|
22
|
+
signedPreKeySignature: string;
|
|
23
|
+
}): Promise<void>;
|
|
24
|
+
loadIdentityKeys(userId: string): Promise<{
|
|
25
|
+
identityKeyPair: {
|
|
26
|
+
publicKey: string;
|
|
27
|
+
privateKey: string;
|
|
28
|
+
};
|
|
29
|
+
signedPreKeyPair: {
|
|
30
|
+
publicKey: string;
|
|
31
|
+
privateKey: string;
|
|
32
|
+
};
|
|
33
|
+
signedPreKeySignature: string;
|
|
34
|
+
} | null>;
|
|
35
|
+
/** Delete stored keys (for logout / account reset) */
|
|
36
|
+
deleteIdentityKeys(userId: string): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Session storage implementation for browser environments
|
|
40
|
+
*/
|
|
41
|
+
export declare class LocalStorageSessionStore {
|
|
42
|
+
private storageKey;
|
|
43
|
+
constructor(userId: string, storageKeyPrefix?: string);
|
|
44
|
+
saveSession(userId: string, peerId: string, _session: unknown): Promise<void>;
|
|
45
|
+
loadSession(userId: string, peerId: string): Promise<unknown | null>;
|
|
46
|
+
deleteSession(userId: string, peerId: string): Promise<void>;
|
|
47
|
+
listSessions(userId: string): Promise<string[]>;
|
|
48
|
+
private getAllSessions;
|
|
49
|
+
}
|
|
50
|
+
export default LocalStorageIdentityStore;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LocalStorage-based Identity Store for browser environments
|
|
3
|
+
* Implements IIdentityStore for persistent identity key storage
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Browser-based identity storage using localStorage
|
|
7
|
+
* CRITICAL: Keys are stored in base64url — in production, use encrypted storage
|
|
8
|
+
*/
|
|
9
|
+
export class LocalStorageIdentityStore {
|
|
10
|
+
constructor(userId, storageKeyPrefix = 'stvor_identity_') {
|
|
11
|
+
this.storageKey = `${storageKeyPrefix}${userId}`;
|
|
12
|
+
}
|
|
13
|
+
async saveIdentityKeys(userId, keys) {
|
|
14
|
+
try {
|
|
15
|
+
const data = {
|
|
16
|
+
identityKeyPair: keys.identityKeyPair,
|
|
17
|
+
signedPreKeyPair: keys.signedPreKeyPair,
|
|
18
|
+
signedPreKeySignature: keys.signedPreKeySignature,
|
|
19
|
+
};
|
|
20
|
+
localStorage.setItem(this.storageKey, JSON.stringify(data));
|
|
21
|
+
console.log(`[LocalStorageIdentityStore] Keys saved for user: ${userId}`);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error('[LocalStorageIdentityStore] Failed to save keys:', error);
|
|
25
|
+
throw new Error('Failed to save identity keys to localStorage');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async loadIdentityKeys(userId) {
|
|
29
|
+
try {
|
|
30
|
+
const data = localStorage.getItem(this.storageKey);
|
|
31
|
+
if (!data) {
|
|
32
|
+
console.log(`[LocalStorageIdentityStore] No keys found for user: ${userId}`);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const parsed = JSON.parse(data);
|
|
36
|
+
console.log(`[LocalStorageIdentityStore] Keys loaded for user: ${userId}`);
|
|
37
|
+
return {
|
|
38
|
+
identityKeyPair: parsed.identityKeyPair,
|
|
39
|
+
signedPreKeyPair: parsed.signedPreKeyPair,
|
|
40
|
+
signedPreKeySignature: parsed.signedPreKeySignature,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
console.error('[LocalStorageIdentityStore] Failed to load keys:', error);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Delete stored keys (for logout / account reset) */
|
|
49
|
+
async deleteIdentityKeys(userId) {
|
|
50
|
+
localStorage.removeItem(this.storageKey);
|
|
51
|
+
console.log(`[LocalStorageIdentityStore] Keys deleted for user: ${userId}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Session storage implementation for browser environments
|
|
56
|
+
*/
|
|
57
|
+
export class LocalStorageSessionStore {
|
|
58
|
+
constructor(userId, storageKeyPrefix = 'stvor_session_') {
|
|
59
|
+
this.storageKey = `${storageKeyPrefix}${userId}`;
|
|
60
|
+
}
|
|
61
|
+
async saveSession(userId, peerId, _session) {
|
|
62
|
+
try {
|
|
63
|
+
const allSessions = this.getAllSessions();
|
|
64
|
+
allSessions[peerId] = { savedAt: Date.now() };
|
|
65
|
+
localStorage.setItem(this.storageKey, JSON.stringify(allSessions));
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.error('[LocalStorageSessionStore] Failed to save session:', error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async loadSession(userId, peerId) {
|
|
72
|
+
try {
|
|
73
|
+
const allSessions = this.getAllSessions();
|
|
74
|
+
return allSessions[peerId] || null;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.error('[LocalStorageSessionStore] Failed to load session:', error);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async deleteSession(userId, peerId) {
|
|
82
|
+
try {
|
|
83
|
+
const allSessions = this.getAllSessions();
|
|
84
|
+
delete allSessions[peerId];
|
|
85
|
+
localStorage.setItem(this.storageKey, JSON.stringify(allSessions));
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
console.error('[LocalStorageSessionStore] Failed to delete session:', error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async listSessions(userId) {
|
|
92
|
+
const allSessions = this.getAllSessions();
|
|
93
|
+
return Object.keys(allSessions);
|
|
94
|
+
}
|
|
95
|
+
getAllSessions() {
|
|
96
|
+
const data = localStorage.getItem(this.storageKey);
|
|
97
|
+
return data ? JSON.parse(data) : {};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export default LocalStorageIdentityStore;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Auto-generated CommonJS wrapper for facade/metrics-attestation.js
|
|
4
|
+
// This allows `require('@stvor/sdk')` to work alongside ESM `import`.
|
|
5
|
+
|
|
6
|
+
const mod = require('module');
|
|
7
|
+
const url = require('url');
|
|
8
|
+
|
|
9
|
+
// Use dynamic import to load the ESM module
|
|
10
|
+
let _cached;
|
|
11
|
+
async function _load() {
|
|
12
|
+
if (!_cached) {
|
|
13
|
+
_cached = await import(url.pathToFileURL(__filename.replace(/\.cjs$/, '.js')).href);
|
|
14
|
+
}
|
|
15
|
+
return _cached;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// For simple CJS usage, expose a promise-based loader
|
|
19
|
+
module.exports = new Proxy({ load: _load }, {
|
|
20
|
+
get(target, prop) {
|
|
21
|
+
if (prop === '__esModule') return true;
|
|
22
|
+
if (prop === 'then') return undefined; // prevent treating as thenable
|
|
23
|
+
if (prop === 'load') return _load;
|
|
24
|
+
if (prop === 'default') {
|
|
25
|
+
return _load().then(m => m.default);
|
|
26
|
+
}
|
|
27
|
+
return _load().then(m => m[prop]);
|
|
28
|
+
}
|
|
29
|
+
});
|