diff --git a/.changeset/calm-guests-sin.md b/.changeset/calm-guests-sin.md new file mode 100644 index 000000000..fe3e4f35e --- /dev/null +++ b/.changeset/calm-guests-sin.md @@ -0,0 +1,5 @@ +--- +"@xmtp/browser-sdk": patch +--- + +Update Browser SDK diff --git a/.gitignore b/.gitignore index 9a8e4708d..0d7467267 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ yarn-error.log* # local env files .env* +!.env.example # turbo .turbo diff --git a/examples/react-vite-browser-sdk/.env.example b/examples/react-vite-browser-sdk/.env.example new file mode 100644 index 000000000..b435a22a9 --- /dev/null +++ b/examples/react-vite-browser-sdk/.env.example @@ -0,0 +1,2 @@ +VITE_PROJECT_ID= # Your Reown (https://reown.com/) project ID +VITE_ENCRYPTION_KEY= # 32 byte hex string diff --git a/examples/react-vite-browser-sdk/src/createClient.ts b/examples/react-vite-browser-sdk/src/createClient.ts index 19874209d..261ffd8d8 100644 --- a/examples/react-vite-browser-sdk/src/createClient.ts +++ b/examples/react-vite-browser-sdk/src/createClient.ts @@ -16,8 +16,13 @@ export const getSignature = async (client: Client, wallet: Wallet) => { }; export const createClient = async (walletKey: string) => { + const encryptionKeyHex = import.meta.env.VITE_ENCRYPTION_KEY; + if (!encryptionKeyHex) { + throw new Error("VITE_ENCRYPTION_KEY must be set in the environment"); + } + const encryptionBytes = toBytes(encryptionKeyHex); const wallet = createWallet(walletKey); - const client = await Client.create(wallet.account.address, { + const client = await Client.create(wallet.account.address, encryptionBytes, { env: "local", }); const isRegistered = await client.isRegistered(); diff --git a/examples/react-vite-browser-sdk/src/globals.d.ts b/examples/react-vite-browser-sdk/src/globals.d.ts index 5b948c9b6..a774196a4 100644 --- a/examples/react-vite-browser-sdk/src/globals.d.ts +++ b/examples/react-vite-browser-sdk/src/globals.d.ts @@ -1,6 +1,7 @@ interface ImportMeta { env: { - VITE_PROJECT_ID: string; + VITE_PROJECT_ID?: string; + VITE_ENCRYPTION_KEY?: string; }; } diff --git a/examples/react-vite-browser-sdk/src/main.tsx b/examples/react-vite-browser-sdk/src/main.tsx index c4833794e..ba5dd8a1b 100644 --- a/examples/react-vite-browser-sdk/src/main.tsx +++ b/examples/react-vite-browser-sdk/src/main.tsx @@ -9,9 +9,14 @@ import { WagmiProvider } from "wagmi"; import { App } from "./App"; import "./index.css"; +const projectId = import.meta.env.VITE_PROJECT_ID; +if (!projectId) { + throw new Error("VITE_PROJECT_ID must be set in the environment"); +} + export const config = getDefaultConfig({ appName: "XMTP V3 Browser SDK Example", - projectId: import.meta.env.VITE_PROJECT_ID, + projectId, chains: [mainnet], transports: { [mainnet.id]: http(), diff --git a/packages/frames-validator/package.json b/packages/frames-validator/package.json index e0960c0cb..dd6590b8d 100644 --- a/packages/frames-validator/package.json +++ b/packages/frames-validator/package.json @@ -38,6 +38,7 @@ "devDependencies": { "@open-frames/types": "^0.1.1", "@rollup/plugin-typescript": "^12.1.1", + "@types/bl": "^5.1.4", "@xmtp/frames-client": "^0.5.4", "@xmtp/xmtp-js": "^12.1.0", "ethers": "^6.10.0", diff --git a/packages/frames-validator/tsconfig.json b/packages/frames-validator/tsconfig.json index 016c95fe6..a8f57807b 100644 --- a/packages/frames-validator/tsconfig.json +++ b/packages/frames-validator/tsconfig.json @@ -1,4 +1,5 @@ { "extends": "tsconfig/base.json", - "includes": ["src", "rollup.config.js", "vitest.config.ts", "vitest.setup.ts"] + "include": ["src", "rollup.config.js", "vitest.config.ts"], + "exclude": ["dist", "node_modules"] } diff --git a/sdks/browser-sdk/package.json b/sdks/browser-sdk/package.json index 3c28cdab7..0535c1fad 100644 --- a/sdks/browser-sdk/package.json +++ b/sdks/browser-sdk/package.json @@ -65,7 +65,7 @@ "@xmtp/content-type-primitives": "^1.0.1", "@xmtp/content-type-text": "^1.0.0", "@xmtp/proto": "^3.70.0", - "@xmtp/wasm-bindings": "^0.0.3", + "@xmtp/wasm-bindings": "^0.0.4", "uuid": "^10.0.0" }, "devDependencies": { diff --git a/sdks/browser-sdk/rollup.config.js b/sdks/browser-sdk/rollup.config.js index c8c6d8dc0..92b8c241a 100644 --- a/sdks/browser-sdk/rollup.config.js +++ b/sdks/browser-sdk/rollup.config.js @@ -21,6 +21,7 @@ const external = [ "@xmtp/content-type-text", "@xmtp/wasm-bindings", "@xmtp/content-type-primitives", + "@xmtp/content-type-group-updated", "@xmtp/proto", "uuid", ]; diff --git a/sdks/browser-sdk/src/Client.ts b/sdks/browser-sdk/src/Client.ts index da9cfb6f8..b028a1a92 100644 --- a/sdks/browser-sdk/src/Client.ts +++ b/sdks/browser-sdk/src/Client.ts @@ -37,13 +37,23 @@ export class Client extends ClientWorkerClass { #codecs: Map; - constructor(address: string, options?: ClientOptions) { + #encryptionKey: Uint8Array; + + constructor( + address: string, + encryptionKey: Uint8Array, + options?: ClientOptions, + ) { const worker = new Worker(new URL("./workers/client", import.meta.url), { type: "module", }); - super(worker, options?.enableLogging ?? false); + super( + worker, + options?.loggingLevel !== undefined && options.loggingLevel !== "off", + ); this.address = address; this.options = options; + this.#encryptionKey = encryptionKey; this.#conversations = new Conversations(this); const codecs = [ new GroupUpdatedCodec(), @@ -58,6 +68,7 @@ export class Client extends ClientWorkerClass { async init() { const result = await this.sendMessage("init", { address: this.address, + encryptionKey: this.#encryptionKey, options: this.options, }); this.#inboxId = result.inboxId; @@ -65,8 +76,12 @@ export class Client extends ClientWorkerClass { this.#isReady = true; } - static async create(address: string, options?: ClientOptions) { - const client = new Client(address, options); + static async create( + address: string, + encryptionKey: Uint8Array, + options?: ClientOptions, + ) { + const client = new Client(address, encryptionKey, options); await client.init(); return client; } @@ -103,6 +118,20 @@ export class Client extends ClientWorkerClass { return this.sendMessage("addSignature", { type, bytes }); } + async addScwSignature( + type: SignatureRequestType, + bytes: Uint8Array, + chainId: bigint, + blockNumber?: bigint, + ) { + return this.sendMessage("addScwSignature", { + type, + bytes, + chainId, + blockNumber, + }); + } + async applySignatures() { return this.sendMessage("applySignatures", undefined); } diff --git a/sdks/browser-sdk/src/WorkerClient.ts b/sdks/browser-sdk/src/WorkerClient.ts index 821a4cbf8..7e87ed0cc 100644 --- a/sdks/browser-sdk/src/WorkerClient.ts +++ b/sdks/browser-sdk/src/WorkerClient.ts @@ -23,9 +23,10 @@ export class WorkerClient { static async create( accountAddress: string, + encryptionKey: Uint8Array, options?: Omit, ) { - const client = await createClient(accountAddress, options); + const client = await createClient(accountAddress, encryptionKey, options); return new WorkerClient(client); } @@ -84,6 +85,15 @@ export class WorkerClient { return this.#client.addSignature(type, bytes); } + async addScwSignature( + type: SignatureRequestType, + bytes: Uint8Array, + chainId: bigint, + blockNumber?: bigint, + ) { + return this.#client.addScwSignature(type, bytes, chainId, blockNumber); + } + async applySignatures() { return this.#client.applySignatureRequests(); } diff --git a/sdks/browser-sdk/src/WorkerConversation.ts b/sdks/browser-sdk/src/WorkerConversation.ts index 8ea03208a..4d7309ba7 100644 --- a/sdks/browser-sdk/src/WorkerConversation.ts +++ b/sdks/browser-sdk/src/WorkerConversation.ts @@ -2,12 +2,12 @@ import type { ConsentState, Conversation, EncodedContent, + GroupMember, } from "@xmtp/wasm-bindings"; import { fromSafeListMessagesOptions, toSafeGroupMember, type SafeListMessagesOptions, - type WasmGroupMember, } from "@/utils/conversions"; import type { WorkerClient } from "@/WorkerClient"; @@ -73,13 +73,13 @@ export class WorkerConversation { get metadata() { const metadata = this.#group.groupMetadata(); return { - creatorInboxId: metadata.creator_inbox_id(), - conversationType: metadata.conversation_type(), + creatorInboxId: metadata.creatorInboxId(), + conversationType: metadata.conversationType(), }; } async members() { - const members = (await this.#group.listMembers()) as WasmGroupMember[]; + const members = (await this.#group.listMembers()) as GroupMember[]; return members.map((member) => toSafeGroupMember(member)); } diff --git a/sdks/browser-sdk/src/index.ts b/sdks/browser-sdk/src/index.ts index 67da20342..93c6b043c 100644 --- a/sdks/browser-sdk/src/index.ts +++ b/sdks/browser-sdk/src/index.ts @@ -1,9 +1,36 @@ export { Client } from "./Client"; export { Conversations } from "./Conversations"; export { Conversation } from "./Conversation"; -export * from "./DecodedMessage"; +export type { MessageDeliveryStatus, MessageKind } from "./DecodedMessage"; +export { DecodedMessage } from "./DecodedMessage"; export { Utils } from "./Utils"; export { ApiUrls } from "./constants"; export type * from "./types"; export * from "./utils/conversions"; -export type * from "@xmtp/wasm-bindings"; +export { + ConsentEntityType, + ConsentState, + ConversationType, + CreateGroupOptions, + DeliveryStatus, + GroupMembershipState, + EncodedContent, + GroupMember, + GroupMetadata, + GroupPermissions, + GroupMessageKind, + GroupPermissionsOptions, + InboxState, + Installation, + ListConversationsOptions, + ListMessagesOptions, + Message, + PermissionLevel, + PermissionPolicy, + PermissionPolicySet, + PermissionUpdateType, + SignatureRequestType, + SortDirection, + Consent, + ContentTypeId, +} from "@xmtp/wasm-bindings"; diff --git a/sdks/browser-sdk/src/types/clientEvents.ts b/sdks/browser-sdk/src/types/clientEvents.ts index f642c8192..e3ba164ec 100644 --- a/sdks/browser-sdk/src/types/clientEvents.ts +++ b/sdks/browser-sdk/src/types/clientEvents.ts @@ -38,6 +38,7 @@ export type ClientEvents = }; data: { address: string; + encryptionKey: Uint8Array; options?: ClientOptions; }; } @@ -78,6 +79,17 @@ export type ClientEvents = bytes: Uint8Array; }; } + | { + action: "addScwSignature"; + id: string; + result: undefined; + data: { + type: SignatureRequestType; + bytes: Uint8Array; + chainId: bigint; + blockNumber?: bigint; + }; + } | { action: "applySignatures"; id: string; diff --git a/sdks/browser-sdk/src/types/options.ts b/sdks/browser-sdk/src/types/options.ts index 6d016c3df..c4c1f5815 100644 --- a/sdks/browser-sdk/src/types/options.ts +++ b/sdks/browser-sdk/src/types/options.ts @@ -18,16 +18,6 @@ export type NetworkOptions = { apiUrl?: string; }; -/** - * Encryption options - */ -export type EncryptionOptions = { - /** - * Encryption key to use for the local DB - */ - encryptionKey?: Uint8Array; -}; - /** * Storage options */ @@ -47,13 +37,20 @@ export type ContentOptions = { export type OtherOptions = { /** - * Enable logging of events between the client and worker + * Enable structured JSON logging + */ + structuredLogging?: boolean; + /** + * Enable performance metrics + */ + performanceLogging?: boolean; + /** + * Logging level */ - enableLogging?: boolean; + loggingLevel?: "off" | "error" | "warn" | "info" | "debug" | "trace"; }; export type ClientOptions = NetworkOptions & - EncryptionOptions & StorageOptions & ContentOptions & OtherOptions; diff --git a/sdks/browser-sdk/src/utils/conversions.ts b/sdks/browser-sdk/src/utils/conversions.ts index 093d9c14f..1e9f32045 100644 --- a/sdks/browser-sdk/src/utils/conversions.ts +++ b/sdks/browser-sdk/src/utils/conversions.ts @@ -341,36 +341,12 @@ export type SafeGroupMember = { permissionLevel: PermissionLevel; }; -export class WasmGroupMember { - account_addresses: string[]; - consent_state: ConsentState; - inbox_id: string; - installation_ids: string[]; - permission_level: PermissionLevel; - - constructor( - inbox_id: string, - account_addresses: string[], - installation_ids: string[], - permission_level: PermissionLevel, - consent_state: ConsentState, - ) { - this.inbox_id = inbox_id; - this.account_addresses = account_addresses; - this.installation_ids = installation_ids; - this.permission_level = permission_level; - this.consent_state = consent_state; - } -} - -export const toSafeGroupMember = ( - member: WasmGroupMember, -): SafeGroupMember => ({ - accountAddresses: member.account_addresses, - consentState: member.consent_state, - inboxId: member.inbox_id, - installationIds: member.installation_ids, - permissionLevel: member.permission_level, +export const toSafeGroupMember = (member: GroupMember): SafeGroupMember => ({ + accountAddresses: member.accountAddresses, + consentState: member.consentState, + inboxId: member.inboxId, + installationIds: member.installationIds, + permissionLevel: member.permissionLevel, }); export const fromSafeGroupMember = (member: SafeGroupMember): GroupMember => diff --git a/sdks/browser-sdk/src/utils/createClient.ts b/sdks/browser-sdk/src/utils/createClient.ts index 068d59bb8..cd4ba6ab5 100644 --- a/sdks/browser-sdk/src/utils/createClient.ts +++ b/sdks/browser-sdk/src/utils/createClient.ts @@ -2,12 +2,14 @@ import init, { createClient as createWasmClient, generateInboxId, getInboxIdForAddress, + LogOptions, } from "@xmtp/wasm-bindings"; import { ApiUrls } from "@/constants"; import type { ClientOptions } from "@/types"; export const createClient = async ( accountAddress: string, + encryptionKey: Uint8Array, options?: Omit, ) => { // initialize WASM module @@ -25,11 +27,25 @@ export const createClient = async ( (await getInboxIdForAddress(host, accountAddress)) || generateInboxId(accountAddress); + const isLogging = + options && + (options.loggingLevel !== undefined || + options.structuredLogging || + options.performanceLogging); + return createWasmClient( host, inboxId, accountAddress, dbPath, - options?.encryptionKey, + encryptionKey, + undefined, + isLogging + ? new LogOptions( + options.structuredLogging ?? false, + options.performanceLogging ?? false, + options.loggingLevel, + ) + : undefined, ); }; diff --git a/sdks/browser-sdk/src/workers/client.ts b/sdks/browser-sdk/src/workers/client.ts index 49b1b8455..3e8c97324 100644 --- a/sdks/browser-sdk/src/workers/client.ts +++ b/sdks/browser-sdk/src/workers/client.ts @@ -56,8 +56,14 @@ self.onmessage = async (event: MessageEvent) => { * Client actions */ case "init": - client = await WorkerClient.create(data.address, data.options); - enableLogging = data.options?.enableLogging ?? false; + client = await WorkerClient.create( + data.address, + data.encryptionKey, + data.options, + ); + enableLogging = + data.options?.loggingLevel !== undefined && + data.options.loggingLevel !== "off"; postMessage({ id, action, @@ -115,6 +121,19 @@ self.onmessage = async (event: MessageEvent) => { result: undefined, }); break; + case "addScwSignature": + await client.addScwSignature( + data.type, + data.bytes, + data.chainId, + data.blockNumber, + ); + postMessage({ + id, + action, + result: undefined, + }); + break; case "applySignatures": await client.applySignatures(); postMessage({ diff --git a/sdks/browser-sdk/test/helpers.ts b/sdks/browser-sdk/test/helpers.ts index f95eed455..d52bd1eb3 100644 --- a/sdks/browser-sdk/test/helpers.ts +++ b/sdks/browser-sdk/test/helpers.ts @@ -11,6 +11,8 @@ import { sepolia } from "viem/chains"; import { Client } from "@/Client"; import type { ClientOptions } from "@/types"; +const testEncryptionKey = window.crypto.getRandomValues(new Uint8Array(32)); + export const createUser = () => { const key = generatePrivateKey(); const account = privateKeyToAccount(key); @@ -44,7 +46,7 @@ export const createClient = async (user: User, options?: ClientOptions) => { ...options, env: options?.env ?? "local", }; - return Client.create(user.account.address, { + return Client.create(user.account.address, testEncryptionKey, { ...opts, dbPath: `./test-${user.uuid}.db3`, }); diff --git a/yarn.lock b/yarn.lock index b170635c6..0928c6afa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3604,6 +3604,15 @@ __metadata: languageName: node linkType: hard +"@types/bl@npm:^5.1.4": + version: 5.1.4 + resolution: "@types/bl@npm:5.1.4" + dependencies: + bl: "npm:*" + checksum: 10/ba36d83893b1e2fee2106977c5de6437b6198398beed6b0bad2d4475a84f81a30f842ff9b63025d5a8aed223141c6a3264fa725ce1d98ece679638fd6a350ee0 + languageName: node + linkType: hard + "@types/bn.js@npm:*": version: 5.1.6 resolution: "@types/bn.js@npm:5.1.6" @@ -4569,7 +4578,7 @@ __metadata: "@xmtp/content-type-primitives": "npm:^1.0.1" "@xmtp/content-type-text": "npm:^1.0.0" "@xmtp/proto": "npm:^3.70.0" - "@xmtp/wasm-bindings": "npm:^0.0.3" + "@xmtp/wasm-bindings": "npm:^0.0.4" playwright: "npm:^1.48.1" rollup: "npm:^4.18.1" rollup-plugin-dts: "npm:^6.1.1" @@ -4820,6 +4829,7 @@ __metadata: "@noble/hashes": "npm:^1.4.0" "@open-frames/types": "npm:^0.1.1" "@rollup/plugin-typescript": "npm:^12.1.1" + "@types/bl": "npm:^5.1.4" "@xmtp/frames-client": "npm:^0.5.4" "@xmtp/proto": "npm:3.61.1" "@xmtp/xmtp-js": "npm:^12.1.0" @@ -4960,10 +4970,10 @@ __metadata: languageName: node linkType: hard -"@xmtp/wasm-bindings@npm:^0.0.3": - version: 0.0.3 - resolution: "@xmtp/wasm-bindings@npm:0.0.3" - checksum: 10/7d7faf7062cf7f08a5063a2d9a61c6eb3ded50343025b349f3a23b5136c983cb4a8d3f4407bfb5d5843fa163f8a53ed18cd5d0d58759f7ba46ee3f342fd70be8 +"@xmtp/wasm-bindings@npm:^0.0.4": + version: 0.0.4 + resolution: "@xmtp/wasm-bindings@npm:0.0.4" + checksum: 10/6edbb9cf0a116f75a2ac1a9cce730e3988672aa48a524933555747c2f2c88616dae758e45a0f341ed052bb6e84da758c026e61f6a425072f32ec151202bda442 languageName: node linkType: hard