@orangecheck/me-client 0.9.0 → 0.12.0

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/dist/index.d.mts CHANGED
@@ -1,6 +1,28 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
 
4
+ type Scope = 'bitcoin_address' | 'email' | 'attest_tier' | 'display_name';
5
+ interface GrantedOptions {
6
+ project_key: string;
7
+ signal?: AbortSignal;
8
+ }
9
+ interface GrantedResult {
10
+ sub: string;
11
+ scopes_granted: Scope[];
12
+ scopes: Partial<Record<Scope, string>>;
13
+ }
14
+ declare function granted(options: GrantedOptions): Promise<GrantedResult>;
15
+ interface RequestOptions {
16
+ project_key: string;
17
+ return_to: string;
18
+ origin?: string;
19
+ }
20
+ declare function request(scopes: Scope[], options: RequestOptions): never;
21
+ declare const scope: {
22
+ granted: typeof granted;
23
+ request: typeof request;
24
+ };
25
+
4
26
  interface VerifyResult {
5
27
  ok: boolean;
6
28
  reason?: string;
@@ -152,6 +174,37 @@ interface FireEventOptions {
152
174
  metadata?: Record<string, unknown>;
153
175
  }
154
176
  declare function fire(options: FireEventOptions): Promise<BillableEvent>;
177
+ interface BatchEventInput {
178
+ subtype: EventSubtype;
179
+ payment_amount_sats?: number;
180
+ action_label?: string;
181
+ note?: string;
182
+ idempotency_key?: string;
183
+ }
184
+ type BatchEventResult = {
185
+ index: number;
186
+ status: 'recorded';
187
+ event: BillableEvent;
188
+ } | {
189
+ index: number;
190
+ status: 'duplicate';
191
+ event: BillableEvent;
192
+ } | {
193
+ index: number;
194
+ status: 'rejected';
195
+ reason: string;
196
+ };
197
+ interface FireBatchOptions {
198
+ project_key: string;
199
+ events: BatchEventInput[];
200
+ }
201
+ interface FireBatchResponse {
202
+ ok: boolean;
203
+ results: BatchEventResult[];
204
+ escrow_mode: 'simulation' | 'live';
205
+ simulation_mode_note?: string;
206
+ }
207
+ declare function fireBatch(options: FireBatchOptions): Promise<FireBatchResponse>;
155
208
  interface VerifyEventResult {
156
209
  ok: boolean;
157
210
  issues: Array<{
@@ -170,6 +223,7 @@ interface VerifyEventResult {
170
223
  declare function verify(envelope: BillableEvent, config: IntegratorPriceConfig, payment_amount_sats?: number): VerifyEventResult;
171
224
  declare const event: {
172
225
  fire: typeof fire;
226
+ fireBatch: typeof fireBatch;
173
227
  verify: typeof verify;
174
228
  };
175
229
 
@@ -221,8 +275,20 @@ declare function getBearerToken(): string | null;
221
275
  declare function clearBearerToken(): void;
222
276
  declare class MeClientError extends Error {
223
277
  readonly status: number;
224
- constructor(message: string, status: number);
278
+ readonly errorCode: string | undefined;
279
+ readonly retryAfterSeconds: number | undefined;
280
+ constructor(message: string, status: number, opts?: {
281
+ errorCode?: string;
282
+ retryAfterSeconds?: number;
283
+ });
284
+ get isRateLimited(): boolean;
225
285
  }
286
+ interface WithRateLimitRetryOptions {
287
+ maxRetries?: number;
288
+ maxRetryAfterSeconds?: number;
289
+ baseBackoffMs?: number;
290
+ }
291
+ declare function withRateLimitRetry<T>(fn: () => Promise<T>, opts?: WithRateLimitRetryOptions): Promise<T>;
226
292
 
227
293
  declare const oc: {
228
294
  session: {
@@ -235,6 +301,7 @@ declare const oc: {
235
301
  };
236
302
  event: {
237
303
  fire: (options: FireEventOptions) => Promise<BillableEvent>;
304
+ fireBatch: (options: FireBatchOptions) => Promise<FireBatchResponse>;
238
305
  verify: (envelope: BillableEvent, config: IntegratorPriceConfig, payment_amount_sats?: number) => VerifyEventResult;
239
306
  };
240
307
  config: {
@@ -250,6 +317,10 @@ declare const oc: {
250
317
  get: (slug: string) => Promise<Federation | null>;
251
318
  defaultLive: () => Promise<Federation | null>;
252
319
  };
320
+ scope: {
321
+ granted: (options: GrantedOptions) => Promise<GrantedResult>;
322
+ request: typeof request;
323
+ };
253
324
  };
254
325
 
255
- export { type AttestTier, type BillableEvent, type ClassASubtype, type ClassBSubtype, type ClassCSubtype, type ComputedFees, type EventClass, type EventSubtype, type Federation, type FederationStatus, type FireEventOptions, type IntegratorEventConfig, type IntegratorPriceConfig, MIN_INTEGRATOR_PRICE_SATS, MeClientError, type OcPublicJwk, OcSignInButton, type OcSignInButtonProps, PLATFORM_FEE_POLICY, type PaymentAuthorizeOptions, type PaymentResult, type Session, type SessionPolicy, type SignInOptions, type SigningMethod, type SiteFeeShape, type TelemetryEvent, type ValidationResult, type VerifyEventResult, type VerifyOptions, type VerifyResult, clearBearerToken, computeFees, config, event, federations, getBearerToken, getOrigin, oc, onTelemetry, payment, session, setBearerToken, setOrigin, validateIntegratorConfig, webhook };
326
+ export { type AttestTier, type BillableEvent, type ClassASubtype, type ClassBSubtype, type ClassCSubtype, type ComputedFees, type EventClass, type EventSubtype, type Federation, type FederationStatus, type FireEventOptions, type GrantedOptions, type GrantedResult, type IntegratorEventConfig, type IntegratorPriceConfig, MIN_INTEGRATOR_PRICE_SATS, MeClientError, type OcPublicJwk, OcSignInButton, type OcSignInButtonProps, PLATFORM_FEE_POLICY, type PaymentAuthorizeOptions, type PaymentResult, type RequestOptions, type Scope, type Session, type SessionPolicy, type SignInOptions, type SigningMethod, type SiteFeeShape, type TelemetryEvent, type ValidationResult, type VerifyEventResult, type VerifyOptions, type VerifyResult, type WithRateLimitRetryOptions, clearBearerToken, computeFees, config, event, federations, getBearerToken, getOrigin, oc, onTelemetry, payment, scope, session, setBearerToken, setOrigin, validateIntegratorConfig, webhook, withRateLimitRetry };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,28 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
3
 
4
+ type Scope = 'bitcoin_address' | 'email' | 'attest_tier' | 'display_name';
5
+ interface GrantedOptions {
6
+ project_key: string;
7
+ signal?: AbortSignal;
8
+ }
9
+ interface GrantedResult {
10
+ sub: string;
11
+ scopes_granted: Scope[];
12
+ scopes: Partial<Record<Scope, string>>;
13
+ }
14
+ declare function granted(options: GrantedOptions): Promise<GrantedResult>;
15
+ interface RequestOptions {
16
+ project_key: string;
17
+ return_to: string;
18
+ origin?: string;
19
+ }
20
+ declare function request(scopes: Scope[], options: RequestOptions): never;
21
+ declare const scope: {
22
+ granted: typeof granted;
23
+ request: typeof request;
24
+ };
25
+
4
26
  interface VerifyResult {
5
27
  ok: boolean;
6
28
  reason?: string;
@@ -152,6 +174,37 @@ interface FireEventOptions {
152
174
  metadata?: Record<string, unknown>;
153
175
  }
154
176
  declare function fire(options: FireEventOptions): Promise<BillableEvent>;
177
+ interface BatchEventInput {
178
+ subtype: EventSubtype;
179
+ payment_amount_sats?: number;
180
+ action_label?: string;
181
+ note?: string;
182
+ idempotency_key?: string;
183
+ }
184
+ type BatchEventResult = {
185
+ index: number;
186
+ status: 'recorded';
187
+ event: BillableEvent;
188
+ } | {
189
+ index: number;
190
+ status: 'duplicate';
191
+ event: BillableEvent;
192
+ } | {
193
+ index: number;
194
+ status: 'rejected';
195
+ reason: string;
196
+ };
197
+ interface FireBatchOptions {
198
+ project_key: string;
199
+ events: BatchEventInput[];
200
+ }
201
+ interface FireBatchResponse {
202
+ ok: boolean;
203
+ results: BatchEventResult[];
204
+ escrow_mode: 'simulation' | 'live';
205
+ simulation_mode_note?: string;
206
+ }
207
+ declare function fireBatch(options: FireBatchOptions): Promise<FireBatchResponse>;
155
208
  interface VerifyEventResult {
156
209
  ok: boolean;
157
210
  issues: Array<{
@@ -170,6 +223,7 @@ interface VerifyEventResult {
170
223
  declare function verify(envelope: BillableEvent, config: IntegratorPriceConfig, payment_amount_sats?: number): VerifyEventResult;
171
224
  declare const event: {
172
225
  fire: typeof fire;
226
+ fireBatch: typeof fireBatch;
173
227
  verify: typeof verify;
174
228
  };
175
229
 
@@ -221,8 +275,20 @@ declare function getBearerToken(): string | null;
221
275
  declare function clearBearerToken(): void;
222
276
  declare class MeClientError extends Error {
223
277
  readonly status: number;
224
- constructor(message: string, status: number);
278
+ readonly errorCode: string | undefined;
279
+ readonly retryAfterSeconds: number | undefined;
280
+ constructor(message: string, status: number, opts?: {
281
+ errorCode?: string;
282
+ retryAfterSeconds?: number;
283
+ });
284
+ get isRateLimited(): boolean;
225
285
  }
286
+ interface WithRateLimitRetryOptions {
287
+ maxRetries?: number;
288
+ maxRetryAfterSeconds?: number;
289
+ baseBackoffMs?: number;
290
+ }
291
+ declare function withRateLimitRetry<T>(fn: () => Promise<T>, opts?: WithRateLimitRetryOptions): Promise<T>;
226
292
 
227
293
  declare const oc: {
228
294
  session: {
@@ -235,6 +301,7 @@ declare const oc: {
235
301
  };
236
302
  event: {
237
303
  fire: (options: FireEventOptions) => Promise<BillableEvent>;
304
+ fireBatch: (options: FireBatchOptions) => Promise<FireBatchResponse>;
238
305
  verify: (envelope: BillableEvent, config: IntegratorPriceConfig, payment_amount_sats?: number) => VerifyEventResult;
239
306
  };
240
307
  config: {
@@ -250,6 +317,10 @@ declare const oc: {
250
317
  get: (slug: string) => Promise<Federation | null>;
251
318
  defaultLive: () => Promise<Federation | null>;
252
319
  };
320
+ scope: {
321
+ granted: (options: GrantedOptions) => Promise<GrantedResult>;
322
+ request: typeof request;
323
+ };
253
324
  };
254
325
 
255
- export { type AttestTier, type BillableEvent, type ClassASubtype, type ClassBSubtype, type ClassCSubtype, type ComputedFees, type EventClass, type EventSubtype, type Federation, type FederationStatus, type FireEventOptions, type IntegratorEventConfig, type IntegratorPriceConfig, MIN_INTEGRATOR_PRICE_SATS, MeClientError, type OcPublicJwk, OcSignInButton, type OcSignInButtonProps, PLATFORM_FEE_POLICY, type PaymentAuthorizeOptions, type PaymentResult, type Session, type SessionPolicy, type SignInOptions, type SigningMethod, type SiteFeeShape, type TelemetryEvent, type ValidationResult, type VerifyEventResult, type VerifyOptions, type VerifyResult, clearBearerToken, computeFees, config, event, federations, getBearerToken, getOrigin, oc, onTelemetry, payment, session, setBearerToken, setOrigin, validateIntegratorConfig, webhook };
326
+ export { type AttestTier, type BillableEvent, type ClassASubtype, type ClassBSubtype, type ClassCSubtype, type ComputedFees, type EventClass, type EventSubtype, type Federation, type FederationStatus, type FireEventOptions, type GrantedOptions, type GrantedResult, type IntegratorEventConfig, type IntegratorPriceConfig, MIN_INTEGRATOR_PRICE_SATS, MeClientError, type OcPublicJwk, OcSignInButton, type OcSignInButtonProps, PLATFORM_FEE_POLICY, type PaymentAuthorizeOptions, type PaymentResult, type RequestOptions, type Scope, type Session, type SessionPolicy, type SignInOptions, type SigningMethod, type SiteFeeShape, type TelemetryEvent, type ValidationResult, type VerifyEventResult, type VerifyOptions, type VerifyResult, type WithRateLimitRetryOptions, clearBearerToken, computeFees, config, event, federations, getBearerToken, getOrigin, oc, onTelemetry, payment, scope, session, setBearerToken, setOrigin, validateIntegratorConfig, webhook, withRateLimitRetry };
package/dist/index.js CHANGED
@@ -60,22 +60,69 @@ async function api(path, opts = {}) {
60
60
  });
61
61
  if (!res.ok) {
62
62
  let message = `me.ochk.io ${res.status}`;
63
+ let errorCode;
64
+ let body;
63
65
  try {
64
66
  const data = await res.json();
65
- if (data?.error) message = data.error;
67
+ body = data;
68
+ if (data?.error) {
69
+ errorCode = data.error;
70
+ message = data.reason ?? data.error;
71
+ }
66
72
  } catch {
67
73
  }
68
- throw new MeClientError(message, res.status);
74
+ let retryAfterSeconds;
75
+ if (res.status === 429) {
76
+ const headerValue = res.headers.get("retry-after");
77
+ if (headerValue) {
78
+ const parsed = Number.parseInt(headerValue, 10);
79
+ if (Number.isFinite(parsed) && parsed >= 0) retryAfterSeconds = parsed;
80
+ }
81
+ if (retryAfterSeconds == null && typeof body?.retry_after_seconds === "number") {
82
+ retryAfterSeconds = body.retry_after_seconds;
83
+ }
84
+ }
85
+ throw new MeClientError(message, res.status, { errorCode, retryAfterSeconds });
69
86
  }
70
87
  return await res.json();
71
88
  }
72
89
  var MeClientError = class extends Error {
73
- constructor(message, status) {
90
+ constructor(message, status, opts) {
74
91
  super(message);
75
92
  this.name = "MeClientError";
76
93
  this.status = status;
94
+ this.errorCode = opts?.errorCode;
95
+ this.retryAfterSeconds = opts?.retryAfterSeconds;
96
+ }
97
+ /** True for the 429 surface · convenience boolean so callers don't
98
+ * have to compare status === 429 every time. */
99
+ get isRateLimited() {
100
+ return this.status === 429;
77
101
  }
78
102
  };
103
+ async function withRateLimitRetry(fn, opts = {}) {
104
+ const maxRetries = opts.maxRetries ?? 2;
105
+ const maxRetryAfter = opts.maxRetryAfterSeconds ?? 30;
106
+ const baseBackoff = opts.baseBackoffMs ?? 250;
107
+ let attempt = 0;
108
+ while (true) {
109
+ try {
110
+ return await fn();
111
+ } catch (err) {
112
+ if (!(err instanceof MeClientError) || !err.isRateLimited) throw err;
113
+ if (attempt >= maxRetries) throw err;
114
+ let waitMs;
115
+ if (err.retryAfterSeconds != null) {
116
+ if (err.retryAfterSeconds > maxRetryAfter) throw err;
117
+ waitMs = err.retryAfterSeconds * 1e3;
118
+ } else {
119
+ waitMs = baseBackoff * 2 ** attempt;
120
+ }
121
+ await new Promise((resolve) => setTimeout(resolve, waitMs));
122
+ attempt++;
123
+ }
124
+ }
125
+ }
79
126
 
80
127
  // src/session.ts
81
128
  var DEFAULT_POLICY = {
@@ -131,7 +178,7 @@ async function invalidate(sessionId) {
131
178
  }
132
179
  var session = { create, refresh, invalidate };
133
180
  function OcSignInButton({
134
- scope,
181
+ scope: scope2,
135
182
  sessionPolicy,
136
183
  returnTo,
137
184
  onSignin,
@@ -152,7 +199,7 @@ function OcSignInButton({
152
199
  if (event2.defaultPrevented) return;
153
200
  setPending(true);
154
201
  try {
155
- const result = await session.create({ scope, sessionPolicy, returnTo });
202
+ const result = await session.create({ scope: scope2, sessionPolicy, returnTo });
156
203
  onSignin?.(result);
157
204
  } catch (err) {
158
205
  onSignInError?.(err instanceof Error ? err : new Error(String(err)));
@@ -294,6 +341,23 @@ async function fire(options) {
294
341
  body: options
295
342
  });
296
343
  }
344
+ async function fireBatch(options) {
345
+ if (!options.project_key) {
346
+ throw new Error("event.fireBatch requires project_key");
347
+ }
348
+ if (!Array.isArray(options.events) || options.events.length === 0) {
349
+ throw new Error("event.fireBatch requires at least one event");
350
+ }
351
+ if (options.events.length > 100) {
352
+ throw new Error(
353
+ `event.fireBatch \xB7 max 100 events per call (got ${options.events.length}). Split client-side.`
354
+ );
355
+ }
356
+ return api("/api/integrator/event/batch", {
357
+ method: "POST",
358
+ body: options
359
+ });
360
+ }
297
361
  function verify(envelope, config2, payment_amount_sats) {
298
362
  const issues = [];
299
363
  const eventCfg = config2.events[envelope.subtype];
@@ -378,7 +442,7 @@ function verify(envelope, config2, payment_amount_sats) {
378
442
  issues
379
443
  };
380
444
  }
381
- var event = { fire, verify };
445
+ var event = { fire, fireBatch, verify };
382
446
 
383
447
  // src/config.ts
384
448
  function validate(cfg) {
@@ -744,8 +808,59 @@ async function defaultLive() {
744
808
  }
745
809
  var federations = { list, live, get, defaultLive };
746
810
 
811
+ // src/scope.ts
812
+ var VALID_SCOPES = [
813
+ "bitcoin_address",
814
+ "email",
815
+ "attest_tier",
816
+ "display_name"
817
+ ];
818
+ async function granted(options) {
819
+ if (!options.project_key) {
820
+ throw new Error("oc.scope.granted: project_key is required");
821
+ }
822
+ const r = await api(
823
+ `/api/integrator/scopes/${encodeURIComponent(options.project_key)}`,
824
+ { method: "GET", signal: options.signal }
825
+ );
826
+ return {
827
+ sub: r.sub,
828
+ scopes_granted: r.scopes_granted,
829
+ scopes: r.scopes
830
+ };
831
+ }
832
+ function request(scopes, options) {
833
+ if (!options.project_key) {
834
+ throw new Error("oc.scope.request: project_key is required");
835
+ }
836
+ if (!options.return_to) {
837
+ throw new Error("oc.scope.request: return_to is required");
838
+ }
839
+ if (!Array.isArray(scopes) || scopes.length === 0) {
840
+ throw new Error("oc.scope.request: scopes must be a non-empty array");
841
+ }
842
+ for (const s of scopes) {
843
+ if (!VALID_SCOPES.includes(s)) {
844
+ throw new Error(
845
+ `oc.scope.request: unknown scope "${s}" \xB7 valid: ${VALID_SCOPES.join(", ")}`
846
+ );
847
+ }
848
+ }
849
+ if (typeof window === "undefined") {
850
+ throw new Error("oc.scope.request: must be called in a browser environment");
851
+ }
852
+ const origin2 = options.origin ?? getOrigin();
853
+ const url = new URL("/me/scope-grant", origin2);
854
+ url.searchParams.set("project_key", options.project_key);
855
+ url.searchParams.set("scopes", scopes.join(","));
856
+ url.searchParams.set("return_to", options.return_to);
857
+ window.location.assign(url.toString());
858
+ throw new Error("oc.scope.request: redirected to consent prompt");
859
+ }
860
+ var scope = { granted, request };
861
+
747
862
  // src/index.ts
748
- var oc = { session, payment, event, config, webhook, federations };
863
+ var oc = { session, payment, event, config, webhook, federations, scope };
749
864
 
750
865
  exports.MIN_INTEGRATOR_PRICE_SATS = MIN_INTEGRATOR_PRICE_SATS;
751
866
  exports.MeClientError = MeClientError;
@@ -761,10 +876,12 @@ exports.getOrigin = getOrigin;
761
876
  exports.oc = oc;
762
877
  exports.onTelemetry = onTelemetry;
763
878
  exports.payment = payment;
879
+ exports.scope = scope;
764
880
  exports.session = session;
765
881
  exports.setBearerToken = setBearerToken;
766
882
  exports.setOrigin = setOrigin;
767
883
  exports.validateIntegratorConfig = validateIntegratorConfig;
768
884
  exports.webhook = webhook;
885
+ exports.withRateLimitRetry = withRateLimitRetry;
769
886
  //# sourceMappingURL=index.js.map
770
887
  //# sourceMappingURL=index.js.map