@settlemint/dalp-sdk 3.1.19-main.36458349696 → 3.1.19-main.36472209091

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -35,10 +35,13 @@ mechanism, the acting-context headers (`organizationId`, `participant`,
35
35
  `If-None-Match` reuse, custom fetch injection, the settlement bound, and abort
36
36
  propagation.
37
37
 
38
- **An API key cannot reach a browser.** `createDalpClient` throws at
39
- construction when a key is passed with `runtime: "browser"`, rather than at the
40
- first request. A key carries the full authority of the identity it belongs to.
41
- In a browser the client authenticates with the session cookie instead.
38
+ **An API key cannot reach a browser or an app.** `createDalpClient` and
39
+ `createDalpAuthClient` throw at construction when a key is passed with
40
+ `runtime: "browser"` or `runtime: "react-native"`, rather than at the first
41
+ request. A key carries the
42
+ full authority of the identity it belongs to, and anything bundled into a web
43
+ page or an app ships to every user. There the client authenticates with the
44
+ session cookie instead.
42
45
 
43
46
  ### Dispatch by route id
44
47
 
@@ -151,8 +154,9 @@ import { createDalpAuthClient } from "@settlemint/dalp-sdk/auth";
151
154
 
152
155
  The route surface already carries every auth REST route. `./auth` covers what a
153
156
  REST call cannot express: the session and cookie state machine, plugin actions
154
- that must run in the browser (`passkeyClient` drives the WebAuthn ceremony
155
- through `navigator.credentials`), and the access-control model that evaluates a
157
+ that must run on the device (`passkeyClient` drives the WebAuthn ceremony
158
+ through `navigator.credentials`, or through the `passkey` ceremony a React
159
+ Native app passes), and the access-control model that evaluates a
156
160
  permission locally instead of round-tripping. It sits off the package root
157
161
  because it costs roughly 70 KiB of Better Auth that a consumer who only calls
158
162
  routes should not pay.
@@ -176,6 +180,37 @@ Every other auth route answers a request that carries a key with HTTP 403 and
176
180
  the code `API_KEY_NOT_SUPPORTED_ON_ROUTE`. Use a session cookie for those
177
181
  routes. The API key guide in the DALP API reference has the details.
178
182
 
183
+ ## React Native
184
+
185
+ Supported: Expo SDK 57 (React Native 0.86, React 19.2), with `fetch` from
186
+ `expo/fetch` passed to both clients. The SDK detects React Native from
187
+ `navigator.product` and then keeps the session in its own cookie store, sends
188
+ `credentials: "omit"`, refuses an API key, and sends the platform's origin as
189
+ `Origin` on every call.
190
+
191
+ ```ts
192
+ import { createCookieStoreFetch, createDalpClient, makeCookieStore } from "@settlemint/dalp-sdk";
193
+ import { createDalpAuthClient } from "@settlemint/dalp-sdk/auth";
194
+ import { fetch } from "expo/fetch";
195
+
196
+ const cookieStore = makeCookieStore((await SecureStore.getItemAsync("dalp.session")) ?? "");
197
+ const auth = await createDalpAuthClient({ url, cookieStore, fetch, passkey: ceremony });
198
+ const dalp = createDalpClient({ baseUrl: url, fetch: createCookieStoreFetch(cookieStore, fetch) });
199
+ ```
200
+
201
+ - React Native's built-in `fetch` works for calls but cannot read a stream; a
202
+ stream on it fails with `sdk.stream.UNREADABLE_BODY` before the request is
203
+ sent. Use `expo/fetch`.
204
+ - React Native has no WebAuthn. Pass a `passkey` ceremony (`DalpPasskeyCeremony`:
205
+ `authenticate` and `register`, each taking the platform's options JSON and
206
+ returning the credential JSON) backed by a native passkey module.
207
+ - Persist `cookieStore.header()` in secure storage yourself; the SDK holds it in
208
+ memory only.
209
+
210
+ The SDK's tests run this wiring headless on the fetch and abort polyfills React
211
+ Native ships and a model of `expo/fetch`. They do not run on Hermes, a
212
+ simulator, or a device.
213
+
179
214
  ## Bundler and paymaster
180
215
 
181
216
  Use JSON-RPC 2.0 over `POST https://dalp.example.com/bundler/{chainId}` for
@@ -233,5 +268,5 @@ calls routes never pays for auth, TanStack Query, or webhook code.
233
268
 
234
269
  The integrator guide lives under Developer reference in the platform docs:
235
270
  install and credentials, calling routes, mutations and settlement, streaming,
236
- TanStack Query, server rendering in TanStack Start and Next.js, and the Effect
237
- surface.
271
+ TanStack Query, server rendering in TanStack Start and Next.js, React Native,
272
+ and the Effect surface.
@@ -3,6 +3,8 @@ import type { DalpAuthSurface } from "../auth-surface.ts";
3
3
  import type { AuthGetApiKeyListResponse, AuthGetOrganizationListResponse, AuthPostApiKeyCreateData, AuthPostApiKeyCreateResponse, AuthPostApiKeyDeleteData, AuthPostApiKeyDeleteResponse, AuthPostOrganizationCreateData, AuthPostOrganizationCreateResponse, AuthPostOrganizationRemoveMemberData, AuthPostOrganizationRemoveMemberResponse, AuthPostRevokeOtherSessionsResponse, AuthPostRevokeSessionData, AuthPostRevokeSessionResponse, AuthPostTwoFactorDisableData, AuthPostTwoFactorDisableResponse, AuthPostTwoFactorEnableData, AuthPostTwoFactorEnableResponse, AuthPostTwoFactorVerifyBackupCodeData, AuthPostTwoFactorVerifyBackupCodeResponse, AuthPostTwoFactorVerifyTotpData, AuthPostTwoFactorVerifyTotpResponse, CreateOrganizationInvitationData, CreateOrganizationInvitationResponse, GetSessionResponse, ListUserSessionsResponse, Member, SetActiveOrganizationData, SetActiveOrganizationResponse } from "../generated/api-types.d.ts";
4
4
  import { Secret } from "../secret.ts";
5
5
  import type { CookieStoreShape } from "../transport/cookie-store.ts";
6
+ import type { DalpRuntime } from "../runtime.ts";
7
+ import type { DalpPasskeyCeremony } from "./native-passkey.ts";
6
8
  export interface DalpAuthClientConfig {
7
9
  readonly url: string;
8
10
  readonly apiKey?: string;
@@ -12,6 +14,8 @@ export interface DalpAuthClientConfig {
12
14
  readonly authSurface?: DalpAuthSurface;
13
15
  readonly headers?: Record<string, string>;
14
16
  readonly fetch?: DalpFetch;
17
+ readonly runtime?: DalpRuntime;
18
+ readonly passkey?: DalpPasskeyCeremony;
15
19
  }
16
20
  export interface DecodedDalpAuthClientConfig extends Omit<DalpAuthClientConfig, "apiKey" | "cookie"> {
17
21
  readonly apiKey?: Secret;
@@ -0,0 +1,9 @@
1
+ import { passkeyClient, type AuthenticationResponseJSON, type PublicKeyCredentialCreationOptionsJSON, type PublicKeyCredentialRequestOptionsJSON, type RegistrationResponseJSON } from "@better-auth/passkey/client";
2
+ export interface DalpPasskeyCeremony {
3
+ readonly authenticate: (options: PublicKeyCredentialRequestOptionsJSON) => Promise<AuthenticationResponseJSON>;
4
+ readonly register: (options: PublicKeyCredentialCreationOptionsJSON) => Promise<RegistrationResponseJSON>;
5
+ }
6
+ type PasskeyClientPlugin = ReturnType<typeof passkeyClient>;
7
+ /** @internal */
8
+ export declare function ceremonyPasskeyClient(ceremony: DalpPasskeyCeremony): PasskeyClientPlugin;
9
+ export {};
package/dist/auth.d.ts CHANGED
@@ -3,4 +3,6 @@ export type { DalpAuthClient } from "./auth/live.ts";
3
3
  export { decodeDalpAuthClientConfig } from "./auth/client.ts";
4
4
  export type { DalpAuthSurface } from "./auth-surface.ts";
5
5
  export type { DalpAuthClientConfig, DalpCliAuthClient, DalpAuthError, DalpAuthOperation, DalpAuthResult, DalpOrganizationInvitation, DalpOrganizationInvitationsQuery, DalpOrganizationMember, DalpOrganizationMembersQuery, DecodedDalpAuthClientConfig, } from "./auth/client.ts";
6
+ export type { DalpPasskeyCeremony } from "./auth/native-passkey.ts";
7
+ export type { DalpRuntime } from "./runtime.ts";
6
8
  export type { WalletAuthEndpointPath } from "./auth/wallet-plugins.ts";
package/dist/auth.js CHANGED
@@ -6,20 +6,22 @@ import {
6
6
  secret2,
7
7
  unwrapSecret,
8
8
  normalizeDalpBaseUrl,
9
+ detectDalpRuntime,
10
+ refuseBundledApiKey,
9
11
  STEP_UP_CHALLENGE_HEADER,
10
12
  STEP_UP_CHALLENGE_PATH,
11
13
  carriesWalletProof,
12
14
  stepUpChallengeRequest
13
- } from "./chunk-wp4269cm.js";
15
+ } from "./chunk-4f4kjtt0.js";
14
16
  import {
15
17
  makeCookieStore2,
16
18
  createCookieStoreFetch2,
17
19
  SDK_USER_AGENT2
18
- } from "./chunk-3mxexapc.js";
20
+ } from "./chunk-fwypx1xe.js";
19
21
 
20
22
  // src/auth/live.ts
21
23
  import { apiKeyClient } from "@better-auth/api-key/client";
22
- import { passkeyClient } from "@better-auth/passkey/client";
24
+ import { passkeyClient as passkeyClient2 } from "@better-auth/passkey/client";
23
25
  import {
24
26
  createAuthClient as createBetterAuthClient
25
27
  } from "better-auth/client";
@@ -101,6 +103,21 @@ function optionalFetch(value) {
101
103
  }
102
104
  return value;
103
105
  }
106
+ function optionalRuntime(value) {
107
+ if (value === undefined || value === "browser" || value === "server" || value === "react-native") {
108
+ return value;
109
+ }
110
+ throw new AuthConfigError("runtime", 'must be "browser", "server", or "react-native"');
111
+ }
112
+ function optionalPasskeyCeremony(value) {
113
+ if (value === undefined) {
114
+ return;
115
+ }
116
+ if (typeof value !== "object" || value === null || typeof Reflect.get(value, "authenticate") !== "function" || typeof Reflect.get(value, "register") !== "function") {
117
+ throw new AuthConfigError("passkey", "must expose authenticate and register");
118
+ }
119
+ return value;
120
+ }
104
121
  function optionalCookieStore(value) {
105
122
  if (value === undefined) {
106
123
  return;
@@ -124,6 +141,8 @@ function decodeAuthConfig(input) {
124
141
  const authSurface = optionalAuthSurface(raw.authSurface);
125
142
  const headers = optionalHeaders(raw.headers);
126
143
  const configuredFetch = optionalFetch(raw.fetch);
144
+ const runtime = optionalRuntime(raw.runtime);
145
+ const passkey = optionalPasskeyCeremony(raw.passkey);
127
146
  return {
128
147
  url,
129
148
  ...apiKey === undefined ? {} : { apiKey: secret2(apiKey) },
@@ -132,7 +151,9 @@ function decodeAuthConfig(input) {
132
151
  ...organizationId === undefined ? {} : { organizationId },
133
152
  ...authSurface === undefined ? {} : { authSurface },
134
153
  ...headers === undefined ? {} : { headers },
135
- ...configuredFetch === undefined ? {} : { fetch: configuredFetch }
154
+ ...configuredFetch === undefined ? {} : { fetch: configuredFetch },
155
+ ...runtime === undefined ? {} : { runtime },
156
+ ...passkey === undefined ? {} : { passkey }
136
157
  };
137
158
  }
138
159
  async function decodeDalpAuthClientConfig(input) {
@@ -141,8 +162,8 @@ async function decodeDalpAuthClientConfig(input) {
141
162
  } catch (error) {
142
163
  throw createDalpSdkConfigurationError2({
143
164
  message: "DALP SDK: auth client configuration is invalid.",
144
- why: "The auth client received a malformed URL, credential, header map, fetch seam, or cookie store.",
145
- fix: "Pass one credential source, an HTTP(S) URL, and valid fetch and cookie-store seams.",
165
+ why: "The auth client received a malformed URL, credential, header map, runtime, fetch seam, cookie store, or passkey ceremony.",
166
+ fix: "Pass one credential source, an HTTP(S) URL, a known runtime, and valid fetch, cookie-store, and passkey seams.",
146
167
  cause: error
147
168
  });
148
169
  }
@@ -191,6 +212,106 @@ function withInviteStepUp(fetchImpl, baseUrl) {
191
212
  };
192
213
  }
193
214
 
215
+ // src/auth/native-passkey.ts
216
+ import {
217
+ passkeyClient
218
+ } from "@better-auth/passkey/client";
219
+ var ceremonyFailed = (code, error) => ({
220
+ data: null,
221
+ error: {
222
+ code,
223
+ message: error instanceof Error ? error.message : "The passkey ceremony did not complete.",
224
+ status: 400,
225
+ statusText: "BAD_REQUEST"
226
+ }
227
+ });
228
+ function withExtensions(options, extensions) {
229
+ return options.extensions === undefined && extensions === undefined ? options : { ...options, extensions: { ...options.extensions, ...extensions } };
230
+ }
231
+ function ceremonyPasskeyClient(ceremony) {
232
+ const plugin = passkeyClient();
233
+ return {
234
+ ...plugin,
235
+ getActions: ($fetch, $store) => {
236
+ const signIn = async (opts, options) => {
237
+ const generated = await $fetch("/passkey/generate-authenticate-options", {
238
+ method: "GET",
239
+ throw: false
240
+ });
241
+ if (generated.data === null) {
242
+ return generated;
243
+ }
244
+ let response;
245
+ try {
246
+ response = await ceremony.authenticate(withExtensions(generated.data, opts?.extensions));
247
+ } catch (error) {
248
+ return ceremonyFailed("AUTH_CANCELLED", error);
249
+ }
250
+ const { clientExtensionResults, ...responseBody } = response;
251
+ try {
252
+ const verified = await $fetch("/passkey/verify-authentication", {
253
+ body: { response: responseBody },
254
+ ...opts?.fetchOptions,
255
+ ...options,
256
+ method: "POST",
257
+ throw: false
258
+ });
259
+ $store.notify("$listPasskeys");
260
+ $store.notify("$sessionSignal");
261
+ return opts?.returnWebAuthnResponse === true ? { ...verified, webauthn: { response, clientExtensionResults } } : verified;
262
+ } catch (error) {
263
+ return ceremonyFailed("AUTH_CANCELLED", error);
264
+ }
265
+ };
266
+ const addPasskey = async (opts, fetchOpts) => {
267
+ const generated = await $fetch("/passkey/generate-register-options", {
268
+ method: "GET",
269
+ query: {
270
+ ...opts?.authenticatorAttachment === undefined ? {} : { authenticatorAttachment: opts.authenticatorAttachment },
271
+ ...opts?.name === undefined || opts.name === "" ? {} : { name: opts.name },
272
+ ...opts?.context === undefined || opts.context === null ? {} : { context: opts.context }
273
+ },
274
+ throw: false
275
+ });
276
+ if (generated.data === null) {
277
+ return generated;
278
+ }
279
+ let response;
280
+ try {
281
+ response = await ceremony.register(withExtensions(generated.data, opts?.extensions));
282
+ } catch (error) {
283
+ return ceremonyFailed("REGISTRATION_CANCELLED", error);
284
+ }
285
+ const { clientExtensionResults, ...responseBody } = response;
286
+ try {
287
+ const verified = await $fetch("/passkey/verify-registration", {
288
+ ...opts?.fetchOptions,
289
+ ...fetchOpts,
290
+ body: {
291
+ response: responseBody,
292
+ name: opts?.name,
293
+ ...opts?.createSession === true ? { createSession: true } : {}
294
+ },
295
+ method: "POST",
296
+ throw: false
297
+ });
298
+ if (verified.data === null) {
299
+ return verified;
300
+ }
301
+ $store.notify("$listPasskeys");
302
+ if (verified.data.session !== undefined) {
303
+ $store.notify("$sessionSignal");
304
+ }
305
+ return opts?.returnWebAuthnResponse === true ? { ...verified, webauthn: { response, clientExtensionResults } } : verified;
306
+ } catch (error) {
307
+ return ceremonyFailed("UNKNOWN_ERROR", error);
308
+ }
309
+ };
310
+ return { signIn: { passkey: signIn }, passkey: { addPasskey }, $Infer: {} };
311
+ }
312
+ };
313
+ }
314
+
194
315
  // src/auth/permissions.ts
195
316
  import { createAccessControl } from "better-auth/plugins/access";
196
317
  import { adminAc, defaultStatements as adminDefaultStatements } from "better-auth/plugins/admin/access";
@@ -330,11 +451,14 @@ var organizationPluginOptions = {
330
451
  roles: { owner: ownerRole, member: memberRole }
331
452
  };
332
453
  var reservedHeaders = new Set(["x-api-key", "authorization", "cookie"]);
333
- function authHeaders(config) {
454
+ function authHeaders(config, runtime, baseUrl) {
334
455
  const headers = { "User-Agent": SDK_USER_AGENT2 };
456
+ if (runtime === "react-native") {
457
+ headers.origin = new URL(baseUrl).origin;
458
+ }
335
459
  for (const [name, value] of Object.entries(config.headers ?? {})) {
336
460
  if (!reservedHeaders.has(name.toLowerCase())) {
337
- headers[name] = value;
461
+ headers[name.toLowerCase() === "origin" ? "origin" : name] = value;
338
462
  }
339
463
  }
340
464
  if (config.apiKey !== undefined) {
@@ -354,7 +478,7 @@ function createConfiguredBetterAuthClient(config, cookieStore) {
354
478
  inferAdditionalFields(),
355
479
  adminClient(adminPluginOptions),
356
480
  apiKeyClient(),
357
- passkeyClient(),
481
+ config.passkey === undefined ? passkeyClient2() : ceremonyPasskeyClient(config.passkey),
358
482
  walletPincodeClient(),
359
483
  walletTwoFactorClient(),
360
484
  walletSecretCodesClient(),
@@ -363,12 +487,16 @@ function createConfiguredBetterAuthClient(config, cookieStore) {
363
487
  organizationClient(organizationPluginOptions)
364
488
  ];
365
489
  const baseUrl = normalizeDalpBaseUrl(config.url);
490
+ const runtime = config.runtime ?? detectDalpRuntime();
491
+ if (config.apiKey !== undefined) {
492
+ refuseBundledApiKey(runtime);
493
+ }
366
494
  return createBetterAuthClient({
367
495
  baseURL: `${baseUrl}/api/auth`,
368
496
  fetchOptions: {
369
- headers: authHeaders(config),
497
+ headers: authHeaders(config, runtime, baseUrl),
370
498
  customFetchImpl: withInviteStepUp(createCookieStoreFetch2(cookieStore, config.fetch), baseUrl),
371
- credentials: "include"
499
+ credentials: runtime === "react-native" ? "omit" : "include"
372
500
  },
373
501
  plugins
374
502
  });
@@ -63,6 +63,35 @@ function normalizeDalpBaseUrl(url) {
63
63
  return `${parsed.origin}${parsed.pathname.replace(/\/+$/, "")}`;
64
64
  }
65
65
 
66
+ // src/runtime.ts
67
+ var REACT_NATIVE_NAVIGATOR_PRODUCT = "ReactNative";
68
+ function detectDalpRuntime() {
69
+ if (Reflect.get(globalThis, "navigator") !== undefined && Reflect.get(navigator, "product") === REACT_NATIVE_NAVIGATOR_PRODUCT) {
70
+ return "react-native";
71
+ }
72
+ return globalThis.window === undefined ? "server" : "browser";
73
+ }
74
+ function runtimeCredentials(runtime) {
75
+ return runtime === "browser" ? "include" : "omit";
76
+ }
77
+ var WHATWG_FETCH_POLYFILL_FLAG = "polyfill";
78
+ function isBufferingFetch(fetchImpl) {
79
+ return typeof fetchImpl === "function" && Reflect.get(fetchImpl, WHATWG_FETCH_POLYFILL_FLAG) === true;
80
+ }
81
+ function keepBufferingMarker(wrapped, wrapper) {
82
+ return isBufferingFetch(wrapped) ? Object.assign(wrapper, { [WHATWG_FETCH_POLYFILL_FLAG]: true }) : wrapper;
83
+ }
84
+ function refuseBundledApiKey(runtime) {
85
+ if (runtime === "server") {
86
+ return;
87
+ }
88
+ throw createDalpSdkConfigurationError2({
89
+ message: `DALP SDK: API keys are server/CLI-only credentials and cannot configure a ${runtime} client.`,
90
+ why: "An API key placed in a browser or app bundle ships the credential to every visitor or device.",
91
+ fix: "Keep the API key on the server or CLI process; browser and app clients authenticate with the session cookie."
92
+ });
93
+ }
94
+
66
95
  // ../kernel/json/src/step-up-payload-text.ts
67
96
  var PROOF_FIELD = "walletVerification";
68
97
  var isAbsent = (value) => value === undefined || typeof value === "function" || typeof value === "symbol";
@@ -99,12 +128,12 @@ var canonicalText = (value, omitted = null) => {
99
128
  var stepUpPayloadText = (body) => canonicalText(body, PROOF_FIELD);
100
129
 
101
130
  // src/client/step-up-challenge.ts
131
+ import { sha256, stringToBytes } from "viem/utils";
102
132
  var STEP_UP_CHALLENGE_HEADER = "X-Step-Up-Challenge";
103
133
  var WALLET_VERIFICATION_HEADER = "X-Wallet-Verification";
104
134
  var STEP_UP_CHALLENGE_PATH = "/api/v2/step-up-challenges";
105
135
  var PROOF_FIELD2 = "walletVerification";
106
- var HEX_RADIX = 16;
107
- var HEX_BYTE_WIDTH = 2;
136
+ var HEX_PREFIX = "0x";
108
137
  function carriesWalletProof(body) {
109
138
  if (body === null || typeof body !== "object" || Array.isArray(body)) {
110
139
  return false;
@@ -113,9 +142,8 @@ function carriesWalletProof(body) {
113
142
  return proof !== null && proof !== undefined;
114
143
  }
115
144
  async function stepUpChallengeRequest(operation, target, body) {
116
- const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(stepUpPayloadText(body)));
117
- const payloadDigest = Array.from(new Uint8Array(digest), (byte) => byte.toString(HEX_RADIX).padStart(HEX_BYTE_WIDTH, "0")).join("");
145
+ const payloadDigest = sha256(stringToBytes(stepUpPayloadText(body))).slice(HEX_PREFIX.length);
118
146
  return { operation, target, payloadDigest };
119
147
  }
120
148
 
121
- export { DALP_AUTH_SURFACE_HEADER2, SECRET_REDACTION_MARKER2, Secret2, secret2, unwrapSecret, normalizeDalpBaseUrl, STEP_UP_CHALLENGE_HEADER, WALLET_VERIFICATION_HEADER, STEP_UP_CHALLENGE_PATH, carriesWalletProof, stepUpChallengeRequest };
149
+ export { DALP_AUTH_SURFACE_HEADER2, SECRET_REDACTION_MARKER2, Secret2, secret2, unwrapSecret, normalizeDalpBaseUrl, detectDalpRuntime, runtimeCredentials, isBufferingFetch, keepBufferingMarker, refuseBundledApiKey, STEP_UP_CHALLENGE_HEADER, WALLET_VERIFICATION_HEADER, STEP_UP_CHALLENGE_PATH, carriesWalletProof, stepUpChallengeRequest };
@@ -1,3 +1,7 @@
1
+ import {
2
+ keepBufferingMarker
3
+ } from "./chunk-4f4kjtt0.js";
4
+
1
5
  // src/transport/cookie-store.ts
2
6
  var parseCookieHeader = (header) => {
3
7
  const cookies = new Map;
@@ -87,7 +91,7 @@ var splitCombinedSetCookie = (value) => {
87
91
  return cookies;
88
92
  };
89
93
  function createCookieStoreFetch2(cookieStore, fetchImpl = globalThis.fetch) {
90
- return async (input, init) => {
94
+ const cookieStoreFetch = async (input, init) => {
91
95
  const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : undefined));
92
96
  if (!cookieStore.isEmpty() && !headers.has("cookie")) {
93
97
  headers.set("cookie", cookieStore.header());
@@ -98,11 +102,12 @@ function createCookieStoreFetch2(cookieStore, fetchImpl = globalThis.fetch) {
98
102
  }
99
103
  return response;
100
104
  };
105
+ return keepBufferingMarker(fetchImpl, cookieStoreFetch);
101
106
  }
102
107
  // package.json
103
108
  var package_default = {
104
109
  name: "@settlemint/dalp-sdk",
105
- version: "3.1.19-main.36458349696",
110
+ version: "3.1.19-main.36472209091",
106
111
  private: false,
107
112
  description: "Fully typed SDK for the DALP tokenization platform API",
108
113
  homepage: "https://settlemint.com",
@@ -229,10 +234,12 @@ var package_default = {
229
234
  "@tools/typescript-config": "workspace:*",
230
235
  "@tools/vitest-config": "workspace:*",
231
236
  "@types/bun": "1.4.2",
237
+ "abort-controller": "3.0.0",
232
238
  effect: "4.0.0-rc.117",
233
239
  publint: "0.3.24",
234
240
  typescript: "7.0.2",
235
241
  vitest: "5.0.1",
242
+ "whatwg-fetch": "3.6.20",
236
243
  zod: "4.6.5"
237
244
  },
238
245
  peerDependencies: {
@@ -4,7 +4,7 @@ import {
4
4
  sendRequest,
5
5
  createFetchExecutor,
6
6
  createTanstackFactories
7
- } from "./chunk-yr7kp37j.js";
7
+ } from "./chunk-szp73yj8.js";
8
8
  import {
9
9
  buildProxy,
10
10
  findRoute
@@ -1,6 +1,5 @@
1
1
  import {
2
- DalpSdkError2,
3
- createDalpSdkConfigurationError2
2
+ DalpSdkError2
4
3
  } from "./chunk-k3ntysxx.js";
5
4
  import {
6
5
  DALP_AUTH_SURFACE_HEADER2,
@@ -8,12 +7,16 @@ import {
8
7
  secret2,
9
8
  unwrapSecret,
10
9
  normalizeDalpBaseUrl,
10
+ detectDalpRuntime,
11
+ runtimeCredentials,
12
+ isBufferingFetch,
13
+ refuseBundledApiKey,
11
14
  STEP_UP_CHALLENGE_HEADER,
12
15
  WALLET_VERIFICATION_HEADER,
13
16
  STEP_UP_CHALLENGE_PATH,
14
17
  carriesWalletProof,
15
18
  stepUpChallengeRequest
16
- } from "./chunk-wp4269cm.js";
19
+ } from "./chunk-4f4kjtt0.js";
17
20
 
18
21
  // src/errors/find-current-error.ts
19
22
  var readProperty = (target, key) => Reflect.get(target, key);
@@ -142,6 +145,7 @@ var SDK_LOCAL_ERROR_CODES = new Set([
142
145
  "sdk.transport.UNVERIFIED_ERROR_CODE",
143
146
  "sdk.response.MALFORMED_SUCCESS",
144
147
  "sdk.stream.FAILED",
148
+ "sdk.stream.UNREADABLE_BODY",
145
149
  "sdk.scalar.BIG_DECIMAL_INVALID",
146
150
  "sdk.scalar.EVM_ADDRESS_INVALID",
147
151
  "sdk.scalar.ORGANIZATION_ID_INVALID",
@@ -519,10 +523,10 @@ async function captureSuccessfulGet(store, key, response) {
519
523
  store.delete(key);
520
524
  return response;
521
525
  }
522
- const body = new Uint8Array(await response.arrayBuffer());
526
+ const body = await response.blob();
523
527
  const headers = new Headers(response.headers);
524
528
  headers.set("etag", etag);
525
- if (body.byteLength <= CONDITIONAL_RESPONSE_MAX_BODY_BYTES) {
529
+ if (body.size <= CONDITIONAL_RESPONSE_MAX_BODY_BYTES) {
526
530
  remember(store, key, { body, etag, headers });
527
531
  }
528
532
  return new Response(body, {
@@ -570,7 +574,7 @@ function withConditionalRequests(base) {
570
574
  const retried = await base(requestWithoutIfNoneMatch(sent));
571
575
  return retried.status === 304 ? retried : captureSuccessfulGet(state.store, key, retried);
572
576
  }
573
- return new Response(new Uint8Array(entry.body), { status: 200, statusText: "OK", headers: entry.headers });
577
+ return new Response(entry.body, { status: 200, statusText: "OK", headers: entry.headers });
574
578
  };
575
579
  return {
576
580
  fetch: decorated,
@@ -696,9 +700,9 @@ function preferDirective(timeoutMs) {
696
700
  }
697
701
  function createTransport(options) {
698
702
  const baseUrl = normalizeDalpBaseUrl(String(options.baseUrl));
699
- const runtime = options.runtime ?? (globalThis.window === undefined ? "server" : "browser");
703
+ const runtime = options.runtime ?? detectDalpRuntime();
700
704
  const baseFetch = options.fetch ?? globalThis.fetch;
701
- const transportFetch = runtime === "browser" ? baseFetch : withRequestBodyCompression(baseFetch);
705
+ const transportFetch = runtime === "server" ? withRequestBodyCompression(baseFetch) : baseFetch;
702
706
  const { apiKey: apiKeyOption } = options;
703
707
  let apiKey;
704
708
  if (apiKeyOption === undefined) {
@@ -708,12 +712,8 @@ function createTransport(options) {
708
712
  } else {
709
713
  apiKey = secret2(apiKeyOption.trim());
710
714
  }
711
- if (runtime === "browser" && apiKey !== undefined) {
712
- throw createDalpSdkConfigurationError2({
713
- message: "DALP SDK: API keys are server/CLI-only credentials and cannot configure a browser client.",
714
- why: "An API key placed in a browser client ships the credential to every visitor's bundle.",
715
- fix: "Keep the API key on the server or CLI process; browser clients authenticate with the session cookie."
716
- });
715
+ if (apiKey !== undefined) {
716
+ refuseBundledApiKey(runtime);
717
717
  }
718
718
  const sessionFetch = options.ssr === undefined ? transportFetch : createSsrFetch({
719
719
  allowedOrigins: options.ssr.allowedOrigins,
@@ -726,14 +726,18 @@ function createTransport(options) {
726
726
  if (options.authSurface !== undefined) {
727
727
  contextHeaders.set(DALP_AUTH_SURFACE_HEADER2, options.authSurface);
728
728
  }
729
+ if (runtime === "react-native") {
730
+ contextHeaders.set("Origin", new URL(baseUrl).origin);
731
+ }
729
732
  return {
730
733
  baseUrl,
731
734
  fetch: conditional.fetch,
732
735
  conditionalRequests: conditional.store,
733
- credentials: runtime === "browser" ? "include" : "omit",
736
+ credentials: runtimeCredentials(runtime),
734
737
  apiKey,
735
738
  contextHeaders,
736
- settlementTimeoutMs: options.settlementTimeoutMs
739
+ settlementTimeoutMs: options.settlementTimeoutMs,
740
+ streamsResponseBody: !isBufferingFetch(baseFetch)
737
741
  };
738
742
  }
739
743
  function callHeaders(transport, options) {
@@ -1332,6 +1336,21 @@ async function loadResponseDecoder() {
1332
1336
  // src/proxy/sse.ts
1333
1337
  var RESERVED_STREAM_FAILURE_EVENT2 = "effect/httpapi/stream/failure";
1334
1338
  var STREAM_FAILED_ERROR_CODE2 = "sdk.stream.FAILED";
1339
+ var STREAM_UNREADABLE_BODY_ERROR_CODE2 = "sdk.stream.UNREADABLE_BODY";
1340
+ function isReadableBody(body) {
1341
+ return typeof body === "object" && body !== null && typeof Reflect.get(body, "getReader") === "function";
1342
+ }
1343
+ function unreadableBodyError(routeId) {
1344
+ return makeDalpApiError2(new DalpSdkError2({
1345
+ code: STREAM_UNREADABLE_BODY_ERROR_CODE2,
1346
+ routeId,
1347
+ category: "sdk.configuration",
1348
+ retryable: false,
1349
+ message: "The fetch this client uses cannot stream a response body.",
1350
+ why: "React Native's built-in fetch resolves only after the whole response has arrived and exposes no `response.body`, so a stream never yields a frame.",
1351
+ fix: 'Pass a streaming fetch as `fetch`; on React Native that is `fetch` from "expo/fetch", wrapped with the cookie store.'
1352
+ }));
1353
+ }
1335
1354
  function parseFrame(raw) {
1336
1355
  try {
1337
1356
  return JSON.parse(raw.data);
@@ -1412,6 +1431,9 @@ async function* decodeEventStream2(body, decodeFrame, routeId) {
1412
1431
  }
1413
1432
  }
1414
1433
  async function* openEventStream(transport, route, routeId, input, options, signal, closed) {
1434
+ if (!transport.streamsResponseBody) {
1435
+ throw unreadableBodyError(routeId);
1436
+ }
1415
1437
  const prepared = prepareRequest(transport, route, input, options);
1416
1438
  prepared.headers.set("Accept", "text/event-stream");
1417
1439
  const response = await transport.fetch(prepared.url, {
@@ -1424,13 +1446,16 @@ async function* openEventStream(transport, route, routeId, input, options, signa
1424
1446
  if (!response.ok) {
1425
1447
  await raiseResponseError(response, routeId);
1426
1448
  }
1427
- if (response.body === null) {
1449
+ const { body, status } = response;
1450
+ if (body === null) {
1428
1451
  return;
1429
1452
  }
1453
+ if (!isReadableBody(body)) {
1454
+ throw unreadableBodyError(routeId);
1455
+ }
1430
1456
  const decoder = await loadResponseDecoder();
1431
- const { status } = response;
1432
1457
  try {
1433
- yield* decodeEventStream2(response.body, (frame) => decoder.decode(routeId, status, frame), routeId);
1458
+ yield* decodeEventStream2(body, (frame) => decoder.decode(routeId, status, frame), routeId);
1434
1459
  } catch (error) {
1435
1460
  if (!closed.byConsumer) {
1436
1461
  throw error;
@@ -1564,4 +1589,4 @@ function createTanstackFactories(transport) {
1564
1589
  };
1565
1590
  }
1566
1591
 
1567
- export { DalpApiError2, isDalpApiError2, findDalpSdkError2, makeDalpApiError2, isDalpErrorCode2, isDalpSdkLocalErrorCode2, isDalpApiErrorCode2, answeredByRoute, isDalpRouteError2, createDalpContextHeaders2, createTransport, MALFORMED_SUCCESS_ERROR_CODE2, RESERVED_STREAM_FAILURE_EVENT2, STREAM_FAILED_ERROR_CODE2, decodeEventStream2, streamRequest, sendRequest, createFetchExecutor, canonicalize2, queryKeyFor2, UI_SETTLEMENT_TIMEOUT_MS2, createTanstackFactories };
1592
+ export { DalpApiError2, isDalpApiError2, findDalpSdkError2, makeDalpApiError2, isDalpErrorCode2, isDalpSdkLocalErrorCode2, isDalpApiErrorCode2, answeredByRoute, isDalpRouteError2, createDalpContextHeaders2, createTransport, MALFORMED_SUCCESS_ERROR_CODE2, RESERVED_STREAM_FAILURE_EVENT2, STREAM_FAILED_ERROR_CODE2, STREAM_UNREADABLE_BODY_ERROR_CODE2, decodeEventStream2, streamRequest, sendRequest, createFetchExecutor, canonicalize2, queryKeyFor2, UI_SETTLEMENT_TIMEOUT_MS2, createTanstackFactories };
package/dist/effect.js CHANGED
@@ -8,8 +8,8 @@ import {
8
8
  streamRequest,
9
9
  sendRequest,
10
10
  queryKeyFor2
11
- } from "./chunk-yr7kp37j.js";
12
- import"./chunk-wp4269cm.js";
11
+ } from "./chunk-szp73yj8.js";
12
+ import"./chunk-4f4kjtt0.js";
13
13
  import {
14
14
  buildProxy,
15
15
  findRoute
@@ -1,6 +1,6 @@
1
1
  import type { DalpErrorCode, DalpRouteErrorCode, DalpRouteId } from "../generated/api-types.d.ts";
2
2
  import { type DalpApiError } from "./api-error.ts";
3
- export type DalpSdkLocalErrorCode = "DALP_SDK_ERROR" | "sdk.transport.REQUEST_FAILED" | "sdk.transport.MALFORMED_RESPONSE" | "sdk.transport.UNPUBLISHED_ERROR_CODE" | "sdk.transport.UNVERIFIED_ERROR_CODE" | `sdk.transport.HTTP_${number}` | "sdk.response.MALFORMED_SUCCESS" | "sdk.stream.FAILED" | "sdk.scalar.BIG_DECIMAL_INVALID" | "sdk.scalar.EVM_ADDRESS_INVALID" | "sdk.scalar.ORGANIZATION_ID_INVALID" | "sdk.scalar.TOKEN_DECIMALS_INVALID";
3
+ export type DalpSdkLocalErrorCode = "DALP_SDK_ERROR" | "sdk.transport.REQUEST_FAILED" | "sdk.transport.MALFORMED_RESPONSE" | "sdk.transport.UNPUBLISHED_ERROR_CODE" | "sdk.transport.UNVERIFIED_ERROR_CODE" | `sdk.transport.HTTP_${number}` | "sdk.response.MALFORMED_SUCCESS" | "sdk.stream.FAILED" | "sdk.stream.UNREADABLE_BODY" | "sdk.scalar.BIG_DECIMAL_INVALID" | "sdk.scalar.EVM_ADDRESS_INVALID" | "sdk.scalar.ORGANIZATION_ID_INVALID" | "sdk.scalar.TOKEN_DECIMALS_INVALID";
4
4
  export type DalpApiErrorCode = DalpErrorCode | DalpSdkLocalErrorCode;
5
5
  export declare const isDalpErrorCode: (code: string) => code is DalpErrorCode;
6
6
  export declare const isDalpSdkLocalErrorCode: (code: string) => code is DalpSdkLocalErrorCode;
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export type { DalpExecuteResult, DalpRequestParts, DalpRequestRouteKey, DalpRout
4
4
  export type { CompressionRequestInit } from "./client/request-compression.ts";
5
5
  export type { CreateDalpClientOptions, DalpClientContext, DalpSsrOptions } from "./client/factory.ts";
6
6
  export type { ConditionalRequestStore } from "./client/conditional-request.ts";
7
+ export type { DalpRuntime } from "./runtime.ts";
7
8
  export { createCookieStoreFetch, makeCookieStore } from "./transport/cookie-store.ts";
8
9
  export type { CookieStoreShape } from "./transport/cookie-store.ts";
9
10
  export { SECRET_REDACTION_MARKER, Secret, secret } from "./secret.ts";
@@ -12,7 +13,7 @@ export type { SseEventDisposition, SseLifecycleFailureReason, SseLifecycleOption
12
13
  export { WEBHOOK_MANIFEST_VERSION } from "./webhook-events.ts";
13
14
  export type { Webhook } from "./webhook-events.ts";
14
15
  export type { CallOptions } from "./proxy/builder.ts";
15
- export { decodeEventStream, RESERVED_STREAM_FAILURE_EVENT, STREAM_FAILED_ERROR_CODE } from "./proxy/sse.ts";
16
+ export { decodeEventStream, RESERVED_STREAM_FAILURE_EVENT, STREAM_FAILED_ERROR_CODE, STREAM_UNREADABLE_BODY_ERROR_CODE, } from "./proxy/sse.ts";
16
17
  export { MALFORMED_SUCCESS_ERROR_CODE } from "./proxy/response-decode.ts";
17
18
  export { createDalpSdkConfigurationError, DalpSdkError } from "./errors/sdk-error.ts";
18
19
  export { DalpApiError, findDalpSdkError, isDalpApiError, makeDalpApiError } from "./errors/api-error.ts";