@relume_io/partner-node 2.0.1 → 2.2.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.ts CHANGED
@@ -1,9 +1,16 @@
1
1
  import { IncomingMessage, ServerResponse } from 'node:http';
2
2
 
3
3
  type CallType = 'user.get' | 'credits.get' | 'credits.charge' | 'credits.reverse';
4
+ /** A key's environment, and the `environment` every signed call carries. */
5
+ type Environment = 'test' | 'live';
4
6
  /** Passed to every callback, for your logs. */
5
7
  interface CallContext {
6
8
  type: CallType;
9
+ /**
10
+ * Always your secret key's: a call for the other environment is refused
11
+ * before any callback runs.
12
+ */
13
+ environment: Environment;
7
14
  /** Relume-Request-Id: unique per attempt. Quote it to Relume support. */
8
15
  requestId: string | null;
9
16
  }
@@ -39,7 +46,13 @@ interface Charge {
39
46
  action: string;
40
47
  siteId?: string;
41
48
  pageId?: string;
42
- /** For the user's wallet statement. At most 200 characters. */
49
+ /**
50
+ * The pages one charge pays for, when it covers several (a `page.unlock`
51
+ * for each, with both prices multiplied by their count). Sent instead of
52
+ * `pageId`. Debit `partnerCredits` as for any charge.
53
+ */
54
+ pageIds?: string[];
55
+ /** For the user's wallet statement, e.g. `Build 3 pages`. At most 200 characters. */
43
56
  description: string;
44
57
  }
45
58
  interface ChargeResult {
@@ -245,6 +258,20 @@ interface Price {
245
258
  partnerCredits: number | null;
246
259
  relumeCredits: number;
247
260
  }
261
+ /** What you call your credits: the word the builder shows beside amounts. */
262
+ interface CreditUnit {
263
+ /** e.g. `coin`. */
264
+ singular: string;
265
+ /** e.g. `coins`. */
266
+ plural: string;
267
+ /** The https icon the builder shows beside amounts; absent when not set. */
268
+ iconUrl?: string;
269
+ }
270
+ interface PriceList {
271
+ data: Price[];
272
+ /** What the builder calls your credits, as Relume staff set it for you. */
273
+ creditUnit: CreditUnit;
274
+ }
248
275
 
249
276
  declare const DEFAULT_API_BASE_URL = "https://apis.relume.io/nova/partner/v1";
250
277
  /**
@@ -356,6 +383,8 @@ interface PartnerApi {
356
383
  prices: {
357
384
  /** GET /prices: every billable action's price in this environment. */
358
385
  list(): Promise<Price[]>;
386
+ /** GET /prices: the prices, and what the builder calls your credits. */
387
+ get(): Promise<PriceList>;
359
388
  };
360
389
  }
361
390
 
@@ -451,8 +480,9 @@ declare class Relume<TRequest extends SessionRequest = SessionRequest> {
451
480
  express(): NodeHandler;
452
481
  /**
453
482
  * `POST /relume/calls`, framework-agnostic: verifies the signature and the
454
- * 5-minute window, routes by `type`, runs your callback and validates what
455
- * it returns. Pass the raw body exactly as received.
483
+ * 5-minute window, refuses a call for another environment than the secret
484
+ * key's (`400 environment_mismatch`), routes by `type`, runs your callback
485
+ * and validates what it returns. Pass the raw body exactly as received.
456
486
  */
457
487
  handleCall(input: {
458
488
  rawBody: string | Uint8Array;
@@ -522,4 +552,4 @@ declare function verifySignature(input: {
522
552
  nowSeconds?: number;
523
553
  }): Promise<VerifySignatureResult>;
524
554
 
525
- export { type CallContext, type CallType, type Charge, type ChargeCreditsCallback, type ChargeResult, type CreateSessionBody, type CreateSiteBody, type CreatedSite, type Credits, DEFAULT_API_BASE_URL, EMBED_HEADER, EMBED_HEADER_VALUE, type Export, type ExportStatus, type GetCreditsCallback, type GetUserCallback, type GetUserIdFromRequest, type HandlerErrorInfo, InvalidCallbackResultError, type LedgerEntry, type LedgerEntryDetail, type LedgerEntryStatus, type LedgerEntryType, MAX_SIGNATURES, type NodeHandler, type NodeRequest, type Page, type PageQuery, PagedList, type ParsedSignatureHeader, type PartnerApi, type PartnerApiErrorCode, type PartnerResponse, type Price, REQUEST_ID_HEADER, Relume, RelumeError, type RelumeErrorCode, type RelumeOptions, type RelumeResponse, type Reversal, type ReversalReason, type ReversalResult, type ReverseChargeCallback, SIGNATURE_HEADER, SIGNATURE_TOLERANCE_SECONDS, type Session, type SessionRequest, type SignatureFailure, type Site, type UsageGroup, type UsageParams, type UsageRow, type UsageTotals, type User, type UserProfile, type UserStatus, type VerifySignatureResult, computeSignature, parseSignatureHeader, signCall, verifySignature };
555
+ export { type CallContext, type CallType, type Charge, type ChargeCreditsCallback, type ChargeResult, type CreateSessionBody, type CreateSiteBody, type CreatedSite, type CreditUnit, type Credits, DEFAULT_API_BASE_URL, EMBED_HEADER, EMBED_HEADER_VALUE, type Environment, type Export, type ExportStatus, type GetCreditsCallback, type GetUserCallback, type GetUserIdFromRequest, type HandlerErrorInfo, InvalidCallbackResultError, type LedgerEntry, type LedgerEntryDetail, type LedgerEntryStatus, type LedgerEntryType, MAX_SIGNATURES, type NodeHandler, type NodeRequest, type Page, type PageQuery, PagedList, type ParsedSignatureHeader, type PartnerApi, type PartnerApiErrorCode, type PartnerResponse, type Price, type PriceList, REQUEST_ID_HEADER, Relume, RelumeError, type RelumeErrorCode, type RelumeOptions, type RelumeResponse, type Reversal, type ReversalReason, type ReversalResult, type ReverseChargeCallback, SIGNATURE_HEADER, SIGNATURE_TOLERANCE_SECONDS, type Session, type SessionRequest, type SignatureFailure, type Site, type UsageGroup, type UsageParams, type UsageRow, type UsageTotals, type User, type UserProfile, type UserStatus, type VerifySignatureResult, computeSignature, parseSignatureHeader, signCall, verifySignature };
package/dist/index.js CHANGED
@@ -1,22 +1,24 @@
1
1
  import {
2
2
  EMBED_HEADER,
3
3
  EMBED_HEADER_VALUE,
4
+ InvalidCallbackResultError,
4
5
  MAX_BODY_BYTES,
5
6
  MAX_SIGNATURES,
6
7
  REQUEST_ID_HEADER,
7
8
  SIGNATURE_HEADER,
8
9
  SIGNATURE_TOLERANCE_SECONDS,
9
- absent,
10
+ checkChargeResult,
11
+ checkCredits,
12
+ checkReversalResult,
13
+ checkUserProfile,
10
14
  computeSignature,
11
15
  errorResponse,
12
- isEmail,
13
- isHttpsUrl,
14
16
  isRecord,
15
- isString,
16
- isWhole,
17
17
  jsonResponse,
18
+ keyEnvironment,
18
19
  methodNotAllowed,
19
20
  mountedRouteOf,
21
+ parseCall,
20
22
  parseSignatureHeader,
21
23
  payloadTooLarge,
22
24
  preflight,
@@ -24,7 +26,7 @@ import {
24
26
  routeOf,
25
27
  signCall,
26
28
  verifySignature
27
- } from "./chunk-HPVVBUR7.js";
29
+ } from "./chunk-WUW4CIBP.js";
28
30
 
29
31
  // src/adapters/express.ts
30
32
  var PayloadTooLargeError = class extends Error {
@@ -226,7 +228,7 @@ function fetchHandler(client, maxBodyBytes) {
226
228
 
227
229
  // src/api.ts
228
230
  var DEFAULT_API_BASE_URL = "https://apis.relume.io/nova/partner/v1";
229
- var SDK_VERSION = "2.0.1";
231
+ var SDK_VERSION = "2.2.0";
230
232
  var USER_AGENT = `relume-partner-node/${SDK_VERSION}`;
231
233
  var DEFAULT_API_TIMEOUT_MS = 15e3;
232
234
  var DEFAULT_MAX_RETRIES = 2;
@@ -377,171 +379,6 @@ function isSession(value) {
377
379
  return isRecord(value) && typeof value.clientSecret === "string" && typeof value.expiresAt === "string";
378
380
  }
379
381
 
380
- // src/calls.ts
381
- var CALL_TYPES = [
382
- "user.get",
383
- "credits.get",
384
- "credits.charge",
385
- "credits.reverse"
386
- ];
387
- var InvalidCallbackResultError = class extends Error {
388
- constructor(message) {
389
- super(message);
390
- this.name = "InvalidCallbackResultError";
391
- }
392
- };
393
- function optionalString(value, max) {
394
- if (value === void 0 || value === null) {
395
- return { ok: true, value: void 0 };
396
- }
397
- return isString(value, 1, max) ? { ok: true, value } : { ok: false };
398
- }
399
- function isCallType(value) {
400
- return CALL_TYPES.some((type) => type === value);
401
- }
402
- function parseCall(body) {
403
- if (!isRecord(body)) {
404
- return {
405
- ok: false,
406
- code: "invalid_request",
407
- message: "The body must be a JSON object."
408
- };
409
- }
410
- const { type } = body;
411
- if (!isCallType(type)) {
412
- return {
413
- ok: false,
414
- code: "unsupported_call_type",
415
- message: `Unsupported call type: ${JSON.stringify(type ?? null).slice(0, 64)}.`
416
- };
417
- }
418
- const invalid = (field) => ({
419
- ok: false,
420
- code: "invalid_request",
421
- message: `Invalid ${field} for ${type}.`,
422
- type
423
- });
424
- const { userId } = body;
425
- if (!isString(userId, 1, 128)) return invalid("userId");
426
- switch (type) {
427
- case "user.get":
428
- case "credits.get":
429
- return { ok: true, call: { type, userId } };
430
- case "credits.charge": {
431
- const { chargeId, partnerCredits, relumeCredits, action, description } = body;
432
- if (!isString(chargeId, 1, 64)) return invalid("chargeId");
433
- if (!isWhole(partnerCredits, 1)) return invalid("partnerCredits");
434
- if (!isWhole(relumeCredits, 0)) return invalid("relumeCredits");
435
- if (!isString(action, 1, 200)) return invalid("action");
436
- if (!isString(description, 0, 200)) return invalid("description");
437
- const siteId = optionalString(body.siteId, 200);
438
- if (!siteId.ok) return invalid("siteId");
439
- const pageId = optionalString(body.pageId, 200);
440
- if (!pageId.ok) return invalid("pageId");
441
- const charge = {
442
- userId,
443
- chargeId,
444
- partnerCredits,
445
- relumeCredits,
446
- action,
447
- description
448
- };
449
- if (siteId.value !== void 0) charge.siteId = siteId.value;
450
- if (pageId.value !== void 0) charge.pageId = pageId.value;
451
- return { ok: true, call: { type, charge } };
452
- }
453
- case "credits.reverse": {
454
- const { reversalId, partnerCredits, relumeCredits, reason } = body;
455
- if (!isString(reversalId, 1, 64)) return invalid("reversalId");
456
- if (!isWhole(partnerCredits, 1)) return invalid("partnerCredits");
457
- if (!isWhole(relumeCredits, 0)) return invalid("relumeCredits");
458
- if (!isString(reason, 1, 200)) return invalid("reason");
459
- const chargeId = optionalString(body.chargeId, 64);
460
- if (!chargeId.ok) return invalid("chargeId");
461
- const reversal = {
462
- userId,
463
- reversalId,
464
- partnerCredits,
465
- relumeCredits,
466
- reason
467
- };
468
- if (chargeId.value !== void 0) reversal.chargeId = chargeId.value;
469
- return { ok: true, call: { type, reversal } };
470
- }
471
- }
472
- }
473
- function balanceOrThrow(value, callback) {
474
- if (!isWhole(value, 0)) {
475
- throw new InvalidCallbackResultError(
476
- `${callback} must return a balance that is a whole number, zero or more; got ${JSON.stringify(value)}.`
477
- );
478
- }
479
- return value;
480
- }
481
- function checkUserProfile(value) {
482
- if (value === null || value === void 0) return null;
483
- if (!isRecord(value)) {
484
- throw new InvalidCallbackResultError(
485
- "onGetUser must return { name?, email?, avatarUrl? } or null."
486
- );
487
- }
488
- const profile = {};
489
- const { name, email, avatarUrl } = value;
490
- if (!absent(name)) {
491
- if (!isString(name, 1, 200)) {
492
- throw new InvalidCallbackResultError(
493
- "onGetUser: name must be a string of at most 200 characters."
494
- );
495
- }
496
- profile.name = name;
497
- }
498
- if (!absent(email)) {
499
- if (!isEmail(email)) {
500
- throw new InvalidCallbackResultError(
501
- "onGetUser: email must be a valid email address."
502
- );
503
- }
504
- profile.email = email;
505
- }
506
- if (!absent(avatarUrl)) {
507
- if (!isHttpsUrl(avatarUrl)) {
508
- throw new InvalidCallbackResultError(
509
- "onGetUser: avatarUrl must be an https URL."
510
- );
511
- }
512
- profile.avatarUrl = avatarUrl;
513
- }
514
- return profile;
515
- }
516
- function checkCredits(value) {
517
- if (!isRecord(value)) {
518
- throw new InvalidCallbackResultError(
519
- "onGetCredits must return { balance }."
520
- );
521
- }
522
- return { balance: balanceOrThrow(value.balance, "onGetCredits") };
523
- }
524
- function optionalBalance(value, callback) {
525
- return absent(value.balance) ? {} : { balance: balanceOrThrow(value.balance, callback) };
526
- }
527
- function checkChargeResult(value) {
528
- if (!isRecord(value) || typeof value.ok !== "boolean") {
529
- throw new InvalidCallbackResultError(
530
- "onChargeCredits must return { ok: boolean, balance? }."
531
- );
532
- }
533
- return { ok: value.ok, ...optionalBalance(value, "onChargeCredits") };
534
- }
535
- function checkReversalResult(value) {
536
- if (value === void 0 || value === null) return {};
537
- if (!isRecord(value)) {
538
- throw new InvalidCallbackResultError(
539
- "onReverseCharge must return { balance? } or nothing."
540
- );
541
- }
542
- return optionalBalance(value, "onReverseCharge");
543
- }
544
-
545
382
  // src/pages.ts
546
383
  function isPage(value) {
547
384
  return isRecord(value) && Array.isArray(value.data) && typeof value.nextCursor === "string" && typeof value.hasMore === "boolean";
@@ -606,8 +443,9 @@ var isLedgerEntryDetail = hasStrings(
606
443
  "type",
607
444
  "status"
608
445
  );
446
+ var isCreditUnit = hasStrings("singular", "plural");
609
447
  function isPriceList(value) {
610
- return isRecord(value) && Array.isArray(value.data);
448
+ return isRecord(value) && Array.isArray(value.data) && isCreditUnit(value.creditUnit);
611
449
  }
612
450
  function segment(name, value) {
613
451
  if (typeof value !== "string" || value === "") {
@@ -686,7 +524,8 @@ function partnerApi(config) {
686
524
  })
687
525
  },
688
526
  prices: {
689
- list: async () => (await get("/prices", isPriceList)).data
527
+ list: async () => (await get("/prices", isPriceList)).data,
528
+ get: () => get("/prices", isPriceList)
690
529
  }
691
530
  };
692
531
  }
@@ -723,6 +562,7 @@ var Relume = class _Relume {
723
562
  */
724
563
  fetchHandler;
725
564
  #secretKey;
565
+ #environment;
726
566
  #api;
727
567
  #callbacks = {};
728
568
  #onHandlerError;
@@ -742,6 +582,12 @@ var Relume = class _Relume {
742
582
  "Relume.init: that is a publishable key. Pass the secret key (sk_\u2026); the publishable key goes to the embed."
743
583
  );
744
584
  }
585
+ const environment = keyEnvironment(secretKey);
586
+ if (environment === null) {
587
+ throw new TypeError(
588
+ "Relume.init: secretKey must start with sk_test_ or sk_live_."
589
+ );
590
+ }
745
591
  const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
746
592
  if (!Number.isSafeInteger(maxRetries) || maxRetries < 0) {
747
593
  throw new TypeError(
@@ -749,6 +595,7 @@ var Relume = class _Relume {
749
595
  );
750
596
  }
751
597
  this.#secretKey = secretKey;
598
+ this.#environment = environment;
752
599
  this.#now = options.now ?? Date.now;
753
600
  this.#api = {
754
601
  secretKey,
@@ -824,8 +671,9 @@ var Relume = class _Relume {
824
671
  }
825
672
  /**
826
673
  * `POST /relume/calls`, framework-agnostic: verifies the signature and the
827
- * 5-minute window, routes by `type`, runs your callback and validates what
828
- * it returns. Pass the raw body exactly as received.
674
+ * 5-minute window, refuses a call for another environment than the secret
675
+ * key's (`400 environment_mismatch`), routes by `type`, runs your callback
676
+ * and validates what it returns. Pass the raw body exactly as received.
829
677
  */
830
678
  async handleCall(input) {
831
679
  const requestId = input.requestId ?? null;
@@ -849,10 +697,14 @@ var Relume = class _Relume {
849
697
  "The body is not valid JSON."
850
698
  );
851
699
  }
852
- const parsed = parseCall(body);
700
+ const parsed = parseCall(body, this.#environment);
853
701
  if (!parsed.ok) return errorResponse(400, parsed.code, parsed.message);
854
702
  const { call } = parsed;
855
- const context = { type: call.type, requestId };
703
+ const context = {
704
+ type: call.type,
705
+ environment: this.#environment,
706
+ requestId
707
+ };
856
708
  const callbackName = CALLBACK_FOR[call.type];
857
709
  if (!this.#callbacks[callbackName]) {
858
710
  this.#onHandlerError(