-
Notifications
You must be signed in to change notification settings - Fork 43
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
Add 1:1 messaging to Browser SDK #703
Changes from all commits
6f9ac70
77f4e99
48368e6
ad0fa8e
c8a526f
0f64130
e4fc2f5
c8e14a7
8fe8d12
fe97cc5
04a8244
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -282,4 +282,10 @@ export class Conversation { | |
state, | ||
}); | ||
} | ||
|
||
async dmPeerInboxId() { | ||
return this.#client.sendMessage("getDmPeerInboxId", { | ||
id: this.#id, | ||
}); | ||
} | ||
Comment on lines
+286
to
+290
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Handler implementation verified but needs async support The client worker implements the "getDmPeerInboxId" case handler, but there's a mismatch in the implementation:
This could lead to timing issues in message handling. Locations needing attention:
🔗 Analysis chainVerify client message handler implementation. Let's ensure the client properly handles the "getDmPeerInboxId" action. 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Check if the client implements the getDmPeerInboxId message handler
# Test: Search for getDmPeerInboxId handler implementation
rg -A 5 "getDmPeerInboxId.*case"
Length of output: 34 Script: #!/bin/bash
# Let's try a broader search for getDmPeerInboxId implementation
rg -i "getDmPeerInboxId"
# Also search for message handler patterns in client implementation
rg -A 10 "sendMessage.*case|handleMessage.*case|onMessage.*case"
# Look for client class implementation
ast-grep --pattern 'class $_ {
$$$
sendMessage($$$) {
$$$
}
$$$
}'
Length of output: 416 Script: #!/bin/bash
# Let's check the implementation in the client worker
cat sdks/browser-sdk/src/workers/client.ts
Length of output: 18834 |
||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -28,6 +28,12 @@ export class Conversations { | |||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
async getDmByInboxId(inboxId: string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
return this.#client.sendMessage("getDmByInboxId", { | ||||||||||||||||||||||||||||||||||||||||||||||||||
inboxId, | ||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
async list(options?: SafeListConversationsOptions) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
const conversations = await this.#client.sendMessage("getConversations", { | ||||||||||||||||||||||||||||||||||||||||||||||||||
options, | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -39,6 +45,22 @@ export class Conversations { | |||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
async listGroups( | ||||||||||||||||||||||||||||||||||||||||||||||||||
options?: Omit<SafeListConversationsOptions, "conversation_type">, | ||||||||||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
return this.#client.sendMessage("getGroups", { | ||||||||||||||||||||||||||||||||||||||||||||||||||
options, | ||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
async listDms( | ||||||||||||||||||||||||||||||||||||||||||||||||||
options?: Omit<SafeListConversationsOptions, "conversation_type">, | ||||||||||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
return this.#client.sendMessage("getDms", { | ||||||||||||||||||||||||||||||||||||||||||||||||||
options, | ||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
async newGroup(accountAddresses: string[], options?: SafeCreateGroupOptions) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
const conversation = await this.#client.sendMessage("newGroup", { | ||||||||||||||||||||||||||||||||||||||||||||||||||
accountAddresses, | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
@@ -47,4 +69,12 @@ export class Conversations { | |||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
return new Conversation(this.#client, conversation.id, conversation); | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
async newDm(accountAddress: string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
const conversation = await this.#client.sendMessage("newDm", { | ||||||||||||||||||||||||||||||||||||||||||||||||||
accountAddress, | ||||||||||||||||||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
return new Conversation(this.#client, conversation.id, conversation); | ||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+73
to
+79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add return type, input validation, and documentation. Consider the following improvements:
+ /**
+ * Creates a new direct message conversation with the specified account.
+ * @param accountAddress The Ethereum address of the account to start a DM with
+ * @returns Promise<Conversation> The newly created DM conversation
+ * @throws {Error} If the account address is invalid
+ */
- async newDm(accountAddress: string) {
+ async newDm(accountAddress: string): Promise<Conversation> {
+ if (!accountAddress || !/^0x[a-fA-F0-9]{40}$/.test(accountAddress)) {
+ throw new Error('Invalid Ethereum address');
+ }
+
const conversation = await this.#client.sendMessage("newDm", {
accountAddress,
});
return new Conversation(this.#client, conversation.id, conversation);
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -84,7 +84,7 @@ export class WorkerClient { | |
return this.#client.addSignature(type, bytes); | ||
} | ||
|
||
async applySignaturesRequests() { | ||
async applySignatures() { | ||
return this.#client.applySignatureRequests(); | ||
} | ||
Comment on lines
+87
to
89
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Method naming inconsistency found across SDKs The verification reveals that
The change should be applied consistently across all SDK implementations to maintain API parity. 🔗 Analysis chainBreaking Change: Method rename requires attention. The renaming of Let's verify the impact and consistency: Consider either:
Here's the suggested fix for consistency: async applySignatures() {
- return this.#client.applySignatureRequests();
+ return this.#client.applySignatures();
} 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Description: Check for any remaining references to the old method name and verify consistency
# Test 1: Check for any remaining references to old method name
echo "Checking for references to 'applySignaturesRequests'..."
rg -i "applySignaturesRequests"
# Test 2: Check for all references to both old and new internal method name
echo "Checking for references to internal method 'applySignatureRequests'..."
rg "applySignatureRequests"
# Test 3: Check for all references to new method name
echo "Checking for references to new method 'applySignatures'..."
rg "applySignatures"
Length of output: 1735 |
||
|
||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -43,18 +43,50 @@ export class WorkerConversations { | |||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
getDmByInboxId(inboxId: string) { | ||||||||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||||||||
const group = this.#conversations.find_dm_by_target_inbox_id(inboxId); | ||||||||||||||||||||||||||||||||||||||||||||
return new WorkerConversation(this.#client, group); | ||||||||||||||||||||||||||||||||||||||||||||
} catch { | ||||||||||||||||||||||||||||||||||||||||||||
return undefined; | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
async list(options?: SafeListConversationsOptions) { | ||||||||||||||||||||||||||||||||||||||||||||
const groups = (await this.#conversations.list( | ||||||||||||||||||||||||||||||||||||||||||||
options ? fromSafeListConversationsOptions(options) : undefined, | ||||||||||||||||||||||||||||||||||||||||||||
)) as WasmGroup[]; | ||||||||||||||||||||||||||||||||||||||||||||
return groups.map((group) => new WorkerConversation(this.#client, group)); | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
async listGroups( | ||||||||||||||||||||||||||||||||||||||||||||
options?: Omit<SafeListConversationsOptions, "conversation_type">, | ||||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||||
const groups = (await this.#conversations.list_groups( | ||||||||||||||||||||||||||||||||||||||||||||
options ? fromSafeListConversationsOptions(options) : undefined, | ||||||||||||||||||||||||||||||||||||||||||||
)) as WasmGroup[]; | ||||||||||||||||||||||||||||||||||||||||||||
return groups.map((group) => new WorkerConversation(this.#client, group)); | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
async listDms( | ||||||||||||||||||||||||||||||||||||||||||||
options?: Omit<SafeListConversationsOptions, "conversation_type">, | ||||||||||||||||||||||||||||||||||||||||||||
) { | ||||||||||||||||||||||||||||||||||||||||||||
const groups = (await this.#conversations.list_dms( | ||||||||||||||||||||||||||||||||||||||||||||
options ? fromSafeListConversationsOptions(options) : undefined, | ||||||||||||||||||||||||||||||||||||||||||||
)) as WasmGroup[]; | ||||||||||||||||||||||||||||||||||||||||||||
return groups.map((group) => new WorkerConversation(this.#client, group)); | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+71
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider refactoring to reduce code duplication with listGroups. The implementation is nearly identical to + private async listByType(
+ type: 'groups' | 'dms',
+ options?: Omit<SafeListConversationsOptions, "conversation_type">,
+ ): Promise<WorkerConversation[]> {
+ const method = type === 'groups' ? 'list_groups' : 'list_dms';
+ const groups = (await this.#conversations[method](
+ options ? fromSafeListConversationsOptions(options) : undefined,
+ )) as WasmGroup[];
+ return groups.map((group) => new WorkerConversation(this.#client, group));
+ }
+ /**
+ * Lists all DM conversations.
+ * @param options - Filtering options for listing DMs (excluding conversation_type)
+ * @returns Promise<WorkerConversation[]> - Array of DM conversations
+ */
async listDms(
options?: Omit<SafeListConversationsOptions, "conversation_type">,
): Promise<WorkerConversation[]> {
- const groups = (await this.#conversations.list_dms(
- options ? fromSafeListConversationsOptions(options) : undefined,
- )) as WasmGroup[];
- return groups.map((group) => new WorkerConversation(this.#client, group));
+ return this.listByType('dms', options);
}
|
||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
async newGroup(accountAddresses: string[], options?: SafeCreateGroupOptions) { | ||||||||||||||||||||||||||||||||||||||||||||
const group = await this.#conversations.create_group( | ||||||||||||||||||||||||||||||||||||||||||||
accountAddresses, | ||||||||||||||||||||||||||||||||||||||||||||
options ? fromSafeCreateGroupOptions(options) : undefined, | ||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||
return new WorkerConversation(this.#client, group); | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
async newDm(accountAddress: string) { | ||||||||||||||||||||||||||||||||||||||||||||
const group = await this.#conversations.create_dm(accountAddress); | ||||||||||||||||||||||||||||||||||||||||||||
return new WorkerConversation(this.#client, group); | ||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+88
to
+91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling and input validation. The implementation should be more robust with these improvements: + /**
+ * Creates a new DM conversation with the specified account.
+ * @param accountAddress - The Ethereum address of the account to start a DM with
+ * @returns Promise<WorkerConversation> - The newly created DM conversation
+ * @throws Error if the account address is invalid or DM creation fails
+ */
- async newDm(accountAddress: string) {
+ async newDm(accountAddress: string): Promise<WorkerConversation> {
+ if (!accountAddress?.trim()) {
+ throw new Error("Account address is required");
+ }
+ try {
const group = await this.#conversations.create_dm(accountAddress);
return new WorkerConversation(this.#client, group);
+ } catch (error) {
+ throw new Error(`Failed to create DM: ${error.message}`);
+ }
} 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,10 @@ import { | |
WasmListMessagesOptions, | ||
type WasmConsentEntityType, | ||
type WasmConsentState, | ||
type WasmConversationType, | ||
type WasmDeliveryStatus, | ||
type WasmDirection, | ||
type WasmGroupMembershipState, | ||
type WasmGroupMessageKind, | ||
type WasmGroupPermissionsOptions, | ||
type WasmInboxState, | ||
|
@@ -141,6 +144,7 @@ export const toSafeMessage = (message: WasmMessage): SafeMessage => ({ | |
|
||
export type SafeListMessagesOptions = { | ||
delivery_status?: WasmDeliveryStatus; | ||
direction?: WasmDirection; | ||
limit?: bigint; | ||
sent_after_ns?: bigint; | ||
sent_before_ns?: bigint; | ||
|
@@ -150,6 +154,7 @@ export const toSafeListMessagesOptions = ( | |
options: WasmListMessagesOptions, | ||
): SafeListMessagesOptions => ({ | ||
delivery_status: options.delivery_status, | ||
direction: options.direction, | ||
limit: options.limit, | ||
sent_after_ns: options.sent_after_ns, | ||
sent_before_ns: options.sent_before_ns, | ||
|
@@ -163,9 +168,12 @@ export const fromSafeListMessagesOptions = ( | |
options.sent_after_ns, | ||
options.limit, | ||
options.delivery_status, | ||
options.direction, | ||
); | ||
|
||
export type SafeListConversationsOptions = { | ||
allowed_states?: WasmGroupMembershipState[]; | ||
conversation_type?: WasmConversationType; | ||
Comment on lines
+175
to
+176
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix asymmetric conversion in ListConversationsOptions. The conversion functions for
Update export const toSafeListConversationsOptions = (
options: WasmListConversationsOptions,
): SafeListConversationsOptions => ({
+ allowed_states: options.allowed_states,
+ conversation_type: options.conversation_type,
created_after_ns: options.created_after_ns,
created_before_ns: options.created_before_ns,
limit: options.limit,
}); Also applies to: 194-195 |
||
created_after_ns?: bigint; | ||
created_before_ns?: bigint; | ||
limit?: bigint; | ||
|
@@ -174,6 +182,8 @@ export type SafeListConversationsOptions = { | |
export const toSafeListConversationsOptions = ( | ||
options: WasmListConversationsOptions, | ||
): SafeListConversationsOptions => ({ | ||
allowed_states: options.allowed_states, | ||
conversation_type: options.conversation_type, | ||
created_after_ns: options.created_after_ns, | ||
created_before_ns: options.created_before_ns, | ||
limit: options.limit, | ||
|
@@ -183,6 +193,8 @@ export const fromSafeListConversationsOptions = ( | |
options: SafeListConversationsOptions, | ||
): WasmListConversationsOptions => | ||
new WasmListConversationsOptions( | ||
options.allowed_states, | ||
options.conversation_type, | ||
options.created_after_ns, | ||
options.created_before_ns, | ||
options.limit, | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -14,7 +14,12 @@ export const createClient = async ( | |||||||||||||||||||||
await init(); | ||||||||||||||||||||||
|
||||||||||||||||||||||
const host = options?.apiUrl ?? ApiUrls[options?.env ?? "dev"]; | ||||||||||||||||||||||
const dbPath = `xmtp-${options?.env ?? "dev"}-${accountAddress}.db3`; | ||||||||||||||||||||||
// TODO: add db path validation | ||||||||||||||||||||||
// - must end with .db3 | ||||||||||||||||||||||
// - must not contain invalid characters | ||||||||||||||||||||||
// - must not start with a dot | ||||||||||||||||||||||
const dbPath = | ||||||||||||||||||||||
options?.dbPath ?? `xmtp-${options?.env ?? "dev"}-${accountAddress}.db3`; | ||||||||||||||||||||||
Comment on lines
+21
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add validation for user-provided dbPath. The dbPath option should be validated to ensure it meets security requirements and has the correct extension. Add validation before using the dbPath: const dbPath =
options?.dbPath ?? `xmtp-${options?.env ?? "dev"}-${accountAddress}.db3`;
+ if (!dbPath.endsWith('.db3')) {
+ throw new Error('Database path must end with .db3 extension');
+ }
+ if (dbPath.includes('../') || dbPath.includes('..\\')) {
+ throw new Error('Database path cannot contain parent directory references');
+ } 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
const inboxId = | ||||||||||||||||||||||
(await getInboxIdForAddress(host, accountAddress)) || | ||||||||||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Codebase verification
Inconsistency found in method naming between Client and WorkerClient
The verification revealed an inconsistency in the implementation:
WorkerClient.ts
andClient.ts
in browser-sdk callapplySignatureRequests()
internallyapplySignatures()
in the public APIThis creates a mismatch between the internal implementation and public interface. The internal method should be renamed to match the public API.
Locations needing attention:
sdks/browser-sdk/src/WorkerClient.ts
: ChangeapplySignatureRequests()
toapplySignatures()
sdks/node-sdk/src/Client.ts
: ChangeapplySignatureRequests()
toapplySignatures()
🔗 Analysis chain
Verify the method rename across the codebase.
The rename from
applySignaturesRequests
toapplySignatures
improves clarity. However, we should ensure all call sites are updated.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
Length of output: 2912