From ee370b55a7c6d7eacc49b8a0232ac94cb6afb8e3 Mon Sep 17 00:00:00 2001 From: Nico Arqueros <1622112+nicarq@users.noreply.github.com> Date: Sun, 10 Mar 2024 20:09:54 -0500 Subject: [PATCH 1/6] add file uploader manager n stuff --- src/schemas/inbox_name.ts | 16 +++ src/utils/FileUploaderManager.ts | 201 +++++++++++++++++++++++++++++++ src/utils/url-join.ts | 6 + tests/inbox_name.test.ts | 29 ++++- tests/url-join.test.ts | 19 +++ 5 files changed, 269 insertions(+), 2 deletions(-) create mode 100644 src/utils/FileUploaderManager.ts create mode 100644 src/utils/url-join.ts create mode 100644 tests/url-join.test.ts diff --git a/src/schemas/inbox_name.ts b/src/schemas/inbox_name.ts index d904ef6..08afea4 100644 --- a/src/schemas/inbox_name.ts +++ b/src/schemas/inbox_name.ts @@ -136,6 +136,22 @@ export class InboxName { getValue(): string { return this.value; } + + getUniqueId(): string { + const parts: string[] = this.value.split("::"); + // Ensure there are at least 3 parts for a valid inbox name + if (parts.length < 3) { + throw new InboxNameError(`Invalid inbox name format: ${this.value}`); + } + + // Remove the first part (inbox type) and the last part (boolean value) + const idParts = parts.slice(1, parts.length - 1); + + // Rejoin the remaining parts to form SOME_ID, which may contain '::' + const someId = idParts.join("::"); + + return someId; + } } export class RegularInbox extends InboxName { diff --git a/src/utils/FileUploaderManager.ts b/src/utils/FileUploaderManager.ts new file mode 100644 index 0000000..779558c --- /dev/null +++ b/src/utils/FileUploaderManager.ts @@ -0,0 +1,201 @@ +import { blake3 } from "@noble/hashes/blake3"; +import { urlJoin } from "../utils/url-join"; +import { InboxName } from "../schemas/inbox_name"; +import { hexToBytes } from "../cryptography/crypto_utils"; +import { ShinkaiMessageBuilder } from "../shinkai_message_builder/shinkai_message_builder"; + +export class FileUploader { + private base_url: string; + private my_encryption_secret_key: string; + private my_signature_secret_key: string; + private receiver_public_key: string; + private sender: string; + private sender_subidentity: string; + private receiver: string; + private job_id: string; + private job_inbox: string; + private symmetric_key: CryptoKey | null; + private folder_id: string | null; + + constructor( + base_url: string, + my_encryption_secret_key: string, + my_signature_secret_key: string, + receiver_public_key: string, + job_inbox: string, + sender: string, + sender_subidentity: string, + receiver: string + ) { + this.base_url = base_url; + this.my_encryption_secret_key = my_encryption_secret_key; + this.my_signature_secret_key = my_signature_secret_key; + this.receiver_public_key = receiver_public_key; + + const inbox = InboxName.fromString(job_inbox); + this.job_id = inbox.getUniqueId(); + this.job_inbox = job_inbox; + this.sender = sender; + this.sender_subidentity = sender_subidentity; + this.receiver = receiver; + this.symmetric_key = null; + this.folder_id = null; + } + + async calculateHashFromSymmetricKey(): Promise { + if (!this.symmetric_key) { + throw new Error("Symmetric key is not set"); + } + + const rawKey = await window.crypto.subtle.exportKey( + "raw", + this.symmetric_key + ); + + const rawKeyArray = new Uint8Array(rawKey); + const hash = blake3(rawKeyArray); + const hashHex = Array.from(hash) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + + return hashHex; + } + + async createFolder(): Promise { + try { + const keyData = window.crypto.getRandomValues(new Uint8Array(32)); + this.symmetric_key = await window.crypto.subtle.importKey( + "raw", + keyData, + "AES-GCM", + true, + ["encrypt", "decrypt"] + ); + + // Export symmetric key + const exportedKey = await window.crypto.subtle.exportKey( + "raw", + this.symmetric_key + ); + const exportedKeyArray = new Uint8Array(exportedKey); + const exportedKeyString = Array.from(exportedKeyArray) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + + const message = + await ShinkaiMessageBuilder.createFilesInboxWithSymKey( + hexToBytes(this.my_encryption_secret_key), + hexToBytes(this.my_signature_secret_key), + hexToBytes(this.receiver_public_key), + this.job_inbox, + exportedKeyString, + this.sender, + this.sender_subidentity, + this.receiver + ); + + const response = await fetch( + urlJoin(this.base_url, "/v1/create_files_inbox_with_symmetric_key"), + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(message), + } + ); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + this.folder_id = await this.calculateHashFromSymmetricKey(); + return response.text(); + } catch (error) { + console.error("Error creating folder:", error); + throw error; + } + } + + async uploadEncryptedFile(file: File, filename?: string): Promise { + if (!this.symmetric_key) { + throw new Error("Symmetric key is not set"); + } + + try { + const iv = window.crypto.getRandomValues(new Uint8Array(12)); + const algorithm = { + name: "AES-GCM", + iv, + }; + const fileData = await file.arrayBuffer(); + const encryptedFileData = await window.crypto.subtle.encrypt( + algorithm, + this.symmetric_key, + fileData + ); + + const hash = await this.calculateHashFromSymmetricKey(); + const nonce = Array.from(iv) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + + const formData = new FormData(); + formData.append( + "file", + new Blob([encryptedFileData]), + filename || file.name + ); + + await fetch( + urlJoin( + this.base_url, + "/v1/add_file_to_inbox_with_symmetric_key", + hash, + nonce + ), + { + method: "POST", + body: formData, + } + ); + } catch (error) { + console.error("Error uploading encrypted file:", error); + throw error; + } + } + + async finalizeAndSend( + content: string, + parent: string | null + ): Promise { + try { + const message = await ShinkaiMessageBuilder.jobMessage( + this.job_id, + content, + this.folder_id || "", + parent, + hexToBytes(this.my_encryption_secret_key), + hexToBytes(this.my_signature_secret_key), + hexToBytes(this.receiver_public_key), + this.sender, + this.sender_subidentity, + this.receiver, + this.sender_subidentity + ); + + const response = await fetch(urlJoin(this.base_url, "/v1/job_message"), { + method: "POST", + body: JSON.stringify(message), + headers: { "Content-Type": "application/json" }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.text(); + } catch (error) { + console.error("Error finalizing and sending:", error); + throw error; + } + } +} diff --git a/src/utils/url-join.ts b/src/utils/url-join.ts new file mode 100644 index 0000000..5515d2e --- /dev/null +++ b/src/utils/url-join.ts @@ -0,0 +1,6 @@ +// It safe join url chunks avoiding double '/' between paths +// Warning: It doesn't supports all cases but it's enough for join shinkai-node api urls +export const urlJoin = (...chunks: string[]): string => { + return chunks.map(chunk => chunk.replace(/(^\/+|\/+$)/mg, '')).filter(chunk => !!chunk).join('/') + }; + \ No newline at end of file diff --git a/tests/inbox_name.test.ts b/tests/inbox_name.test.ts index 9da1141..3171ea9 100644 --- a/tests/inbox_name.test.ts +++ b/tests/inbox_name.test.ts @@ -1,4 +1,4 @@ -import { InboxName } from "../src/schemas/inbox_name"; +import { InboxName, InboxNameError } from "../src/schemas/inbox_name"; describe("InboxName", () => { test("valid_inbox_names", () => { @@ -36,5 +36,30 @@ describe("InboxName", () => { } }); - // Add other tests here... + describe("InboxName getId method", () => { + it("extracts SOME_ID correctly for simple inbox format", () => { + const inbox = new InboxName("inbox::simpleId::true", false); + expect(inbox.getUniqueId()).toBe("simpleId"); + }); + + it("extracts SOME_ID correctly for inbox format with separator in ID", () => { + const inbox = new InboxName("inbox::complex::Id::true", false); + expect(inbox.getUniqueId()).toBe("complex::Id"); + }); + + it("extracts SOME_ID correctly for simple job_inbox format", () => { + const jobInbox = new InboxName("job_inbox::uniqueId::false", false); + expect(jobInbox.getUniqueId()).toBe("uniqueId"); + }); + + it("extracts SOME_ID correctly for job_inbox format with separator in ID", () => { + const jobInbox = new InboxName("job_inbox::unique::Id::false", false); + expect(jobInbox.getUniqueId()).toBe("unique::Id"); + }); + + it("throws an error for invalid inbox name format", () => { + const invalidInbox = new InboxName("invalidFormat", false); + expect(() => invalidInbox.getUniqueId()).toThrow(InboxNameError); + }); + }); }); diff --git a/tests/url-join.test.ts b/tests/url-join.test.ts new file mode 100644 index 0000000..1a7bc89 --- /dev/null +++ b/tests/url-join.test.ts @@ -0,0 +1,19 @@ +import { urlJoin } from "../src/utils/url-join"; + +describe('url join', () => { + const sharedExpectedValue = 'localhost:5555/api/v1/send'; + const cases = [ + { data: ['localhost:5555', 'api', 'v1/send'], expectedValue: sharedExpectedValue }, + { data: ['localhost:5555/', '/api/', 'v1/send/'], expectedValue: sharedExpectedValue }, + { data: ['localhost:5555///', 'api//', '/v1/send'], expectedValue: sharedExpectedValue }, + { data: ['localhost:5555', 'api//', '//v1/send/'], expectedValue: sharedExpectedValue }, + { data: ['localhost:5555', 'api//', '//v1/send?foo=bar/bar2'], expectedValue: `${sharedExpectedValue}?foo=bar/bar2` }, + { data: ['localhost:5555', '/', ''], expectedValue: 'localhost:5555' }, + { data: ['localhost:5555'], expectedValue: 'localhost:5555' }, + { data: ['https://google.com/', 'auth/', 'foo'], expectedValue: 'https://google.com/auth/foo' }, + ]; + test.each(cases)('should generate a valid url', async ({ data, expectedValue }) => { + const url = urlJoin(...data); + expect(url).toBe(expectedValue); + }); +}); From 143a7180b998d867c74cdd4223d0b64b89f46a51 Mon Sep 17 00:00:00 2001 From: Nico Arqueros <1622112+nicarq@users.noreply.github.com> Date: Mon, 11 Mar 2024 23:53:13 -0500 Subject: [PATCH 2/6] update --- package-lock.json | 65 ++++- package.json | 1 + src/index.ts | 1 + .../shinkai_message_builder.ts | 5 +- src/utils/FileUploaderInterfaces.ts | 24 ++ src/utils/FileUploaderManager.ts | 226 ++++++++++-------- 6 files changed, 216 insertions(+), 106 deletions(-) create mode 100644 src/utils/FileUploaderInterfaces.ts diff --git a/package-lock.json b/package-lock.json index 116d719..817b95e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { - "name": "@shinkai_network/shinkai-typescript", - "version": "0.0.1", + "name": "@shinkai_protocol/shinkai-typescript-lib", + "version": "0.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@shinkai_network/shinkai-typescript", - "version": "0.0.1", + "name": "@shinkai_protocol/shinkai-typescript-lib", + "version": "0.5.0", "license": "ISC", "dependencies": { "@noble/ed25519": "^2.0.0", "@noble/hashes": "^1.3.2", "@peculiar/webcrypto": "^1.4.3", "curve25519-js": "^0.0.4", + "form-data": "^4.0.0", "libsodium-wrappers-sumo": "^0.7.13", "noble-ed25519": "^1.2.6", "tweetnacl": "^1.0.3" @@ -3375,6 +3376,11 @@ "node": ">=12.0.0" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -3749,6 +3755,17 @@ "node": ">=0.1.90" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", @@ -3876,6 +3893,14 @@ "node": ">=0.10.0" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -4117,6 +4142,19 @@ "node": ">=8" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", @@ -5328,6 +5366,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", diff --git a/package.json b/package.json index c9a1871..889f7f6 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@noble/hashes": "^1.3.2", "@peculiar/webcrypto": "^1.4.3", "curve25519-js": "^0.0.4", + "form-data": "^4.0.0", "libsodium-wrappers-sumo": "^0.7.13", "noble-ed25519": "^1.2.6", "tweetnacl": "^1.0.3" diff --git a/src/index.ts b/src/index.ts index 6b85f32..fb91fbf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,3 +18,4 @@ export * from './shinkai_message/shinkai_message'; export * from './shinkai_message/shinkai_version'; export * from './shinkai_message_builder/shinkai_message_builder'; +export * from './utils/FileUploaderManager'; diff --git a/src/shinkai_message_builder/shinkai_message_builder.ts b/src/shinkai_message_builder/shinkai_message_builder.ts index 3f457e6..b2a1e21 100644 --- a/src/shinkai_message_builder/shinkai_message_builder.ts +++ b/src/shinkai_message_builder/shinkai_message_builder.ts @@ -796,10 +796,9 @@ export class ShinkaiMessageBuilder { my_subidentity_encryption_sk: EncryptionStaticKey, my_subidentity_signature_sk: SignatureStaticKey, receiver_public_key: EncryptionPublicKey, - inbox: string, symmetric_key_sk: string, - sender_subidentity: string, sender: ProfileName, + sender_subidentity: string, receiver: ProfileName ): Promise { return new ShinkaiMessageBuilder( @@ -812,7 +811,7 @@ export class ShinkaiMessageBuilder { .set_internal_metadata_with_schema( sender_subidentity, "", - inbox, + "", MessageSchemaType.SymmetricKeyExchange, TSEncryptionMethod.None ) diff --git a/src/utils/FileUploaderInterfaces.ts b/src/utils/FileUploaderInterfaces.ts new file mode 100644 index 0000000..6f2e16d --- /dev/null +++ b/src/utils/FileUploaderInterfaces.ts @@ -0,0 +1,24 @@ +// HTTP Service Interface +export interface IHttpService { + fetch(url: string, options: any): Promise; + } + + // Crypto Service Interface + export interface ICryptoService { + getRandomValues(buffer: Uint8Array): Uint8Array; + subtle: { + exportKey(format: string, key: CryptoKey): Promise; + importKey( + format: string, + keyData: BufferSource, + algorithm: string | Algorithm, + extractable: boolean, + keyUsages: KeyUsage[] + ): Promise; + encrypt( + algorithm: AlgorithmIdentifier, + key: CryptoKey, + data: BufferSource + ): Promise; + }; + } \ No newline at end of file diff --git a/src/utils/FileUploaderManager.ts b/src/utils/FileUploaderManager.ts index 779558c..276d055 100644 --- a/src/utils/FileUploaderManager.ts +++ b/src/utils/FileUploaderManager.ts @@ -3,8 +3,46 @@ import { urlJoin } from "../utils/url-join"; import { InboxName } from "../schemas/inbox_name"; import { hexToBytes } from "../cryptography/crypto_utils"; import { ShinkaiMessageBuilder } from "../shinkai_message_builder/shinkai_message_builder"; +import { ICryptoService, IHttpService } from "./FileUploaderInterfaces"; + +// Conditional FormData implementation +interface IFormData { + append(name: string, value: any, fileName?: string): void; +} + +// Conditional FormData implementation +let FormDataImplementation: { new (): IFormData }; +if (typeof window === "undefined") { + // We are in a Node.js environment + FormDataImplementation = require("form-data") as unknown as { + new (): IFormData; + }; +} else { + // We are in a web browser environment + FormDataImplementation = FormData as unknown as { new (): IFormData }; +} + +function createFormData(): IFormData { + return new FormDataImplementation(); +} + +function appendFile( + formData: IFormData, + fieldName: string, + file: any, + fileName: string +) { + // Convert ArrayBuffer to Buffer in Node.js environment + if (typeof window === "undefined" && file instanceof ArrayBuffer) { + file = Buffer.from(file); + } + formData.append(fieldName, file, fileName); +} export class FileUploader { + private httpService: IHttpService; + private cryptoService: ICryptoService; + private base_url: string; private my_encryption_secret_key: string; private my_signature_secret_key: string; @@ -12,29 +50,27 @@ export class FileUploader { private sender: string; private sender_subidentity: string; private receiver: string; - private job_id: string; - private job_inbox: string; private symmetric_key: CryptoKey | null; private folder_id: string | null; constructor( + httpService: IHttpService, + cryptoService: ICryptoService, base_url: string, my_encryption_secret_key: string, my_signature_secret_key: string, receiver_public_key: string, - job_inbox: string, sender: string, sender_subidentity: string, receiver: string ) { + this.httpService = httpService; + this.cryptoService = cryptoService; this.base_url = base_url; this.my_encryption_secret_key = my_encryption_secret_key; this.my_signature_secret_key = my_signature_secret_key; this.receiver_public_key = receiver_public_key; - const inbox = InboxName.fromString(job_inbox); - this.job_id = inbox.getUniqueId(); - this.job_inbox = job_inbox; this.sender = sender; this.sender_subidentity = sender_subidentity; this.receiver = receiver; @@ -47,13 +83,16 @@ export class FileUploader { throw new Error("Symmetric key is not set"); } - const rawKey = await window.crypto.subtle.exportKey( + const rawKey = await this.cryptoService.subtle.exportKey( "raw", this.symmetric_key ); - const rawKeyArray = new Uint8Array(rawKey); - const hash = blake3(rawKeyArray); + const keyHexString = Array.from(rawKeyArray) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + + const hash = blake3(keyHexString); const hashHex = Array.from(hash) .map((b) => b.toString(16).padStart(2, "0")) .join(""); @@ -61,10 +100,21 @@ export class FileUploader { return hashHex; } + async generateAndUpdateSymmetricKey(): Promise { + const keyData = this.cryptoService.getRandomValues(new Uint8Array(32)); + this.symmetric_key = await this.cryptoService.subtle.importKey( + "raw", + keyData, + "AES-GCM", + true, + ["encrypt", "decrypt"] + ); + } + async createFolder(): Promise { try { - const keyData = window.crypto.getRandomValues(new Uint8Array(32)); - this.symmetric_key = await window.crypto.subtle.importKey( + const keyData = this.cryptoService.getRandomValues(new Uint8Array(32)); + this.symmetric_key = await this.cryptoService.subtle.importKey( "raw", keyData, "AES-GCM", @@ -72,8 +122,7 @@ export class FileUploader { ["encrypt", "decrypt"] ); - // Export symmetric key - const exportedKey = await window.crypto.subtle.exportKey( + const exportedKey = await this.cryptoService.subtle.exportKey( "raw", this.symmetric_key ); @@ -82,19 +131,18 @@ export class FileUploader { .map((b) => b.toString(16).padStart(2, "0")) .join(""); - const message = - await ShinkaiMessageBuilder.createFilesInboxWithSymKey( - hexToBytes(this.my_encryption_secret_key), - hexToBytes(this.my_signature_secret_key), - hexToBytes(this.receiver_public_key), - this.job_inbox, - exportedKeyString, - this.sender, - this.sender_subidentity, - this.receiver - ); - - const response = await fetch( + const hash = await this.calculateHashFromSymmetricKey(); + const message = await ShinkaiMessageBuilder.createFilesInboxWithSymKey( + hexToBytes(this.my_encryption_secret_key), + hexToBytes(this.my_signature_secret_key), + hexToBytes(this.receiver_public_key), + exportedKeyString, + this.sender, + this.sender_subidentity, + this.receiver + ); + + const response = await this.httpService.fetch( urlJoin(this.base_url, "/v1/create_files_inbox_with_symmetric_key"), { method: "POST", @@ -108,94 +156,74 @@ export class FileUploader { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } - this.folder_id = await this.calculateHashFromSymmetricKey(); - return response.text(); + this.folder_id = hash; + return this.folder_id; } catch (error) { console.error("Error creating folder:", error); throw error; } } - async uploadEncryptedFile(file: File, filename?: string): Promise { + async uploadEncryptedFileWeb(file: File, filename?: string): Promise { + const fileData = await file.arrayBuffer(); + return this.uploadEncryptedData(fileData, filename || file.name); + } + + // Method for uploading files in a Node.js environment + async uploadEncryptedFileNode( + buffer: Buffer, + filename: string + ): Promise { + return this.uploadEncryptedData(buffer, filename); + } + + private async uploadEncryptedData( + data: ArrayBuffer | Buffer, + filename: string + ): Promise { if (!this.symmetric_key) { throw new Error("Symmetric key is not set"); } - try { - const iv = window.crypto.getRandomValues(new Uint8Array(12)); - const algorithm = { - name: "AES-GCM", - iv, - }; - const fileData = await file.arrayBuffer(); - const encryptedFileData = await window.crypto.subtle.encrypt( - algorithm, - this.symmetric_key, - fileData - ); + // Generate the initialization vector (iv) here + const iv = this.cryptoService.getRandomValues(new Uint8Array(12)); + const algorithm = { name: "AES-GCM", iv }; - const hash = await this.calculateHashFromSymmetricKey(); - const nonce = Array.from(iv) - .map((b) => b.toString(16).padStart(2, "0")) - .join(""); + // Perform encryption + const encryptedFileData = await this.cryptoService.subtle.encrypt( + algorithm, + this.symmetric_key, // symmetric_key is guaranteed to be non-null here + data + ); - const formData = new FormData(); - formData.append( - "file", - new Blob([encryptedFileData]), - filename || file.name - ); + const hash = await this.calculateHashFromSymmetricKey(); + const nonce = Array.from(iv) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); - await fetch( - urlJoin( - this.base_url, - "/v1/add_file_to_inbox_with_symmetric_key", - hash, - nonce - ), - { - method: "POST", - body: formData, - } - ); - } catch (error) { - console.error("Error uploading encrypted file:", error); - throw error; + const formData = createFormData(); + // Adjust for environment differences + let fileData; + if (typeof window === "undefined") { + // In Node.js, directly use Buffer + fileData = Buffer.from(encryptedFileData); + } else { + // In the browser, use Blob + fileData = new Blob([encryptedFileData]); } - } - - async finalizeAndSend( - content: string, - parent: string | null - ): Promise { - try { - const message = await ShinkaiMessageBuilder.jobMessage( - this.job_id, - content, - this.folder_id || "", - parent, - hexToBytes(this.my_encryption_secret_key), - hexToBytes(this.my_signature_secret_key), - hexToBytes(this.receiver_public_key), - this.sender, - this.sender_subidentity, - this.receiver, - this.sender_subidentity - ); - - const response = await fetch(urlJoin(this.base_url, "/v1/job_message"), { + appendFile(formData, "file", fileData, filename); + + await this.httpService.fetch( + urlJoin( + this.base_url, + "/v1/add_file_to_inbox_with_symmetric_key", + hash, + nonce + ), + { method: "POST", - body: JSON.stringify(message), - headers: { "Content-Type": "application/json" }, - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + body: formData, } - return response.text(); - } catch (error) { - console.error("Error finalizing and sending:", error); - throw error; - } + ); } } From d97b4abdcded7498a241f68b58ee0ac4b23220aa Mon Sep 17 00:00:00 2001 From: Nico Arqueros <1622112+nicarq@users.noreply.github.com> Date: Tue, 12 Mar 2024 00:48:02 -0500 Subject: [PATCH 3/6] fix --- src/shinkai_message_builder/shinkai_message_builder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shinkai_message_builder/shinkai_message_builder.ts b/src/shinkai_message_builder/shinkai_message_builder.ts index b2a1e21..39451eb 100644 --- a/src/shinkai_message_builder/shinkai_message_builder.ts +++ b/src/shinkai_message_builder/shinkai_message_builder.ts @@ -1109,7 +1109,7 @@ export class ShinkaiMessageBuilder { receiver: ProfileName, receiver_subidentity: string ): Promise { - const createItemsInfo = { destination_path, file_inbox }; + const createItemsInfo = { path: destination_path, file_inbox }; return ShinkaiMessageBuilder.createCustomShinkaiMessageToNode( my_encryption_secret_key, my_signature_secret_key, From e6eb9d16544bedb6f6dd0791841c2df2a850714a Mon Sep 17 00:00:00 2001 From: Nico Arqueros <1622112+nicarq@users.noreply.github.com> Date: Tue, 12 Mar 2024 00:51:13 -0500 Subject: [PATCH 4/6] fix test --- tests/shinkai_messsage_builder.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/shinkai_messsage_builder.test.ts b/tests/shinkai_messsage_builder.test.ts index b0b56e5..b6b302f 100644 --- a/tests/shinkai_messsage_builder.test.ts +++ b/tests/shinkai_messsage_builder.test.ts @@ -760,14 +760,12 @@ describe("ShinkaiMessageBuilder pre-made methods", () => { const sender = "@@sender.shinkai"; const receiver = "@@receiver.shinkai"; const sender_subidentity = "sender_subidentity"; - const inbox = "inbox_name"; const symmetric_key_sk = "symmetric_key"; const message = await ShinkaiMessageBuilder.createFilesInboxWithSymKey( my_subidentity_encryption_sk, my_subidentity_signature_sk, receiver_public_key, - inbox, symmetric_key_sk, sender_subidentity, sender, From 4b17e19069c3847fa9a675c59b5b9a47620fbdf8 Mon Sep 17 00:00:00 2001 From: Nico Arqueros <1622112+nicarq@users.noreply.github.com> Date: Tue, 12 Mar 2024 00:52:03 -0500 Subject: [PATCH 5/6] version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 889f7f6..6fa9c5c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@shinkai_protocol/shinkai-typescript-lib", - "version": "0.5.0", + "version": "0.5.1", "description": "Typescript library to build and handle Shinkai Messages", "type": "commonjs", "main": "./dist/index.js", From 818dbf1b78ffe59a8270fdeb80dc6f322c1caa0c Mon Sep 17 00:00:00 2001 From: Nico Arqueros <1622112+nicarq@users.noreply.github.com> Date: Tue, 12 Mar 2024 00:54:18 -0500 Subject: [PATCH 6/6] fix test again --- tests/shinkai_messsage_builder.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/shinkai_messsage_builder.test.ts b/tests/shinkai_messsage_builder.test.ts index b6b302f..3e6a798 100644 --- a/tests/shinkai_messsage_builder.test.ts +++ b/tests/shinkai_messsage_builder.test.ts @@ -767,8 +767,8 @@ describe("ShinkaiMessageBuilder pre-made methods", () => { my_subidentity_signature_sk, receiver_public_key, symmetric_key_sk, - sender_subidentity, sender, + sender_subidentity, receiver );