Skip to content

Allow passing a backend into static methods#1724

Merged
neekolas merged 3 commits intomainfrom
02-25-use_provided_backend_in_static_methods
Mar 4, 2026
Merged

Allow passing a backend into static methods#1724
neekolas merged 3 commits intomainfrom
02-25-use_provided_backend_in_static_methods

Conversation

@neekolas
Copy link
Contributor

@neekolas neekolas commented Feb 25, 2026

Allowing passing a backend into static methods

  • Upgrades to a dev release of the Node and WASM bindings, which changes the way backend configuration is passed for all static methods
  • This deprecates the old way of passing configuration in to static methods Client.someStaticMethod(env, gatewayHost, ...) and replaces it with a new method that takes a backend. We don't break the old way, but if you use it you will see a deprecation warning in your IDE.
  • Makes some changes to the ClientOptions to allow callers to pass in a backend explicitly. This lets you re-use the same backend between static methods and instantiated clients like we do on mobile.
  • Deprecates the ApiUrls and moves those constants to Rust

Changes

Design Documentation

  • Add comprehensive design document outlining the migration strategy and backwards compatibility approach

Bindings Updates

  • Upgrade @xmtp/wasm-bindings from 1.9.1 to 1.10.0-dev.a2bdd0a
  • Upgrade @xmtp/node-bindings from 1.10.0-dev.55280fa to 1.10.0-dev.a2bdd0a
  • Fix method renames: findInboxIdByIdentifierfindInboxIdByIdentity, member management method updates
  • Update sendSyncRequest to accept ArchiveOptions and serverUrl parameters

New Backend Utilities

  • Add createBackend utility in both SDKs that wraps BackendBuilder with proper environment mapping
  • Export Backend, BackendBuilder, and createBackend from both SDK index files

Type System Refactoring

  • Move appVersion from OtherOptions to NetworkOptions
  • Extract DeviceSyncOptions containing historySyncUrl and disableDeviceSync
  • Update ClientOptions to union type: (NetworkOptions | { backend: Backend }) & DeviceSyncOptions & ...
  • Add runtime validation preventing simultaneous backend and NetworkOptions usage

Static Method Migration

  • Update all static methods to accept envOrBackend?: XmtpEnv | Backend parameter
  • Add resolveBackend helper that handles backwards compatibility
  • Deprecate env and gatewayHost parameters with JSDoc annotations
  • Methods updated:
    • Client.revokeInstallations
    • Client.fetchInboxStates
    • Client.canMessage
    • Client.isAddressAuthorized (node-sdk only)
    • Client.isInstallationAuthorized (node-sdk only)

Client Creation Updates

  • Replace createClient/createWasmClient with createClientWithBackend
  • Update getInboxIdForIdentifier to use Backend instead of raw host parameters
  • Migrate utility functions (inboxState, installations) to accept Backend

Testing

  • Add comprehensive tests for createBackend in both SDKs
  • Mock BackendBuilder.build() in node-sdk tests for Tokio runtime compatibility
  • Use real WASM bindings in browser-sdk tests
  • Update test helpers to work with new type system

The migration maintains full backwards compatibility - existing code using env and gatewayHost parameters continues to work unchanged, while new code can use the more flexible Backend approach.

Note

Allow passing a Backend instance into static Client methods across browser and node SDKs

  • Adds a createBackend utility (browser and node) that constructs a typed Backend from env/apiUrl/gatewayHost options, and exports it from both SDKs.
  • Overloads static methods (canMessage, fetchInboxStates, revokeInstallations, isAddressAuthorized, isInstallationAuthorized) to accept either a pre-built Backend or the legacy env+gatewayHost strings (now deprecated).
  • Adds a Client.env getter so callers can read the environment after initialization; the worker response for client.init now includes env.
  • Updates sendSyncRequest to require (options: ArchiveOptions, serverUrl: string); ConversationsNavbar resolves serverUrl from HistorySyncUrls[client.env].
  • Expands XmtpEnv to an explicit string union (local | dev | production | testnet-staging | testnet-dev | testnet | mainnet) and introduces DeviceSyncOptions split from NetworkOptions.
  • Risk: sendSyncRequest signature is breaking — callers that invoke it with no arguments will need to pass options and serverUrl.

Macroscope summarized 35cc13b.

@vercel
Copy link

vercel bot commented Feb 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
xmtp-chat-api-service Ready Ready Preview, Comment Mar 4, 2026 1:51am
xmtp-js-xmtp-chat Ready Ready Preview, Comment Mar 4, 2026 1:51am

Request Review

@changeset-bot
Copy link

changeset-bot bot commented Feb 25, 2026

⚠️ No Changeset found

Latest commit: 35cc13b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor Author

neekolas commented Feb 25, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

],
excludeDisappearingMessages: false,
},
serverUrl,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making these updates

Copy link
Contributor Author

neekolas commented Mar 4, 2026

Merge activity

  • Mar 4, 1:39 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Mar 4, 1:40 AM UTC: Graphite rebased this pull request as part of a merge.
  • Mar 4, 1:42 AM UTC: Graphite couldn't merge this PR because it was not satisfying all requirements (Failed CI: 'Typecheck', 'Typecheck', 'Typecheck', 'Lint', 'Prettier', 'Typecheck', 'Typecheck', 'Typecheck', 'Test', 'Test', 'Test', 'Test', 'Build', 'Build', 'Build', 'Build', 'Build', 'Vercel – xmtp-js-xmtp-chat').

@neekolas neekolas changed the base branch from bennycode/1.10 to graphite-base/1724 March 4, 2026 01:39
@neekolas neekolas changed the base branch from graphite-base/1724 to main March 4, 2026 01:39
neekolas and others added 2 commits March 4, 2026 01:40
Documents the plan to migrate SDK static methods and Client.create/build
to use the new Backend/BackendBuilder abstraction from the bindings,
while maintaining backwards compatibility via deprecated env/gatewayHost params.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update @xmtp/node-bindings and @xmtp/wasm-bindings from
1.10.0-dev.82e8ff4 to 1.10.0-dev.2334796 across node-sdk,
browser-sdk, and content-type-primitives.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
const inboxId = await getInboxIdForIdentifier(identifier, env);
const inboxId = await getInboxIdForIdentifier(backend, identifier);
canMessageMap.set(identifier.identifier.toLowerCase(), inboxId !== null);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High src/Client.ts:851

Client.canMessage uses inboxId !== null to check if an identity exists, but getInboxIdForIdentifier returns undefined when not found. Since undefined !== null is true, the method incorrectly reports that non-existent users can be messaged. Consider using inboxId != null or !!inboxId to handle both null and undefined.

-      canMessageMap.set(identifier.identifier.toLowerCase(), inboxId !== null);
+      canMessageMap.set(identifier.identifier.toLowerCase(), inboxId != null);
Also found in 1 other location(s)

sdks/browser-sdk/src/utils/createClient.ts:19

The hasBackend function incorrectly identifies an object as having a valid backend if the backend property exists but is set to undefined (e.g., { backend: undefined }). This occurs because the function relies solely on the in operator, which returns true for keys with undefined values.

This creates two bugs:

  1. If a caller passes { backend: undefined }, resolveBackend returns undefined instead of creating a default backend. This causes createClient to crash downstream when undefined is passed to getInboxIdForIdentifier.
  2. If a caller passes { backend: undefined, env: &#39;production&#39; }, the function incorrectly detects a conflict between a 'backend' and network options, throwing an error instead of creating the backend using the provided options.

Update hasBackend to check that options.backend is not null or undefined.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file sdks/node-sdk/src/Client.ts around line 851:

`Client.canMessage` uses `inboxId !== null` to check if an identity exists, but `getInboxIdForIdentifier` returns `undefined` when not found. Since `undefined !== null` is `true`, the method incorrectly reports that non-existent users can be messaged. Consider using `inboxId != null` or `!!inboxId` to handle both `null` and `undefined`.

Evidence trail:
sdks/node-sdk/src/Client.ts line 850: `canMessageMap.set(identifier.identifier.toLowerCase(), inboxId !== null);` - uses strict inequality with null. sdks/node-sdk/src/utils/inboxId.ts lines 15-19: `getInboxIdForIdentifier` returns result from `getInboxIdByIdentity`. sdks/node-sdk/test/inboxId.test.ts lines 20-25: Test explicitly named "should return `undefined` inbox ID for unregistered address" confirms the function returns `undefined` (not `null`) when identity not found.

Also found in 1 other location(s):
- sdks/browser-sdk/src/utils/createClient.ts:19 -- The `hasBackend` function incorrectly identifies an object as having a valid backend if the `backend` property exists but is set to `undefined` (e.g., `{ backend: undefined }`). This occurs because the function relies solely on the `in` operator, which returns true for keys with undefined values.

This creates two bugs:
1. If a caller passes `{ backend: undefined }`, `resolveBackend` returns `undefined` instead of creating a default backend. This causes `createClient` to crash downstream when `undefined` is passed to `getInboxIdForIdentifier`.
2. If a caller passes `{ backend: undefined, env: 'production' }`, the function incorrectly detects a conflict between a 'backend' and network options, throwing an error instead of creating the backend using the provided options.

Update `hasBackend` to check that `options.backend` is not null or undefined.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium src/Client.ts:843

Client.canMessage returns a Map where keys are the lowercased identifier strings (identifier.identifier.toLowerCase()). This breaks lookups when callers use the original casing — for example, a checksummed Ethereum address like 0xAbC stored as 0xabc will cause map.get("0xAbC") to return undefined instead of the correct boolean. Consider using the original identifier as the map key.

-    const canMessageMap = new Map<string, boolean>();
-    for (const identifier of identifiers) {
-      const inboxId = await getInboxIdForIdentifier(backend, identifier);
-      canMessageMap.set(identifier.identifier.toLowerCase(), inboxId !== null);
-    }
-    return canMessageMap;
+    const canMessageMap = new Map<string, boolean>();
+    for (const identifier of identifiers) {
+      const inboxId = await getInboxIdForIdentifier(backend, identifier);
+      canMessageMap.set(identifier.identifier, inboxId !== null);
+    }
+    return canMessageMap;
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file sdks/node-sdk/src/Client.ts around lines 843-854:

`Client.canMessage` returns a Map where keys are the lowercased identifier strings (`identifier.identifier.toLowerCase()`). This breaks lookups when callers use the original casing — for example, a checksummed Ethereum address like `0xAbC` stored as `0xabc` will cause `map.get("0xAbC")` to return `undefined` instead of the correct boolean. Consider using the original identifier as the map key.

Evidence trail:
sdks/node-sdk/src/Client.ts:850 - canMessageMap.set(identifier.identifier.toLowerCase(), inboxId !== null) shows the key is lowercased
sdks/node-sdk/test/helpers.ts:53-54 - createIdentifier explicitly lowercases: identifier: user.account.address.toLowerCase()
sdks/node-sdk/test/helpers.ts:71 - createSigner returns address: user.account.address.toLowerCase()
sdks/node-sdk/test/Client.test.ts:130-132 - Test uses already-lowercased addresses so doesn't catch the bug

Comment on lines +19 to +21
const hasBackend = (options: object): options is { backend: Backend } => {
return "backend" in options;
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium utils/createClient.ts:19

hasBackend returns true when backend is explicitly set to undefined, causing resolveBackend to return undefined instead of creating a backend. The caller in createClient then crashes when accessing backend.env. Consider checking that options.backend is truthy, not just that the key exists.

+const hasBackend = (options: object): options is { backend: Backend } => {
+  return "backend" in options && (options as Record<string, unknown>).backend !== undefined;
+};
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file sdks/browser-sdk/src/utils/createClient.ts around lines 19-21:

`hasBackend` returns true when `backend` is explicitly set to `undefined`, causing `resolveBackend` to return `undefined` instead of creating a backend. The caller in `createClient` then crashes when accessing `backend.env`. Consider checking that `options.backend` is truthy, not just that the key exists.

Evidence trail:
sdks/browser-sdk/src/utils/createClient.ts lines 18-20 (hasBackend uses `"backend" in options` which checks key existence), lines 36-38 (resolveBackend returns `options.backend` when hasBackend returns true), line 49 (createClient receives backend from resolveBackend), line 55 (crashes on `backend.env` when backend is undefined). Verified at commit REVIEWED_COMMIT.

@neekolas neekolas merged commit 7637a62 into main Mar 4, 2026
31 checks passed
@neekolas neekolas deleted the 02-25-use_provided_backend_in_static_methods branch March 4, 2026 06:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants