@msssystems/mss-link-sdk 0.1.3 → 0.1.6
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/index.d.ts +3 -0
- package/dist/client/index.js +3 -0
- package/dist/client/media-crypto.contracts.d.ts +28 -0
- package/dist/client/media-crypto.contracts.js +1 -0
- package/dist/client/media-message-metadata.d.ts +4 -0
- package/dist/client/media-message-metadata.js +93 -0
- package/dist/client/media-message.contracts.d.ts +41 -0
- package/dist/client/media-message.contracts.js +1 -0
- package/dist/client/mss-link-browser-client.d.ts +5 -0
- package/dist/client/mss-link-browser-client.js +84 -1
- package/dist/client/wasm-client.types.d.ts +8 -2
- package/package.json +3 -3
package/dist/client/index.d.ts
CHANGED
|
@@ -3,6 +3,9 @@ export * from './client-options';
|
|
|
3
3
|
export * from './decryption-errors';
|
|
4
4
|
export * from './local-crypto-health';
|
|
5
5
|
export * from './local-crypto-health.contracts';
|
|
6
|
+
export * from './media-crypto.contracts';
|
|
7
|
+
export * from './media-message.contracts';
|
|
8
|
+
export * from './media-message-metadata';
|
|
6
9
|
export * from './message.contracts';
|
|
7
10
|
export * from './message-decryption.contracts';
|
|
8
11
|
export * from './message-decryption-state';
|
package/dist/client/index.js
CHANGED
|
@@ -3,6 +3,9 @@ export * from './client-options';
|
|
|
3
3
|
export * from './decryption-errors';
|
|
4
4
|
export * from './local-crypto-health';
|
|
5
5
|
export * from './local-crypto-health.contracts';
|
|
6
|
+
export * from './media-crypto.contracts';
|
|
7
|
+
export * from './media-message.contracts';
|
|
8
|
+
export * from './media-message-metadata';
|
|
6
9
|
export * from './message.contracts';
|
|
7
10
|
export * from './message-decryption.contracts';
|
|
8
11
|
export * from './message-decryption-state';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { MediaMessageAttachmentEncryptionInput, MediaMessageAttachmentInput, MediaMessageAttachmentMetadata } from './media-message.contracts';
|
|
2
|
+
export interface EncryptMediaBlobInput {
|
|
3
|
+
assetId: string;
|
|
4
|
+
blob: Blob;
|
|
5
|
+
mimeType: string;
|
|
6
|
+
size: string | number;
|
|
7
|
+
originalName?: string;
|
|
8
|
+
uploadedAt?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface EncryptedMediaBlobResult {
|
|
11
|
+
encryptedBlob: Blob;
|
|
12
|
+
attachment: MediaMessageAttachmentInput;
|
|
13
|
+
encryption: MediaMessageAttachmentEncryptionInput;
|
|
14
|
+
}
|
|
15
|
+
export interface DecryptMediaBlobInput {
|
|
16
|
+
encryptedBlob: Blob;
|
|
17
|
+
metadata: MediaMessageAttachmentMetadata;
|
|
18
|
+
}
|
|
19
|
+
export interface DecryptedMediaBlobResult {
|
|
20
|
+
blob: Blob;
|
|
21
|
+
fileName?: string;
|
|
22
|
+
mimeType: string;
|
|
23
|
+
}
|
|
24
|
+
export interface WasmEncryptedMediaBytes {
|
|
25
|
+
ciphertext: Uint8Array;
|
|
26
|
+
key: string;
|
|
27
|
+
nonce: string;
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MediaMessageAttachmentInput, MediaMessageMetadataPayload } from './media-message.contracts';
|
|
2
|
+
export declare function buildMediaMessageMetadataPayload(attachments: MediaMessageAttachmentInput[], caption?: string): MediaMessageMetadataPayload;
|
|
3
|
+
export declare function serializeMediaMessageMetadata(payload: MediaMessageMetadataPayload): string;
|
|
4
|
+
export declare function extractMediaAssetIds(attachments: MediaMessageAttachmentInput[]): string[];
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export function buildMediaMessageMetadataPayload(attachments, caption) {
|
|
2
|
+
if (attachments.length === 0) {
|
|
3
|
+
throw new Error('Media message must contain at least one attachment.');
|
|
4
|
+
}
|
|
5
|
+
const payload = {
|
|
6
|
+
schema: 'mss.link.media-message.v1',
|
|
7
|
+
attachments: attachments.map(normalizeAttachment),
|
|
8
|
+
};
|
|
9
|
+
if (caption && caption.trim().length > 0) {
|
|
10
|
+
payload.caption = caption.trim();
|
|
11
|
+
}
|
|
12
|
+
return payload;
|
|
13
|
+
}
|
|
14
|
+
export function serializeMediaMessageMetadata(payload) {
|
|
15
|
+
return JSON.stringify({
|
|
16
|
+
schema: payload.schema,
|
|
17
|
+
attachments: payload.attachments.map((attachment) => ({
|
|
18
|
+
assetId: attachment.assetId,
|
|
19
|
+
mimeType: attachment.mimeType,
|
|
20
|
+
size: attachment.size,
|
|
21
|
+
...(attachment.originalName
|
|
22
|
+
? { originalName: attachment.originalName }
|
|
23
|
+
: {}),
|
|
24
|
+
...(attachment.uploadedAt ? { uploadedAt: attachment.uploadedAt } : {}),
|
|
25
|
+
...(attachment.encryption
|
|
26
|
+
? { encryption: serializeAttachmentEncryption(attachment.encryption) }
|
|
27
|
+
: {}),
|
|
28
|
+
})),
|
|
29
|
+
...(payload.caption ? { caption: payload.caption } : {}),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export function extractMediaAssetIds(attachments) {
|
|
33
|
+
return attachments.map((attachment) => attachment.assetId);
|
|
34
|
+
}
|
|
35
|
+
function normalizeAttachment(attachment) {
|
|
36
|
+
const assetId = attachment.assetId.trim();
|
|
37
|
+
const mimeType = attachment.mimeType.trim();
|
|
38
|
+
const size = String(attachment.size);
|
|
39
|
+
if (!assetId) {
|
|
40
|
+
throw new Error('Media attachment assetId is required.');
|
|
41
|
+
}
|
|
42
|
+
if (!mimeType) {
|
|
43
|
+
throw new Error('Media attachment mimeType is required.');
|
|
44
|
+
}
|
|
45
|
+
if (!size || size === 'NaN') {
|
|
46
|
+
throw new Error('Media attachment size is required.');
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
assetId,
|
|
50
|
+
mimeType,
|
|
51
|
+
size,
|
|
52
|
+
...(attachment.originalName?.trim()
|
|
53
|
+
? { originalName: attachment.originalName.trim() }
|
|
54
|
+
: {}),
|
|
55
|
+
...(attachment.uploadedAt?.trim()
|
|
56
|
+
? { uploadedAt: attachment.uploadedAt.trim() }
|
|
57
|
+
: {}),
|
|
58
|
+
...(attachment.encryption
|
|
59
|
+
? { encryption: normalizeAttachmentEncryption(attachment.encryption) }
|
|
60
|
+
: {}),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function normalizeAttachmentEncryption(encryption) {
|
|
64
|
+
const keyId = encryption.keyId?.trim();
|
|
65
|
+
const nonce = encryption.nonce?.trim();
|
|
66
|
+
const wrappedKey = encryption.wrappedKey?.trim();
|
|
67
|
+
const algorithm = encryption.algorithm ?? 'AES-256-GCM';
|
|
68
|
+
if (algorithm !== 'AES-256-GCM') {
|
|
69
|
+
throw new Error('Unsupported media encryption algorithm.');
|
|
70
|
+
}
|
|
71
|
+
if (!nonce) {
|
|
72
|
+
throw new Error('Media encryption nonce is required.');
|
|
73
|
+
}
|
|
74
|
+
if (!wrappedKey) {
|
|
75
|
+
throw new Error('Media encryption wrappedKey is required.');
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
schema: 'mss.link.media-encryption.v1',
|
|
79
|
+
algorithm,
|
|
80
|
+
...(keyId ? { keyId } : {}),
|
|
81
|
+
nonce,
|
|
82
|
+
wrappedKey,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function serializeAttachmentEncryption(encryption) {
|
|
86
|
+
return {
|
|
87
|
+
schema: 'mss.link.media-encryption.v1',
|
|
88
|
+
algorithm: encryption.algorithm,
|
|
89
|
+
...(encryption.keyId ? { keyId: encryption.keyId } : {}),
|
|
90
|
+
nonce: encryption.nonce,
|
|
91
|
+
wrappedKey: encryption.wrappedKey,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface MediaMessageAttachmentInput {
|
|
2
|
+
assetId: string;
|
|
3
|
+
mimeType: string;
|
|
4
|
+
size: string | number;
|
|
5
|
+
originalName?: string;
|
|
6
|
+
uploadedAt?: string;
|
|
7
|
+
encryption?: MediaMessageAttachmentEncryptionInput;
|
|
8
|
+
}
|
|
9
|
+
export interface MediaMessageAttachmentEncryptionInput {
|
|
10
|
+
schema?: 'mss.link.media-encryption.v1';
|
|
11
|
+
keyId?: string;
|
|
12
|
+
nonce?: string;
|
|
13
|
+
wrappedKey?: string;
|
|
14
|
+
algorithm?: 'AES-256-GCM';
|
|
15
|
+
}
|
|
16
|
+
export interface SendMediaMessageParams {
|
|
17
|
+
chatId: string;
|
|
18
|
+
targetUserId: string;
|
|
19
|
+
attachments: MediaMessageAttachmentInput[];
|
|
20
|
+
caption?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface MediaMessageAttachmentMetadata {
|
|
23
|
+
assetId: string;
|
|
24
|
+
mimeType: string;
|
|
25
|
+
size: string;
|
|
26
|
+
originalName?: string;
|
|
27
|
+
uploadedAt?: string;
|
|
28
|
+
encryption?: MediaMessageAttachmentEncryptionMetadata;
|
|
29
|
+
}
|
|
30
|
+
export interface MediaMessageAttachmentEncryptionMetadata {
|
|
31
|
+
schema: 'mss.link.media-encryption.v1';
|
|
32
|
+
algorithm: 'AES-256-GCM';
|
|
33
|
+
keyId?: string;
|
|
34
|
+
nonce: string;
|
|
35
|
+
wrappedKey: string;
|
|
36
|
+
}
|
|
37
|
+
export interface MediaMessageMetadataPayload {
|
|
38
|
+
schema: 'mss.link.media-message.v1';
|
|
39
|
+
attachments: MediaMessageAttachmentMetadata[];
|
|
40
|
+
caption?: string;
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { MssLinkBrowserClientOptions } from './client-options';
|
|
2
2
|
import type { LocalCryptoHealth } from './local-crypto-health.contracts';
|
|
3
|
+
import type { DecryptMediaBlobInput, DecryptedMediaBlobResult, EncryptedMediaBlobResult, EncryptMediaBlobInput } from './media-crypto.contracts';
|
|
4
|
+
import type { SendMediaMessageParams } from './media-message.contracts';
|
|
3
5
|
import type { DecryptedMessage } from './message.contracts';
|
|
4
6
|
export declare class MssLinkBrowserClient {
|
|
5
7
|
private readonly userId;
|
|
@@ -24,6 +26,9 @@ export declare class MssLinkBrowserClient {
|
|
|
24
26
|
text: string;
|
|
25
27
|
kind?: string;
|
|
26
28
|
}): Promise<string>;
|
|
29
|
+
sendMediaMessage(params: SendMediaMessageParams): Promise<string>;
|
|
30
|
+
encryptMediaBlob(input: EncryptMediaBlobInput): Promise<EncryptedMediaBlobResult>;
|
|
31
|
+
decryptMediaBlob(input: DecryptMediaBlobInput): Promise<DecryptedMediaBlobResult>;
|
|
27
32
|
syncMessages(): Promise<void>;
|
|
28
33
|
getLocalMessages(chatId: string): Promise<DecryptedMessage[]>;
|
|
29
34
|
reset(): void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { normalizeProductApiBaseUrl } from './api-base-url';
|
|
2
2
|
import { normalizeLocalCryptoError } from './decryption-errors';
|
|
3
3
|
import { checkLocalCryptoHealth } from './local-crypto-health';
|
|
4
|
+
import { buildMediaMessageMetadataPayload, extractMediaAssetIds, serializeMediaMessageMetadata, } from './media-message-metadata';
|
|
4
5
|
import { mapLocalSdkMessage } from './message.mapper';
|
|
5
6
|
import { WasmCallQueue } from './wasm-call-queue';
|
|
6
7
|
const DEFAULT_DEVICE_ID = 1;
|
|
@@ -67,6 +68,88 @@ export class MssLinkBrowserClient {
|
|
|
67
68
|
throw normalizeLocalCryptoError(error);
|
|
68
69
|
}
|
|
69
70
|
}
|
|
71
|
+
async sendMediaMessage(params) {
|
|
72
|
+
try {
|
|
73
|
+
await this.ensureReady();
|
|
74
|
+
const payload = buildMediaMessageMetadataPayload(params.attachments, params.caption);
|
|
75
|
+
const metadataJson = serializeMediaMessageMetadata(payload);
|
|
76
|
+
const mediaAssetIds = extractMediaAssetIds(params.attachments);
|
|
77
|
+
return this.runWithClient((client) => client.sendMediaMessage(params.chatId, params.targetUserId, metadataJson, mediaAssetIds));
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
throw normalizeLocalCryptoError(error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async encryptMediaBlob(input) {
|
|
84
|
+
try {
|
|
85
|
+
const plaintext = new Uint8Array(await input.blob.arrayBuffer());
|
|
86
|
+
const encrypted = await this.queue.run(async () => {
|
|
87
|
+
const module = await this.loadWasmModule();
|
|
88
|
+
return module.encryptMediaBytes(plaintext);
|
|
89
|
+
});
|
|
90
|
+
const encryptedBytes = new Uint8Array(encrypted.ciphertext);
|
|
91
|
+
return {
|
|
92
|
+
encryptedBlob: new Blob([encryptedBytes], {
|
|
93
|
+
type: 'application/octet-stream',
|
|
94
|
+
}),
|
|
95
|
+
encryption: {
|
|
96
|
+
schema: 'mss.link.media-encryption.v1',
|
|
97
|
+
algorithm: 'AES-256-GCM',
|
|
98
|
+
keyId: input.assetId,
|
|
99
|
+
nonce: encrypted.nonce,
|
|
100
|
+
wrappedKey: encrypted.key,
|
|
101
|
+
},
|
|
102
|
+
attachment: {
|
|
103
|
+
assetId: input.assetId,
|
|
104
|
+
mimeType: input.mimeType,
|
|
105
|
+
size: input.size,
|
|
106
|
+
...(input.originalName ? { originalName: input.originalName } : {}),
|
|
107
|
+
...(input.uploadedAt ? { uploadedAt: input.uploadedAt } : {}),
|
|
108
|
+
encryption: {
|
|
109
|
+
schema: 'mss.link.media-encryption.v1',
|
|
110
|
+
algorithm: 'AES-256-GCM',
|
|
111
|
+
keyId: input.assetId,
|
|
112
|
+
nonce: encrypted.nonce,
|
|
113
|
+
wrappedKey: encrypted.key,
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
throw normalizeLocalCryptoError(error);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async decryptMediaBlob(input) {
|
|
123
|
+
const encryption = input.metadata.encryption;
|
|
124
|
+
if (!encryption) {
|
|
125
|
+
return {
|
|
126
|
+
blob: input.encryptedBlob,
|
|
127
|
+
mimeType: input.metadata.mimeType,
|
|
128
|
+
...(input.metadata.originalName
|
|
129
|
+
? { fileName: input.metadata.originalName }
|
|
130
|
+
: {}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const ciphertext = new Uint8Array(await input.encryptedBlob.arrayBuffer());
|
|
135
|
+
const plaintext = await this.queue.run(async () => {
|
|
136
|
+
const module = await this.loadWasmModule();
|
|
137
|
+
return module.decryptMediaBytes(ciphertext, encryption.wrappedKey, encryption.nonce);
|
|
138
|
+
});
|
|
139
|
+
return {
|
|
140
|
+
blob: new Blob([new Uint8Array(plaintext)], {
|
|
141
|
+
type: input.metadata.mimeType,
|
|
142
|
+
}),
|
|
143
|
+
mimeType: input.metadata.mimeType,
|
|
144
|
+
...(input.metadata.originalName
|
|
145
|
+
? { fileName: input.metadata.originalName }
|
|
146
|
+
: {}),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
throw normalizeLocalCryptoError(error);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
70
153
|
async syncMessages() {
|
|
71
154
|
try {
|
|
72
155
|
await this.runWithClient((client) => client.syncMessages());
|
|
@@ -104,7 +187,7 @@ export class MssLinkBrowserClient {
|
|
|
104
187
|
return this.wasmClient;
|
|
105
188
|
}
|
|
106
189
|
loadWasmModule() {
|
|
107
|
-
this.wasmModulePromise ??= import('@msssystems/mss-crypto-wasm');
|
|
190
|
+
this.wasmModulePromise ??= import('@msssystems/mss-crypto-wasm').then((module) => module);
|
|
108
191
|
return this.wasmModulePromise;
|
|
109
192
|
}
|
|
110
193
|
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { WasmMssClient } from '@msssystems/mss-crypto-wasm';
|
|
2
|
-
|
|
2
|
+
import type { WasmEncryptedMediaBytes } from './media-crypto.contracts';
|
|
3
|
+
export interface MssLinkWasmClient extends WasmMssClient {
|
|
4
|
+
sendMediaMessage(chatId: string, targetUserId: string, metadataJson: string, mediaAssetIds: string[]): Promise<string>;
|
|
5
|
+
}
|
|
6
|
+
export type WasmMssClientConstructor = new (userId: string, deviceId: number, apiBaseUrl: string) => MssLinkWasmClient;
|
|
3
7
|
export interface MssCryptoWasmModule {
|
|
4
8
|
WasmMssClient: WasmMssClientConstructor;
|
|
9
|
+
encryptMediaBytes(plaintext: Uint8Array): WasmEncryptedMediaBytes;
|
|
10
|
+
decryptMediaBytes(ciphertext: Uint8Array, key: string, nonce: string): Uint8Array;
|
|
5
11
|
}
|
|
6
|
-
export type WasmMssClientInstance =
|
|
12
|
+
export type WasmMssClientInstance = MssLinkWasmClient;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@msssystems/mss-link-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Official managed application SDK for MSS ecosystem",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"registry": "https://registry.npmjs.org/"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
|
-
"@msssystems/mss-crypto-wasm": "^0.1.
|
|
56
|
+
"@msssystems/mss-crypto-wasm": "^0.1.17",
|
|
57
57
|
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
|
58
58
|
"@nestjs/core": "^10.0.0 || ^11.0.0",
|
|
59
59
|
"express": "^4.0.0 || ^5.0.0",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
}
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
|
-
"@msssystems/mss-crypto-wasm": "^0.1.
|
|
80
|
+
"@msssystems/mss-crypto-wasm": "^0.1.17",
|
|
81
81
|
"@nestjs/common": "^11.0.1",
|
|
82
82
|
"@nestjs/core": "^11.0.1",
|
|
83
83
|
"@types/express": "^5.0.0",
|