@msssystems/mss-link-sdk 0.1.5 → 0.1.7

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.
@@ -3,6 +3,7 @@ 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';
6
7
  export * from './media-message.contracts';
7
8
  export * from './media-message-metadata';
8
9
  export * from './message.contracts';
@@ -10,3 +11,5 @@ export * from './message-decryption.contracts';
10
11
  export * from './message-decryption-state';
11
12
  export * from './message.mapper';
12
13
  export * from './mss-link-browser-client';
14
+ export * from './storage-metadata-crypto';
15
+ export * from './storage-metadata-crypto.contracts';
@@ -3,6 +3,7 @@ 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';
6
7
  export * from './media-message.contracts';
7
8
  export * from './media-message-metadata';
8
9
  export * from './message.contracts';
@@ -10,3 +11,5 @@ export * from './message-decryption.contracts';
10
11
  export * from './message-decryption-state';
11
12
  export * from './message.mapper';
12
13
  export * from './mss-link-browser-client';
14
+ export * from './storage-metadata-crypto';
15
+ export * from './storage-metadata-crypto.contracts';
@@ -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 {};
@@ -22,6 +22,9 @@ export function serializeMediaMessageMetadata(payload) {
22
22
  ? { originalName: attachment.originalName }
23
23
  : {}),
24
24
  ...(attachment.uploadedAt ? { uploadedAt: attachment.uploadedAt } : {}),
25
+ ...(attachment.encryption
26
+ ? { encryption: serializeAttachmentEncryption(attachment.encryption) }
27
+ : {}),
25
28
  })),
26
29
  ...(payload.caption ? { caption: payload.caption } : {}),
27
30
  });
@@ -52,5 +55,39 @@ function normalizeAttachment(attachment) {
52
55
  ...(attachment.uploadedAt?.trim()
53
56
  ? { uploadedAt: attachment.uploadedAt.trim() }
54
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,
55
92
  };
56
93
  }
@@ -4,6 +4,14 @@ export interface MediaMessageAttachmentInput {
4
4
  size: string | number;
5
5
  originalName?: string;
6
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';
7
15
  }
8
16
  export interface SendMediaMessageParams {
9
17
  chatId: string;
@@ -17,6 +25,14 @@ export interface MediaMessageAttachmentMetadata {
17
25
  size: string;
18
26
  originalName?: string;
19
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;
20
36
  }
21
37
  export interface MediaMessageMetadataPayload {
22
38
  schema: 'mss.link.media-message.v1';
@@ -1,7 +1,9 @@
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';
3
4
  import type { SendMediaMessageParams } from './media-message.contracts';
4
5
  import type { DecryptedMessage } from './message.contracts';
6
+ import type { DecryptDriveFileMetadataInput, DecryptDriveFolderMetadataInput, DecryptedDriveFileMetadata, DecryptedDriveFolderMetadata, EncryptedDriveFileMetadata, EncryptedDriveFolderMetadata, EncryptDriveFileMetadataInput, EncryptDriveFolderMetadataInput } from './storage-metadata-crypto.contracts';
5
7
  export declare class MssLinkBrowserClient {
6
8
  private readonly userId;
7
9
  private readonly apiBaseUrl;
@@ -26,6 +28,12 @@ export declare class MssLinkBrowserClient {
26
28
  kind?: string;
27
29
  }): Promise<string>;
28
30
  sendMediaMessage(params: SendMediaMessageParams): Promise<string>;
31
+ encryptMediaBlob(input: EncryptMediaBlobInput): Promise<EncryptedMediaBlobResult>;
32
+ decryptMediaBlob(input: DecryptMediaBlobInput): Promise<DecryptedMediaBlobResult>;
33
+ encryptDriveFolderMetadata(input: EncryptDriveFolderMetadataInput): Promise<EncryptedDriveFolderMetadata>;
34
+ decryptDriveFolderMetadata(input: DecryptDriveFolderMetadataInput): Promise<DecryptedDriveFolderMetadata>;
35
+ encryptDriveFileMetadata(input: EncryptDriveFileMetadataInput): Promise<EncryptedDriveFileMetadata>;
36
+ decryptDriveFileMetadata(input: DecryptDriveFileMetadataInput): Promise<DecryptedDriveFileMetadata>;
29
37
  syncMessages(): Promise<void>;
30
38
  getLocalMessages(chatId: string): Promise<DecryptedMessage[]>;
31
39
  reset(): void;
@@ -3,6 +3,7 @@ import { normalizeLocalCryptoError } from './decryption-errors';
3
3
  import { checkLocalCryptoHealth } from './local-crypto-health';
4
4
  import { buildMediaMessageMetadataPayload, extractMediaAssetIds, serializeMediaMessageMetadata, } from './media-message-metadata';
5
5
  import { mapLocalSdkMessage } from './message.mapper';
6
+ import { buildDecryptedDriveFileMetadata, buildDecryptedDriveFolderMetadata, buildEncryptedDriveFileMetadata, buildEncryptedDriveFolderMetadata, normalizeDriveFileMetadataInput, normalizeDriveFolderMetadataInput, normalizeEncryptedDriveFileMetadataInput, normalizeEncryptedDriveFolderMetadataInput, } from './storage-metadata-crypto';
6
7
  import { WasmCallQueue } from './wasm-call-queue';
7
8
  const DEFAULT_DEVICE_ID = 1;
8
9
  const DEFAULT_REGISTRATION_ID = 1;
@@ -80,6 +81,134 @@ export class MssLinkBrowserClient {
80
81
  throw normalizeLocalCryptoError(error);
81
82
  }
82
83
  }
84
+ async encryptMediaBlob(input) {
85
+ try {
86
+ const plaintext = new Uint8Array(await input.blob.arrayBuffer());
87
+ const encrypted = await this.queue.run(async () => {
88
+ const module = await this.loadWasmModule();
89
+ return module.encryptMediaBytes(plaintext);
90
+ });
91
+ const encryptedBytes = new Uint8Array(encrypted.ciphertext);
92
+ return {
93
+ encryptedBlob: new Blob([encryptedBytes], {
94
+ type: 'application/octet-stream',
95
+ }),
96
+ encryption: {
97
+ schema: 'mss.link.media-encryption.v1',
98
+ algorithm: 'AES-256-GCM',
99
+ keyId: input.assetId,
100
+ nonce: encrypted.nonce,
101
+ wrappedKey: encrypted.key,
102
+ },
103
+ attachment: {
104
+ assetId: input.assetId,
105
+ mimeType: input.mimeType,
106
+ size: input.size,
107
+ ...(input.originalName ? { originalName: input.originalName } : {}),
108
+ ...(input.uploadedAt ? { uploadedAt: input.uploadedAt } : {}),
109
+ encryption: {
110
+ schema: 'mss.link.media-encryption.v1',
111
+ algorithm: 'AES-256-GCM',
112
+ keyId: input.assetId,
113
+ nonce: encrypted.nonce,
114
+ wrappedKey: encrypted.key,
115
+ },
116
+ },
117
+ };
118
+ }
119
+ catch (error) {
120
+ throw normalizeLocalCryptoError(error);
121
+ }
122
+ }
123
+ async decryptMediaBlob(input) {
124
+ const encryption = input.metadata.encryption;
125
+ if (!encryption) {
126
+ return {
127
+ blob: input.encryptedBlob,
128
+ mimeType: input.metadata.mimeType,
129
+ ...(input.metadata.originalName
130
+ ? { fileName: input.metadata.originalName }
131
+ : {}),
132
+ };
133
+ }
134
+ try {
135
+ const ciphertext = new Uint8Array(await input.encryptedBlob.arrayBuffer());
136
+ const plaintext = await this.queue.run(async () => {
137
+ const module = await this.loadWasmModule();
138
+ return module.decryptMediaBytes(ciphertext, encryption.wrappedKey, encryption.nonce);
139
+ });
140
+ return {
141
+ blob: new Blob([new Uint8Array(plaintext)], {
142
+ type: input.metadata.mimeType,
143
+ }),
144
+ mimeType: input.metadata.mimeType,
145
+ ...(input.metadata.originalName
146
+ ? { fileName: input.metadata.originalName }
147
+ : {}),
148
+ };
149
+ }
150
+ catch (error) {
151
+ throw normalizeLocalCryptoError(error);
152
+ }
153
+ }
154
+ async encryptDriveFolderMetadata(input) {
155
+ try {
156
+ const metadata = normalizeDriveFolderMetadataInput(input);
157
+ const encryptedName = await this.queue.run(async () => {
158
+ const module = await this.loadWasmModule();
159
+ return module.encryptStorageMetadata(metadata.name);
160
+ });
161
+ return buildEncryptedDriveFolderMetadata(encryptedName);
162
+ }
163
+ catch (error) {
164
+ throw normalizeLocalCryptoError(error);
165
+ }
166
+ }
167
+ async decryptDriveFolderMetadata(input) {
168
+ try {
169
+ const metadata = normalizeEncryptedDriveFolderMetadataInput(input);
170
+ const name = await this.queue.run(async () => {
171
+ const module = await this.loadWasmModule();
172
+ return module.decryptStorageMetadata(metadata.encryptedName);
173
+ });
174
+ return buildDecryptedDriveFolderMetadata(name);
175
+ }
176
+ catch (error) {
177
+ throw normalizeLocalCryptoError(error);
178
+ }
179
+ }
180
+ async encryptDriveFileMetadata(input) {
181
+ try {
182
+ const metadata = normalizeDriveFileMetadataInput(input);
183
+ const [encryptedName, encryptedMimeType] = await this.queue.run(async () => {
184
+ const module = await this.loadWasmModule();
185
+ return [
186
+ module.encryptStorageMetadata(metadata.name),
187
+ module.encryptStorageMetadata(metadata.mimeType),
188
+ ];
189
+ });
190
+ return buildEncryptedDriveFileMetadata(encryptedName, encryptedMimeType);
191
+ }
192
+ catch (error) {
193
+ throw normalizeLocalCryptoError(error);
194
+ }
195
+ }
196
+ async decryptDriveFileMetadata(input) {
197
+ try {
198
+ const metadata = normalizeEncryptedDriveFileMetadataInput(input);
199
+ const [name, mimeType] = await this.queue.run(async () => {
200
+ const module = await this.loadWasmModule();
201
+ return [
202
+ module.decryptStorageMetadata(metadata.encryptedName),
203
+ module.decryptStorageMetadata(metadata.encryptedMimeType),
204
+ ];
205
+ });
206
+ return buildDecryptedDriveFileMetadata(name, mimeType);
207
+ }
208
+ catch (error) {
209
+ throw normalizeLocalCryptoError(error);
210
+ }
211
+ }
83
212
  async syncMessages() {
84
213
  try {
85
214
  await this.runWithClient((client) => client.syncMessages());
@@ -0,0 +1,30 @@
1
+ export interface EncryptDriveFolderMetadataInput {
2
+ name: string;
3
+ }
4
+ export interface EncryptedDriveFolderMetadata {
5
+ schema: 'mss.link.drive-folder-metadata.v1';
6
+ encryptedName: string;
7
+ }
8
+ export interface DecryptDriveFolderMetadataInput {
9
+ encryptedName: string;
10
+ }
11
+ export interface DecryptedDriveFolderMetadata {
12
+ name: string;
13
+ }
14
+ export interface EncryptDriveFileMetadataInput {
15
+ name: string;
16
+ mimeType: string;
17
+ }
18
+ export interface EncryptedDriveFileMetadata {
19
+ schema: 'mss.link.drive-file-metadata.v1';
20
+ encryptedName: string;
21
+ encryptedMimeType: string;
22
+ }
23
+ export interface DecryptDriveFileMetadataInput {
24
+ encryptedName: string;
25
+ encryptedMimeType: string;
26
+ }
27
+ export interface DecryptedDriveFileMetadata {
28
+ mimeType: string;
29
+ name: string;
30
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { DecryptDriveFileMetadataInput, DecryptDriveFolderMetadataInput, DecryptedDriveFileMetadata, DecryptedDriveFolderMetadata, EncryptedDriveFileMetadata, EncryptedDriveFolderMetadata, EncryptDriveFileMetadataInput, EncryptDriveFolderMetadataInput } from './storage-metadata-crypto.contracts';
2
+ export declare function normalizeDriveFolderMetadataInput(input: EncryptDriveFolderMetadataInput): EncryptDriveFolderMetadataInput;
3
+ export declare function normalizeDriveFileMetadataInput(input: EncryptDriveFileMetadataInput): EncryptDriveFileMetadataInput;
4
+ export declare function buildEncryptedDriveFolderMetadata(encryptedName: string): EncryptedDriveFolderMetadata;
5
+ export declare function buildEncryptedDriveFileMetadata(encryptedName: string, encryptedMimeType: string): EncryptedDriveFileMetadata;
6
+ export declare function normalizeEncryptedDriveFolderMetadataInput(input: DecryptDriveFolderMetadataInput): DecryptDriveFolderMetadataInput;
7
+ export declare function normalizeEncryptedDriveFileMetadataInput(input: DecryptDriveFileMetadataInput): DecryptDriveFileMetadataInput;
8
+ export declare function buildDecryptedDriveFolderMetadata(name: string): DecryptedDriveFolderMetadata;
9
+ export declare function buildDecryptedDriveFileMetadata(name: string, mimeType: string): DecryptedDriveFileMetadata;
@@ -0,0 +1,76 @@
1
+ export function normalizeDriveFolderMetadataInput(input) {
2
+ const name = input.name.trim();
3
+ if (!name) {
4
+ throw new Error('Drive folder name is required.');
5
+ }
6
+ return { name };
7
+ }
8
+ export function normalizeDriveFileMetadataInput(input) {
9
+ const name = input.name.trim();
10
+ const mimeType = input.mimeType.trim();
11
+ if (!name) {
12
+ throw new Error('Drive file name is required.');
13
+ }
14
+ if (!mimeType) {
15
+ throw new Error('Drive file mimeType is required.');
16
+ }
17
+ return { mimeType, name };
18
+ }
19
+ export function buildEncryptedDriveFolderMetadata(encryptedName) {
20
+ const normalizedName = normalizeEncryptedField(encryptedName, 'encryptedName');
21
+ return {
22
+ schema: 'mss.link.drive-folder-metadata.v1',
23
+ encryptedName: normalizedName,
24
+ };
25
+ }
26
+ export function buildEncryptedDriveFileMetadata(encryptedName, encryptedMimeType) {
27
+ const normalizedName = normalizeEncryptedField(encryptedName, 'encryptedName');
28
+ const normalizedMimeType = normalizeEncryptedField(encryptedMimeType, 'encryptedMimeType');
29
+ return {
30
+ schema: 'mss.link.drive-file-metadata.v1',
31
+ encryptedName: normalizedName,
32
+ encryptedMimeType: normalizedMimeType,
33
+ };
34
+ }
35
+ export function normalizeEncryptedDriveFolderMetadataInput(input) {
36
+ return {
37
+ encryptedName: normalizeEncryptedField(input.encryptedName, 'encryptedName'),
38
+ };
39
+ }
40
+ export function normalizeEncryptedDriveFileMetadataInput(input) {
41
+ return {
42
+ encryptedName: normalizeEncryptedField(input.encryptedName, 'encryptedName'),
43
+ encryptedMimeType: normalizeEncryptedField(input.encryptedMimeType, 'encryptedMimeType'),
44
+ };
45
+ }
46
+ export function buildDecryptedDriveFolderMetadata(name) {
47
+ const normalizedName = name.trim();
48
+ if (!normalizedName) {
49
+ throw new Error('Decrypted drive folder name is empty.');
50
+ }
51
+ return { name: normalizedName };
52
+ }
53
+ export function buildDecryptedDriveFileMetadata(name, mimeType) {
54
+ const normalizedName = name.trim();
55
+ const normalizedMimeType = mimeType.trim();
56
+ if (!normalizedName) {
57
+ throw new Error('Decrypted drive file name is empty.');
58
+ }
59
+ if (!normalizedMimeType) {
60
+ throw new Error('Decrypted drive file mimeType is empty.');
61
+ }
62
+ return {
63
+ mimeType: normalizedMimeType,
64
+ name: normalizedName,
65
+ };
66
+ }
67
+ function normalizeEncryptedField(value, fieldName) {
68
+ const normalized = value.trim();
69
+ if (!normalized) {
70
+ throw new Error(`Drive metadata ${fieldName} is required.`);
71
+ }
72
+ if (!/^[0-9a-f]+$/i.test(normalized)) {
73
+ throw new Error(`Drive metadata ${fieldName} must be hexadecimal.`);
74
+ }
75
+ return normalized;
76
+ }
@@ -1,9 +1,14 @@
1
1
  import type { WasmMssClient } from '@msssystems/mss-crypto-wasm';
2
+ import type { WasmEncryptedMediaBytes } from './media-crypto.contracts';
2
3
  export interface MssLinkWasmClient extends WasmMssClient {
3
4
  sendMediaMessage(chatId: string, targetUserId: string, metadataJson: string, mediaAssetIds: string[]): Promise<string>;
4
5
  }
5
6
  export type WasmMssClientConstructor = new (userId: string, deviceId: number, apiBaseUrl: string) => MssLinkWasmClient;
6
7
  export interface MssCryptoWasmModule {
7
8
  WasmMssClient: WasmMssClientConstructor;
9
+ decryptStorageMetadata(encryptedHex: string): string;
10
+ encryptMediaBytes(plaintext: Uint8Array): WasmEncryptedMediaBytes;
11
+ encryptStorageMetadata(plaintext: string): string;
12
+ decryptMediaBytes(ciphertext: Uint8Array, key: string, nonce: string): Uint8Array;
8
13
  }
9
14
  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.5",
3
+ "version": "0.1.7",
4
4
  "description": "Official managed application SDK for MSS ecosystem",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",