Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor signing in browser and node SDKs #718

Merged
merged 9 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
"ignore": ["@xmtp/react-vite-browser-sdk-example"]
}
6 changes: 6 additions & 0 deletions .changeset/lovely-swans-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@xmtp/browser-sdk": patch
"@xmtp/node-sdk": patch
---

Refactor signing in browser and node SDKs
34 changes: 11 additions & 23 deletions examples/react-vite-browser-sdk/src/createClient.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,25 @@
import { Client, SignatureRequestType } from "@xmtp/browser-sdk";
import { Client, type Signer } from "@xmtp/browser-sdk";
import { toBytes } from "viem/utils";
import { createWallet } from "./wallets";

type Wallet = ReturnType<typeof createWallet>;

export const getSignature = async (client: Client, wallet: Wallet) => {
const signatureText = await client.getCreateInboxSignatureText();
if (signatureText) {
const signature = await wallet.signMessage({
message: signatureText,
});
return toBytes(signature);
}
return null;
};

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, encryptionBytes, {
const signer: Signer = {
getAddress: () => wallet.account.address,
signMessage: async (message: string) => {
const signature = await wallet.signMessage({
message,
});
return toBytes(signature);
},
};
const client = await Client.create(signer, encryptionBytes, {
env: "local",
});
const isRegistered = await client.isRegistered();
if (!isRegistered) {
const signature = await getSignature(client, wallet);
if (signature) {
await client.addSignature(SignatureRequestType.CreateInbox, signature);
}
await client.registerIdentity();
}
return client;
};
191 changes: 146 additions & 45 deletions sdks/browser-sdk/src/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,34 @@ import type {
import { TextCodec } from "@xmtp/content-type-text";
import {
GroupMessageKind,
SignatureRequestType,
type ConsentEntityType,
type SignatureRequestType,
} from "@xmtp/wasm-bindings";
import { ClientWorkerClass } from "@/ClientWorkerClass";
import { Conversations } from "@/Conversations";
import type { ClientOptions } from "@/types";
import type { ClientOptions, XmtpEnv } from "@/types";
import {
fromSafeEncodedContent,
toSafeEncodedContent,
type SafeConsent,
type SafeMessage,
} from "@/utils/conversions";
import { isSmartContractSigner, type Signer } from "@/utils/signer";

export class Client extends ClientWorkerClass {
address: string;

options?: ClientOptions;

#isReady = false;

#inboxId: string | undefined;

#installationId: string | undefined;

#conversations: Conversations;

#accountAddress: string;
#codecs: Map<string, ContentCodec>;

#conversations: Conversations;
#encryptionKey: Uint8Array;
#inboxId: string | undefined;
#installationId: string | undefined;
#isReady = false;
#signer: Signer;
options?: ClientOptions;

constructor(
address: string,
signer: Signer,
accountAddress: string,
encryptionKey: Uint8Array,
options?: ClientOptions,
) {
Expand All @@ -51,9 +47,10 @@ export class Client extends ClientWorkerClass {
worker,
options?.loggingLevel !== undefined && options.loggingLevel !== "off",
);
this.address = address;
this.#accountAddress = accountAddress;
this.options = options;
this.#encryptionKey = encryptionKey;
this.#signer = signer;
this.#conversations = new Conversations(this);
const codecs = [
new GroupUpdatedCodec(),
Expand All @@ -65,9 +62,13 @@ export class Client extends ClientWorkerClass {
);
}

get accountAddress() {
return this.#accountAddress;
}

async init() {
const result = await this.sendMessage("init", {
address: this.address,
address: this.accountAddress,
encryptionKey: this.#encryptionKey,
options: this.options,
});
Expand All @@ -77,12 +78,19 @@ export class Client extends ClientWorkerClass {
}

static async create(
address: string,
signer: Signer,
encryptionKey: Uint8Array,
options?: ClientOptions,
) {
const client = new Client(address, encryptionKey, options);
const address = await signer.getAddress();
const client = new Client(signer, address, encryptionKey, options);

await client.init();

if (!options?.disableAutoRegister) {
await client.register();
}

return client;
}

Expand All @@ -98,48 +106,124 @@ export class Client extends ClientWorkerClass {
return this.#installationId;
}

async getCreateInboxSignatureText() {
return this.sendMessage("getCreateInboxSignatureText", undefined);
async #createInboxSignatureText() {
return this.sendMessage("createInboxSignatureText", undefined);
}

async getAddWalletSignatureText(accountAddress: string) {
return this.sendMessage("getAddWalletSignatureText", { accountAddress });
}

async getRevokeWalletSignatureText(accountAddress: string) {
return this.sendMessage("getRevokeWalletSignatureText", { accountAddress });
async #addAccountSignatureText(newAccountAddress: string) {
return this.sendMessage("addAccountSignatureText", {
newAccountAddress,
});
}

async getRevokeInstallationsSignatureText() {
return this.sendMessage("getRevokeInstallationsSignatureText", undefined);
async #removeAccountSignatureText(accountAddress: string) {
return this.sendMessage("removeAccountSignatureText", { accountAddress });
}

async addSignature(type: SignatureRequestType, bytes: Uint8Array) {
return this.sendMessage("addSignature", { type, bytes });
async #revokeInstallationsSignatureText() {
return this.sendMessage("revokeInstallationsSignatureText", undefined);
}

async addScwSignature(
type: SignatureRequestType,
bytes: Uint8Array,
chainId: bigint,
blockNumber?: bigint,
async #addSignature(
signatureType: SignatureRequestType,
signatureText: string,
signer: Signer,
) {
return this.sendMessage("addScwSignature", {
type,
bytes,
chainId,
blockNumber,
});
const signature = await signer.signMessage(signatureText);

if (isSmartContractSigner(signer)) {
await this.sendMessage("addScwSignature", {
type: signatureType,
bytes: signature,
chainId: signer.getChainId(),
blockNumber: signer.getBlockNumber(),
});
rygine marked this conversation as resolved.
Show resolved Hide resolved
} else {
await this.sendMessage("addSignature", {
type: signatureType,
bytes: signature,
});
}
}

async applySignatures() {
async #applySignatures() {
return this.sendMessage("applySignatures", undefined);
}

async registerIdentity() {
async register() {
const signatureText = await this.#createInboxSignatureText();

// if the signature text is not available, the client is already registered
if (!signatureText) {
return;
}

await this.#addSignature(
SignatureRequestType.CreateInbox,
signatureText,
this.#signer,
);

return this.sendMessage("registerIdentity", undefined);
}

async addAccount(newAccountSigner: Signer) {
const signatureText = await this.#addAccountSignatureText(
await newAccountSigner.getAddress(),
);

if (!signatureText) {
throw new Error("Unable to generate add account signature text");
}

await this.#addSignature(
SignatureRequestType.AddWallet,
signatureText,
this.#signer,
);

await this.#addSignature(
SignatureRequestType.AddWallet,
signatureText,
newAccountSigner,
);

await this.#applySignatures();
}

async removeAccount(accountAddress: string) {
const signatureText =
await this.#removeAccountSignatureText(accountAddress);

if (!signatureText) {
throw new Error("Unable to generate remove account signature text");
}

await this.#addSignature(
SignatureRequestType.RevokeWallet,
signatureText,
this.#signer,
);

await this.#applySignatures();
}

async revokeInstallations() {
const signatureText = await this.#revokeInstallationsSignatureText();

if (!signatureText) {
throw new Error("Unable to generate revoke installations signature text");
}

await this.#addSignature(
SignatureRequestType.RevokeInstallations,
signatureText,
this.#signer,
);

await this.#applySignatures();
}

async isRegistered() {
return this.sendMessage("isRegistered", undefined);
}
Expand All @@ -148,6 +232,23 @@ export class Client extends ClientWorkerClass {
return this.sendMessage("canMessage", { accountAddresses });
}

static async canMessage(accountAddresses: string[], env?: XmtpEnv) {
const accountAddress = "0x0000000000000000000000000000000000000000";
const signer: Signer = {
getAddress: () => accountAddress,
signMessage: () => new Uint8Array(),
};
rygine marked this conversation as resolved.
Show resolved Hide resolved
const client = await Client.create(
signer,
window.crypto.getRandomValues(new Uint8Array(32)),
{
disableAutoRegister: true,
env,
},
rygine marked this conversation as resolved.
Show resolved Hide resolved
);
return client.canMessage(accountAddresses);
}

async findInboxIdByAddress(address: string) {
return this.sendMessage("findInboxIdByAddress", { address });
}
Expand Down
8 changes: 4 additions & 4 deletions sdks/browser-sdk/src/WorkerClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ export class WorkerClient {
return this.#client.isRegistered;
}

async getCreateInboxSignatureText() {
async createInboxSignatureText() {
try {
return await this.#client.createInboxSignatureText();
} catch {
return undefined;
}
}

async getAddWalletSignatureText(accountAddress: string) {
async addAccountSignatureText(accountAddress: string) {
try {
return await this.#client.addWalletSignatureText(
this.#accountAddress,
Expand All @@ -65,15 +65,15 @@ export class WorkerClient {
}
}

async getRevokeWalletSignatureText(accountAddress: string) {
async removeAccountSignatureText(accountAddress: string) {
try {
return await this.#client.revokeWalletSignatureText(accountAddress);
} catch {
return undefined;
}
}

async getRevokeInstallationsSignatureText() {
async revokeInstallationsSignatureText() {
try {
return await this.#client.revokeInstallationsSignatureText();
} catch {
Expand Down
5 changes: 5 additions & 0 deletions sdks/browser-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ export {
Consent,
ContentTypeId,
} from "@xmtp/wasm-bindings";
export {
isSmartContractSigner,
type Signer,
type SmartContractSigner,
} from "./utils/signer";
Loading
Loading