@msssystems/mss-link-sdk 0.1.2 → 0.1.3
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/client/decryption-errors.d.ts +7 -0
- package/dist/client/decryption-errors.js +36 -0
- package/dist/client/index.d.ts +5 -0
- package/dist/client/index.js +5 -0
- package/dist/client/local-crypto-health.contracts.d.ts +9 -0
- package/dist/client/local-crypto-health.contracts.js +1 -0
- package/dist/client/local-crypto-health.d.ts +2 -0
- package/dist/client/local-crypto-health.js +29 -0
- package/dist/client/message-decryption-state.d.ts +2 -0
- package/dist/client/message-decryption-state.js +81 -0
- package/dist/client/message-decryption.contracts.d.ts +24 -0
- package/dist/client/message-decryption.contracts.js +1 -0
- package/dist/client/mss-link-browser-client.d.ts +2 -0
- package/dist/client/mss-link-browser-client.js +34 -8
- package/package.json +1 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type LocalCryptoErrorCode = 'corrupt_session' | 'missing_local_session' | 'unsupported_envelope' | 'local_crypto_unavailable' | 'unknown';
|
|
2
|
+
export declare class LocalCryptoError extends Error {
|
|
3
|
+
readonly code: LocalCryptoErrorCode;
|
|
4
|
+
readonly cause?: unknown;
|
|
5
|
+
constructor(code: LocalCryptoErrorCode, message: string, cause?: unknown);
|
|
6
|
+
}
|
|
7
|
+
export declare function normalizeLocalCryptoError(error: unknown): LocalCryptoError;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export class LocalCryptoError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
cause;
|
|
4
|
+
constructor(code, message, cause) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = 'LocalCryptoError';
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.cause = cause;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function normalizeLocalCryptoError(error) {
|
|
12
|
+
if (error instanceof LocalCryptoError) {
|
|
13
|
+
return error;
|
|
14
|
+
}
|
|
15
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
16
|
+
const normalizedMessage = message.toLowerCase();
|
|
17
|
+
if (normalizedMessage.includes('corrupt') ||
|
|
18
|
+
normalizedMessage.includes('ratchet')) {
|
|
19
|
+
return new LocalCryptoError('corrupt_session', message, error);
|
|
20
|
+
}
|
|
21
|
+
if (normalizedMessage.includes('missing') ||
|
|
22
|
+
normalizedMessage.includes('not found') ||
|
|
23
|
+
normalizedMessage.includes('session')) {
|
|
24
|
+
return new LocalCryptoError('missing_local_session', message, error);
|
|
25
|
+
}
|
|
26
|
+
if (normalizedMessage.includes('unsupported') ||
|
|
27
|
+
normalizedMessage.includes('envelope')) {
|
|
28
|
+
return new LocalCryptoError('unsupported_envelope', message, error);
|
|
29
|
+
}
|
|
30
|
+
if (normalizedMessage.includes('indexeddb') ||
|
|
31
|
+
normalizedMessage.includes('wasm') ||
|
|
32
|
+
normalizedMessage.includes('browser')) {
|
|
33
|
+
return new LocalCryptoError('local_crypto_unavailable', message, error);
|
|
34
|
+
}
|
|
35
|
+
return new LocalCryptoError('unknown', message, error);
|
|
36
|
+
}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export * from './api-base-url';
|
|
2
2
|
export * from './client-options';
|
|
3
|
+
export * from './decryption-errors';
|
|
4
|
+
export * from './local-crypto-health';
|
|
5
|
+
export * from './local-crypto-health.contracts';
|
|
3
6
|
export * from './message.contracts';
|
|
7
|
+
export * from './message-decryption.contracts';
|
|
8
|
+
export * from './message-decryption-state';
|
|
4
9
|
export * from './message.mapper';
|
|
5
10
|
export * from './mss-link-browser-client';
|
package/dist/client/index.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export * from './api-base-url';
|
|
2
2
|
export * from './client-options';
|
|
3
|
+
export * from './decryption-errors';
|
|
4
|
+
export * from './local-crypto-health';
|
|
5
|
+
export * from './local-crypto-health.contracts';
|
|
3
6
|
export * from './message.contracts';
|
|
7
|
+
export * from './message-decryption.contracts';
|
|
8
|
+
export * from './message-decryption-state';
|
|
4
9
|
export * from './message.mapper';
|
|
5
10
|
export * from './mss-link-browser-client';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LocalCryptoErrorCode } from './decryption-errors';
|
|
2
|
+
export type LocalCryptoHealthStatus = 'ready' | 'recovery_required' | 'corrupt' | 'unavailable';
|
|
3
|
+
export type LocalCryptoHealthErrorCode = LocalCryptoErrorCode;
|
|
4
|
+
export interface LocalCryptoHealth {
|
|
5
|
+
ready: boolean;
|
|
6
|
+
status: LocalCryptoHealthStatus;
|
|
7
|
+
errorCode?: LocalCryptoHealthErrorCode;
|
|
8
|
+
checkedAt: string;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { normalizeLocalCryptoError } from './decryption-errors';
|
|
2
|
+
export async function checkLocalCryptoHealth(checkReady) {
|
|
3
|
+
try {
|
|
4
|
+
await checkReady();
|
|
5
|
+
return {
|
|
6
|
+
ready: true,
|
|
7
|
+
status: 'ready',
|
|
8
|
+
checkedAt: new Date().toISOString(),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
const normalizedError = normalizeLocalCryptoError(error);
|
|
13
|
+
return {
|
|
14
|
+
ready: false,
|
|
15
|
+
status: getLocalCryptoHealthStatus(normalizedError.code),
|
|
16
|
+
errorCode: normalizedError.code,
|
|
17
|
+
checkedAt: new Date().toISOString(),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function getLocalCryptoHealthStatus(errorCode) {
|
|
22
|
+
if (errorCode === 'corrupt_session') {
|
|
23
|
+
return 'corrupt';
|
|
24
|
+
}
|
|
25
|
+
if (errorCode === 'local_crypto_unavailable') {
|
|
26
|
+
return 'unavailable';
|
|
27
|
+
}
|
|
28
|
+
return 'recovery_required';
|
|
29
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import type { CreateMessageDecryptionStateIndexInput, EncryptedMessageRef, MessageDecryptStateIndex } from './message-decryption.contracts';
|
|
2
|
+
export declare function createMessageDecryptionStateIndex<TMessage extends EncryptedMessageRef>({ messages, decryptedMessages, isDecrypting, decryptError, }: CreateMessageDecryptionStateIndexInput<TMessage>): MessageDecryptStateIndex;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { normalizeLocalCryptoError } from './decryption-errors';
|
|
2
|
+
export function createMessageDecryptionStateIndex({ messages, decryptedMessages, isDecrypting, decryptError, }) {
|
|
3
|
+
const decryptedById = createDecryptedMessageIndex(decryptedMessages);
|
|
4
|
+
const index = new Map();
|
|
5
|
+
for (const message of messages) {
|
|
6
|
+
const decrypted = findDecryptedMessage(decryptedById, message);
|
|
7
|
+
const state = createMessageDecryptState({
|
|
8
|
+
message,
|
|
9
|
+
decrypted,
|
|
10
|
+
isDecrypting,
|
|
11
|
+
decryptError,
|
|
12
|
+
});
|
|
13
|
+
index.set(message.id, state);
|
|
14
|
+
if (message.clientMessageId) {
|
|
15
|
+
index.set(message.clientMessageId, state);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return index;
|
|
19
|
+
}
|
|
20
|
+
function createMessageDecryptState({ message, decrypted, isDecrypting, decryptError, }) {
|
|
21
|
+
const baseState = createBaseMessageDecryptState(message);
|
|
22
|
+
if (decrypted?.text) {
|
|
23
|
+
return {
|
|
24
|
+
...baseState,
|
|
25
|
+
status: 'decrypted',
|
|
26
|
+
text: decrypted.text,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
if (isDecrypting) {
|
|
30
|
+
return {
|
|
31
|
+
...baseState,
|
|
32
|
+
status: 'pending',
|
|
33
|
+
reason: 'local_sync_in_progress',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (decryptError) {
|
|
37
|
+
return {
|
|
38
|
+
...baseState,
|
|
39
|
+
status: 'failed',
|
|
40
|
+
reason: 'local_crypto_error',
|
|
41
|
+
errorCode: getDecryptErrorCode(decryptError),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
...baseState,
|
|
46
|
+
status: 'recovery_required',
|
|
47
|
+
reason: 'local_message_missing',
|
|
48
|
+
errorCode: 'missing_local_session',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function createBaseMessageDecryptState(message) {
|
|
52
|
+
if (message.clientMessageId) {
|
|
53
|
+
return {
|
|
54
|
+
messageId: message.id,
|
|
55
|
+
clientMessageId: message.clientMessageId,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
messageId: message.id,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function createDecryptedMessageIndex(messages) {
|
|
63
|
+
const index = new Map();
|
|
64
|
+
for (const message of messages) {
|
|
65
|
+
index.set(message.id, message);
|
|
66
|
+
}
|
|
67
|
+
return index;
|
|
68
|
+
}
|
|
69
|
+
function findDecryptedMessage(index, message) {
|
|
70
|
+
const byMessageId = index.get(message.id);
|
|
71
|
+
if (byMessageId) {
|
|
72
|
+
return byMessageId;
|
|
73
|
+
}
|
|
74
|
+
if (message.clientMessageId) {
|
|
75
|
+
return index.get(message.clientMessageId);
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
function getDecryptErrorCode(error) {
|
|
80
|
+
return normalizeLocalCryptoError(error).code;
|
|
81
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { LocalCryptoErrorCode } from './decryption-errors';
|
|
2
|
+
import type { DecryptedMessage } from './message.contracts';
|
|
3
|
+
export type MessageDecryptStatus = 'decrypted' | 'pending' | 'failed' | 'recovery_required';
|
|
4
|
+
export type MessageDecryptReason = 'local_sync_in_progress' | 'local_message_missing' | 'local_crypto_error';
|
|
5
|
+
export type MessageDecryptErrorCode = LocalCryptoErrorCode;
|
|
6
|
+
export interface EncryptedMessageRef {
|
|
7
|
+
id: string;
|
|
8
|
+
clientMessageId?: string | null;
|
|
9
|
+
}
|
|
10
|
+
export interface MessageDecryptState {
|
|
11
|
+
messageId: string;
|
|
12
|
+
clientMessageId?: string | null;
|
|
13
|
+
status: MessageDecryptStatus;
|
|
14
|
+
reason?: MessageDecryptReason;
|
|
15
|
+
errorCode?: MessageDecryptErrorCode;
|
|
16
|
+
text?: string;
|
|
17
|
+
}
|
|
18
|
+
export type MessageDecryptStateIndex = Map<string, MessageDecryptState>;
|
|
19
|
+
export interface CreateMessageDecryptionStateIndexInput<TMessage extends EncryptedMessageRef = EncryptedMessageRef> {
|
|
20
|
+
messages: TMessage[];
|
|
21
|
+
decryptedMessages: DecryptedMessage[];
|
|
22
|
+
isDecrypting: boolean;
|
|
23
|
+
decryptError: unknown;
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MssLinkBrowserClientOptions } from './client-options';
|
|
2
|
+
import type { LocalCryptoHealth } from './local-crypto-health.contracts';
|
|
2
3
|
import type { DecryptedMessage } from './message.contracts';
|
|
3
4
|
export declare class MssLinkBrowserClient {
|
|
4
5
|
private readonly userId;
|
|
@@ -16,6 +17,7 @@ export declare class MssLinkBrowserClient {
|
|
|
16
17
|
constructor(options: MssLinkBrowserClientOptions);
|
|
17
18
|
setAccessToken(accessToken: string | null): void;
|
|
18
19
|
ensureReady(): Promise<void>;
|
|
20
|
+
getLocalCryptoHealth(): Promise<LocalCryptoHealth>;
|
|
19
21
|
sendMessage(params: {
|
|
20
22
|
chatId: string;
|
|
21
23
|
targetUserId: string;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { normalizeProductApiBaseUrl } from './api-base-url';
|
|
2
|
+
import { normalizeLocalCryptoError } from './decryption-errors';
|
|
3
|
+
import { checkLocalCryptoHealth } from './local-crypto-health';
|
|
2
4
|
import { mapLocalSdkMessage } from './message.mapper';
|
|
3
5
|
import { WasmCallQueue } from './wasm-call-queue';
|
|
4
6
|
const DEFAULT_DEVICE_ID = 1;
|
|
@@ -43,20 +45,44 @@ export class MssLinkBrowserClient {
|
|
|
43
45
|
this.wasmClient?.setAccessToken(accessToken);
|
|
44
46
|
}
|
|
45
47
|
async ensureReady() {
|
|
46
|
-
|
|
47
|
-
await
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
try {
|
|
49
|
+
await this.runWithClient(async (client) => {
|
|
50
|
+
await client.ensureDeviceRegistered(this.registrationId, this.signedPrekeyId, this.initialOneTimePrekeyCount);
|
|
51
|
+
await client.replenishOneTimePreKeys(this.minOneTimePrekeys, this.replenishOneTimePrekeys);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
throw normalizeLocalCryptoError(error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async getLocalCryptoHealth() {
|
|
59
|
+
return checkLocalCryptoHealth(() => this.ensureReady());
|
|
50
60
|
}
|
|
51
61
|
async sendMessage(params) {
|
|
52
|
-
|
|
62
|
+
try {
|
|
63
|
+
await this.ensureReady();
|
|
64
|
+
return this.runWithClient((client) => client.sendMessage(params.chatId, params.targetUserId, params.text, params.kind ?? 'TEXT'));
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
throw normalizeLocalCryptoError(error);
|
|
68
|
+
}
|
|
53
69
|
}
|
|
54
70
|
async syncMessages() {
|
|
55
|
-
|
|
71
|
+
try {
|
|
72
|
+
await this.runWithClient((client) => client.syncMessages());
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
throw normalizeLocalCryptoError(error);
|
|
76
|
+
}
|
|
56
77
|
}
|
|
57
78
|
async getLocalMessages(chatId) {
|
|
58
|
-
|
|
59
|
-
|
|
79
|
+
try {
|
|
80
|
+
const rawMessages = await this.runWithClient((client) => client.getLocalMessages(chatId));
|
|
81
|
+
return rawMessages.map((message) => mapLocalSdkMessage(message));
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
throw normalizeLocalCryptoError(error);
|
|
85
|
+
}
|
|
60
86
|
}
|
|
61
87
|
reset() {
|
|
62
88
|
this.wasmClient?.free();
|