@fanvue/builder-sdk 0.4.0 → 0.5.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/README.md +284 -0
- package/dist/bridge/index.d.ts +1 -1
- package/dist/bridge/index.js +2 -2
- package/dist/{bridge-CGVtI3hr.js → bridge-QnF7P4Co.js} +2 -2
- package/dist/{bridge-CGVtI3hr.js.map → bridge-QnF7P4Co.js.map} +1 -1
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +3 -3
- package/dist/{core-BqdYUMrJ.js → core-BhKiA55a.js} +1318 -9
- package/dist/core-BhKiA55a.js.map +1 -0
- package/dist/index-C3CXrRiw.d.ts +1412 -0
- package/dist/index-C3CXrRiw.d.ts.map +1 -0
- package/dist/{index-BRDYLBlc.d.ts → index-CEFYZtlb.d.ts} +2 -2
- package/dist/{index-BRDYLBlc.d.ts.map → index-CEFYZtlb.d.ts.map} +1 -1
- package/dist/{index-Dr-mZ0qP.d.ts → index-cf3ZMnLJ.d.ts} +26 -2
- package/dist/index-cf3ZMnLJ.d.ts.map +1 -0
- package/dist/index-v_6Z4Q0u.d.ts +84 -0
- package/dist/index-v_6Z4Q0u.d.ts.map +1 -0
- package/dist/{schemas-DbyHF7Xi.js → index.cjs-teGsk6HB.js} +1058 -977
- package/dist/index.cjs-teGsk6HB.js.map +1 -0
- package/dist/nextjs/embedded-app/index.d.ts +2 -2
- package/dist/nextjs/embedded-app/index.js +3 -3
- package/dist/nextjs/off-platform/index.d.ts +3 -3
- package/dist/nextjs/off-platform/index.d.ts.map +1 -1
- package/dist/nextjs/off-platform/index.js +4 -4
- package/dist/nextjs/off-platform/index.js.map +1 -1
- package/dist/nextjs-CvVhtMdb.js +144 -0
- package/dist/nextjs-CvVhtMdb.js.map +1 -0
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +3 -3
- package/package.json +1 -1
- package/dist/core-BqdYUMrJ.js.map +0 -1
- package/dist/index-C4ewLil3.d.ts +0 -48
- package/dist/index-C4ewLil3.d.ts.map +0 -1
- package/dist/index-ClbZoV_Z.d.ts +0 -420
- package/dist/index-ClbZoV_Z.d.ts.map +0 -1
- package/dist/index-Dr-mZ0qP.d.ts.map +0 -1
- package/dist/nextjs-B5Tqgt_n.js +0 -80
- package/dist/nextjs-B5Tqgt_n.js.map +0 -1
- package/dist/schemas-DbyHF7Xi.js.map +0 -1
package/README.md
CHANGED
|
@@ -348,6 +348,139 @@ const session = await verifySessionJwt("your-session-secret", jwt);
|
|
|
348
348
|
|
|
349
349
|
</details>
|
|
350
350
|
|
|
351
|
+
## Observability and Machine Auth
|
|
352
|
+
|
|
353
|
+
Privacy-first logging, Sentry scrubbing, machine (cron/queue) authentication, and a
|
|
354
|
+
configuration readiness probe. All exported from the root entrypoint and dependency-free.
|
|
355
|
+
|
|
356
|
+
### Safe structured logging
|
|
357
|
+
|
|
358
|
+
`createSafeLogFields` returns an allowlisting filter: unknown keys are dropped, only
|
|
359
|
+
`string | number | boolean | null` survives (an `Error` or a response body is never
|
|
360
|
+
serialised), UUID-bearing strings become `[redacted-uuid]` — **any** UUID version,
|
|
361
|
+
including v7 — and email-bearing strings become `[redacted-email]`.
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
import { createLogEvent, createSafeLogFields, errorName } from "@fanvue/builder-sdk";
|
|
365
|
+
|
|
366
|
+
const safeLogFields = createSafeLogFields(["wheelId"]); // base keys + your own
|
|
367
|
+
const logEvent = createLogEvent({ prefix: "[spinwheel]", safeLogFields });
|
|
368
|
+
|
|
369
|
+
logEvent("error", "exchange.failed", {
|
|
370
|
+
wheelId: "wheel_1",
|
|
371
|
+
httpStatus: 503,
|
|
372
|
+
errorName: errorName(caught), // class name only, never err.message
|
|
373
|
+
accessToken: "dropped", // not allowlisted
|
|
374
|
+
});
|
|
375
|
+
// [spinwheel] { event: 'exchange.failed', wheelId: 'wheel_1', httpStatus: 503, errorName: 'TypeError' }
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Base allowlist (`BASE_ALLOWED_LOG_KEYS`): `cause`, `code`, `creatorId`, `errorName`,
|
|
379
|
+
`httpStatus`, `reason`, `retryCount`, `status`.
|
|
380
|
+
|
|
381
|
+
### Sentry scrubbing
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
// sentry.server.config.ts
|
|
385
|
+
import { createSentryScrubber } from "@fanvue/builder-sdk";
|
|
386
|
+
|
|
387
|
+
Sentry.init({
|
|
388
|
+
dsn,
|
|
389
|
+
enabled: Boolean(dsn),
|
|
390
|
+
beforeSend: createSentryScrubber(/(prizedetail|fanhmac|seed)/i), // merged with the base pattern
|
|
391
|
+
});
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Recursive, and applies four rules to every node: values under a sensitive key become
|
|
395
|
+
`[redacted]`, UUIDs inside strings become `[redacted-uuid]` (any version, and also when the UUID
|
|
396
|
+
sits behind a `_` or another word character), a string that parses as a URL keeps its origin
|
|
397
|
+
and path but loses its query string, fragment and `user:pass@` userinfo
|
|
398
|
+
(`https://cdn/x?[redacted]`), and email addresses inside strings become `[redacted-email]`.
|
|
399
|
+
Base sensitive keys (`BASE_SENSITIVE_KEY_PATTERN`), matched as case-insensitive substrings:
|
|
400
|
+
`authorization|cookie|token|secret|password|signedurl|api[-_]?key|credential|email|phone|ip[-_]?address`.
|
|
401
|
+
|
|
402
|
+
Total by construction, because a `beforeSend` that throws loses the event and raises inside the
|
|
403
|
+
caller: a repeat visit yields `[circular]`, a node deeper than 32 levels yields `[max-depth]`,
|
|
404
|
+
and a property whose getter throws yields `[unreadable]`. All three fail closed — an untraversed
|
|
405
|
+
value cannot leak.
|
|
406
|
+
|
|
407
|
+
### Machine auth (cron and queue routes)
|
|
408
|
+
|
|
409
|
+
`requireMachineAuth` returns `authorized | unauthorized | not_configured`. The third arm exists
|
|
410
|
+
so a deployment with no usable credential answers 503, never 401 — a broken deploy must not look
|
|
411
|
+
like ordinary auth noise. Bearer secrets must be at least 32 characters (`MINIMUM_BEARER_SECRET_LENGTH`);
|
|
412
|
+
a shorter one is treated as absent rather than compared. Comparison is constant-time over the
|
|
413
|
+
UTF-8 bytes, with a byte-length pre-check. The `Authorization` scheme is required but matched
|
|
414
|
+
case-insensitively (`bearer` and `Bearer` both work, a bare secret does not).
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
// app/api/cron/reconcile/route.ts
|
|
418
|
+
import { boundedBatchSize, requireMachineAuth } from "@fanvue/builder-sdk";
|
|
419
|
+
|
|
420
|
+
export async function POST(request: Request): Promise<Response> {
|
|
421
|
+
const auth = await requireMachineAuth(request, {
|
|
422
|
+
bearerSecret: process.env.CRON_SECRET ?? null,
|
|
423
|
+
signedRequestVerifier: null,
|
|
424
|
+
rawBody: null,
|
|
425
|
+
});
|
|
426
|
+
if (auth.status === "not_configured") return new Response(null, { status: 503 });
|
|
427
|
+
if (auth.status === "unauthorized") return new Response(null, { status: 401 });
|
|
428
|
+
|
|
429
|
+
const limit = boundedBatchSize(new URL(request.url).searchParams.get("limit"), 20, 100);
|
|
430
|
+
// ...
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
A second strategy plugs in via `SignedRequestVerifier`. `@upstash/qstash` is deliberately **not**
|
|
435
|
+
a dependency of this package — the app owns it:
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
import { Receiver } from "@upstash/qstash";
|
|
439
|
+
import type { SignedRequestVerifier } from "@fanvue/builder-sdk";
|
|
440
|
+
|
|
441
|
+
const qstashVerifier: SignedRequestVerifier = {
|
|
442
|
+
verify: async (request, rawBody) => {
|
|
443
|
+
const signature = request.headers.get("upstash-signature");
|
|
444
|
+
const currentSigningKey = process.env.QSTASH_CURRENT_SIGNING_KEY;
|
|
445
|
+
const nextSigningKey = process.env.QSTASH_NEXT_SIGNING_KEY;
|
|
446
|
+
if (!signature || !currentSigningKey || !nextSigningKey) return false; // deny by default
|
|
447
|
+
return new Receiver({ currentSigningKey, nextSigningKey }).verify({
|
|
448
|
+
signature,
|
|
449
|
+
body: new TextDecoder().decode(rawBody),
|
|
450
|
+
url: request.url,
|
|
451
|
+
upstashRegion: request.headers.get("upstash-region") ?? undefined,
|
|
452
|
+
});
|
|
453
|
+
},
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// Pass the exact received bytes — a reparsed body invalidates the signature.
|
|
457
|
+
const rawBody = new Uint8Array(await request.arrayBuffer());
|
|
458
|
+
const auth = await requireMachineAuth(request, {
|
|
459
|
+
bearerSecret: process.env.CRON_SECRET ?? null,
|
|
460
|
+
signedRequestVerifier: qstashVerifier,
|
|
461
|
+
rawBody,
|
|
462
|
+
});
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### Configuration readiness
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
import { configurationReadiness } from "@fanvue/builder-sdk";
|
|
469
|
+
|
|
470
|
+
export function GET(): Response {
|
|
471
|
+
const checks = configurationReadiness(); // env is a parameter; defaults to process.env
|
|
472
|
+
const ready = checks.every((check) => check.ready);
|
|
473
|
+
return Response.json({ ready, checks }, { status: ready ? 200 : 503 });
|
|
474
|
+
}
|
|
475
|
+
// [{ name: 'app_url', ready: false, detail: 'set FANVUE_APP_BASE_URL to an https:// origin' },
|
|
476
|
+
// { name: 'fanvue_oauth', ready: true, detail: null }]
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
`app_url` reads `FANVUE_APP_BASE_URL` (falling back to `APP_BASE_URL`) and requires an https
|
|
480
|
+
origin, because Fanvue refuses to embed anything else. `fanvue_oauth` requires `FANVUE_APP_UUID`,
|
|
481
|
+
`FANVUE_CLIENT_ID`, `FANVUE_CLIENT_SECRET` and `FANVUE_OAUTH_REDIRECT_URI`; details name the
|
|
482
|
+
missing variables and never echo a value.
|
|
483
|
+
|
|
351
484
|
## Environment Variables
|
|
352
485
|
|
|
353
486
|
Add these to your `.env.local` (Next.js) or equivalent:
|
|
@@ -381,6 +514,128 @@ const config = createConfig({
|
|
|
381
514
|
});
|
|
382
515
|
```
|
|
383
516
|
|
|
517
|
+
## Platform Contracts
|
|
518
|
+
|
|
519
|
+
Wire shapes, enums and environment validation shared by every Fanvue app, exported from the root entrypoint (`@fanvue/builder-sdk`). Everything here is runtime-agnostic — safe in a route handler, a worker, or the browser.
|
|
520
|
+
|
|
521
|
+
### `FANVUE_*` environment
|
|
522
|
+
|
|
523
|
+
```env
|
|
524
|
+
FANVUE_APP_UUID= # your app's UUID on Fanvue
|
|
525
|
+
FANVUE_CLIENT_ID=
|
|
526
|
+
FANVUE_CLIENT_SECRET=
|
|
527
|
+
FANVUE_OAUTH_REDIRECT_URI=
|
|
528
|
+
FANVUE_API_BASE_URL=https://api.fanvue.com # optional, shown with defaults
|
|
529
|
+
FANVUE_AUTH_BASE_URL=https://auth.fanvue.com
|
|
530
|
+
FANVUE_WEB_ORIGIN=https://www.fanvue.com
|
|
531
|
+
FANVUE_API_VERSION=2025-06-26
|
|
532
|
+
FANVUE_EXPERIENCE_URL_TEMPLATE= # optional share-link override
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
```ts
|
|
536
|
+
import { fanvueEnv, isFanvueConfigured, flagEnabled } from "@fanvue/builder-sdk";
|
|
537
|
+
|
|
538
|
+
// Parsed on first call and cached; a build never needs the secrets present.
|
|
539
|
+
const apiBaseUrl = fanvueEnv().FANVUE_API_BASE_URL;
|
|
540
|
+
|
|
541
|
+
// The four credentials default to "" so builds pass — check before an OAuth call.
|
|
542
|
+
if (!isFanvueConfigured()) {
|
|
543
|
+
return Response.json(
|
|
544
|
+
{ error: { code: "fanvue_not_configured", message: "Fanvue is not configured." } },
|
|
545
|
+
{ status: 503 },
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Case- and whitespace-insensitive flag reader; warns once on an unreadable value.
|
|
550
|
+
const devMode = flagEnabled("MY_APP_DEV_MODE");
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
Call `resetFanvueEnvCache()` between tests that mutate `process.env`.
|
|
554
|
+
|
|
555
|
+
### Experience postMessage protocol
|
|
556
|
+
|
|
557
|
+
```ts
|
|
558
|
+
import {
|
|
559
|
+
PUBLISH_REQUEST_MESSAGE,
|
|
560
|
+
isPublishResultMessage,
|
|
561
|
+
isFanvueOrigin,
|
|
562
|
+
} from "@fanvue/builder-sdk";
|
|
563
|
+
|
|
564
|
+
window.parent.postMessage({ type: PUBLISH_REQUEST_MESSAGE, token }, platformOrigin);
|
|
565
|
+
|
|
566
|
+
window.addEventListener("message", (event) => {
|
|
567
|
+
// Validate the origin as well as the shape: Fanvue replies with targetOrigin "*".
|
|
568
|
+
if (!isFanvueOrigin(event.origin) || !isPublishResultMessage(event.data)) return;
|
|
569
|
+
if (event.data.status === "published") onPublished(event.data.experienceId);
|
|
570
|
+
});
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
### Access modes and denial reasons
|
|
574
|
+
|
|
575
|
+
```ts
|
|
576
|
+
import { accessModeFromDenialReason } from "@fanvue/builder-sdk";
|
|
577
|
+
|
|
578
|
+
// The exchange fails closed: a denied fan gets a reason but no access mode.
|
|
579
|
+
const accessMode = accessModeFromDenialReason(reason); // 'SUBSCRIPTION' | 'PAID' | 'HIDDEN' | null
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### Error bodies
|
|
583
|
+
|
|
584
|
+
```ts
|
|
585
|
+
import {
|
|
586
|
+
AppErrorEnvelopeSchema,
|
|
587
|
+
NON_SESSION_401_CODES,
|
|
588
|
+
parseFanvueErrorBody,
|
|
589
|
+
} from "@fanvue/builder-sdk";
|
|
590
|
+
|
|
591
|
+
// Normalises all four Fanvue API error-body shapes, preserving `reason`.
|
|
592
|
+
// `reason` is what separates an entitlement refusal from an app-binding mismatch.
|
|
593
|
+
const { message, reason, code } = parseFanvueErrorBody(await fanvueResponse.json());
|
|
594
|
+
|
|
595
|
+
// Client side, against your own app's `{ error: { code, message } }` envelope:
|
|
596
|
+
// a 401 with one of these codes means the Fanvue grant is gone, not the app
|
|
597
|
+
// session, so prompt a reconnect instead of signing the creator out.
|
|
598
|
+
const parsed = AppErrorEnvelopeSchema.safeParse(await appResponse.json());
|
|
599
|
+
const appCode = parsed.success ? parsed.data.error.code : null;
|
|
600
|
+
if (appResponse.status === 401 && (appCode === null || !NON_SESSION_401_CODES.has(appCode))) {
|
|
601
|
+
onSessionExpired();
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### Pagination
|
|
606
|
+
|
|
607
|
+
```ts
|
|
608
|
+
import { cursorPageSchema, offsetPageSchema, clampPageSize } from "@fanvue/builder-sdk";
|
|
609
|
+
|
|
610
|
+
const SubscribersPage = offsetPageSchema(SubscriberSchema); // { data, pagination }
|
|
611
|
+
const PaymentsPage = cursorPageSchema(PaymentSchema); // { data, nextCursor }
|
|
612
|
+
const size = clampPageSize(requested); // the API 400s above 50
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### Contracts API reference
|
|
616
|
+
|
|
617
|
+
| Export | Description |
|
|
618
|
+
|---|---|
|
|
619
|
+
| `FanvueEnvSchema`, `parseFanvueEnv(source)` | Zod schema and pure parser for the `FANVUE_*` contract |
|
|
620
|
+
| `fanvueEnv()`, `resetFanvueEnvCache()` | Lazily parsed, cached `process.env` view, and the test reset |
|
|
621
|
+
| `isFanvueConfigured(env?)` | Whether the four Fanvue credentials are all set |
|
|
622
|
+
| `flagEnabled(name, source?)` | Boolean env-flag reader; trims, lowercases, warns once per flag |
|
|
623
|
+
| `PUBLISH_REQUEST_MESSAGE`, `PUBLISH_RESULT_MESSAGE`, `UNPUBLISH_REQUEST_MESSAGE`, `UNPUBLISH_RESULT_MESSAGE`, `EXPERIENCE_MESSAGE_TYPES` | `fanvue:experience:*` message types |
|
|
624
|
+
| `PublishRequestMessageSchema`, `PublishResultMessageSchema`, `UnpublishRequestMessageSchema`, `UnpublishResultMessageSchema`, `ExperienceMessageSchema` | Zod schemas for the four messages and their discriminated union |
|
|
625
|
+
| `isPublishResultMessage(v)`, `isUnpublishResultMessage(v)` | `safeParse`-backed type guards |
|
|
626
|
+
| `isFanvueOrigin(origin)` | `https` + `fanvue.com` / `*.fanvue.com` origin check |
|
|
627
|
+
| `FANVUE_ACCESS_MODES`, `FanvueAccessModeSchema` | `FREE \| SUBSCRIPTION \| PAID \| HIDDEN` |
|
|
628
|
+
| `EXPERIENCE_DENIAL_REASONS`, `EXPERIENCE_ENTITLED_REASONS` | Every reason the platform reports, documented |
|
|
629
|
+
| `accessModeFromDenialReason(reason)` | Recovers the access mode a denial implies, or `null` |
|
|
630
|
+
| `MAX_PAGE_SIZE`, `DEFAULT_PAGE_SIZE`, `clampPageSize(n)` | Offset page-size limits (50 / 15) and client-side clamping |
|
|
631
|
+
| `OffsetPaginationSchema`, `HybridPaginationSchema` | `{ page, size, hasMore }` and the `v0` subscribers variant |
|
|
632
|
+
| `offsetPageSchema(item)`, `cursorPageSchema(item)` | List-response schema builders |
|
|
633
|
+
| `parseFanvueErrorBody(body)` | Normalises any Fanvue error body to `{ message, reason, code }` |
|
|
634
|
+
| `FanvueErrorBodySchema` (+ the four per-shape schemas) | Zod schemas for the platform's error bodies |
|
|
635
|
+
| `AppErrorEnvelopeSchema` | The app's own `{ error: { code, message } }` envelope |
|
|
636
|
+
| `FANVUE_APP_ERROR_CODES`, `NON_SESSION_401_CODES` | Canonical app-facing codes and the two grant-loss 401s |
|
|
637
|
+
| `OAuthErrorBodySchema` | OAuth error body, normalised to `{ error, errorDescription }` |
|
|
638
|
+
|
|
384
639
|
## Error Handling
|
|
385
640
|
|
|
386
641
|
All async operations return `Result<T, E>` types from [`neverthrow`](https://github.com/supermacro/neverthrow) instead of throwing exceptions. This gives you type-safe, explicit error handling:
|
|
@@ -402,6 +657,16 @@ result
|
|
|
402
657
|
|
|
403
658
|
Error types: `OAuthError` (token exchange/refresh), `EmbeddedAuthError` (delegated authorize-on-behalf), `ApiError` (API requests), `SessionVerifyError` (JWT verification).
|
|
404
659
|
|
|
660
|
+
`TOKEN_EXCHANGE_FAILED` and `TOKEN_REFRESH_FAILED` carry the authorization server's own body as `oauthError: { error, errorDescription } | null` — the only way to tell a retired refresh token from bad client credentials, since both are `400`:
|
|
661
|
+
|
|
662
|
+
```ts
|
|
663
|
+
if (error.code === "TOKEN_REFRESH_FAILED" && error.oauthError?.error === "invalid_grant") {
|
|
664
|
+
// The stored grant is dead: prompt the creator to reconnect.
|
|
665
|
+
}
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
Treat it as a diagnostic, never as end-user copy.
|
|
669
|
+
|
|
405
670
|
## API Reference
|
|
406
671
|
|
|
407
672
|
### Core (`@fanvue/builder-sdk`)
|
|
@@ -420,6 +685,17 @@ Error types: `OAuthError` (token exchange/refresh), `EmbeddedAuthError` (delegat
|
|
|
420
685
|
| `createFanvueClient(accessToken, apiBaseUrl?)` | Create an authenticated API client. |
|
|
421
686
|
| `API_VERSION` | The API version header value (currently `2025-06-26`). |
|
|
422
687
|
| `HEADER_UPDATED_SESSION` | Response header (`X-Updated-Session`) carrying a refreshed session JWT. |
|
|
688
|
+
| `createSafeLogFields(extraAllowedKeys?)` | Build an allowlisting log-field filter. Drops unknown keys and non-scalars; redacts UUID-shaped values. |
|
|
689
|
+
| `createLogEvent(options?)` | Build a structured `logEvent(level, event, fields)` over an allowlist. |
|
|
690
|
+
| `logEvent(level, event, fields?)` | Ready-made structured logger over the base allowlist, prefixed `[fanvue]`. |
|
|
691
|
+
| `errorName(error)` | The `Error` class name (or `typeof`) of a caught value — never its message. |
|
|
692
|
+
| `createSentryScrubber(extraSensitiveKeyPattern?)` | Build a recursive `beforeSend` scrubber for Sentry. |
|
|
693
|
+
| `requireMachineAuth(request, options)` | Authenticate a cron/queue request. Returns `authorized \| unauthorized \| not_configured`. |
|
|
694
|
+
| `boundedBatchSize(requested, defaultSize, max)` | Clamp a caller-supplied batch size to a safe positive bound. |
|
|
695
|
+
| `configurationReadiness(env?)` | Named `app_url` / `fanvue_oauth` configuration checks for a readiness probe. |
|
|
696
|
+
| `BASE_ALLOWED_LOG_KEYS` | The generic log keys every app shares. |
|
|
697
|
+
| `BASE_SENSITIVE_KEY_PATTERN` | Key-name fragments whose values never reach an error tracker. |
|
|
698
|
+
| `MINIMUM_BEARER_SECRET_LENGTH` | Shortest machine-auth bearer secret the SDK will compare (32). |
|
|
423
699
|
|
|
424
700
|
#### Types
|
|
425
701
|
|
|
@@ -435,6 +711,13 @@ Error types: `OAuthError` (token exchange/refresh), `EmbeddedAuthError` (delegat
|
|
|
435
711
|
| `EmbeddedAuthError` | Error from the delegated authorize-on-behalf flow |
|
|
436
712
|
| `ApiError` | Error from API requests |
|
|
437
713
|
| `SessionVerifyError` | Error from session JWT verification |
|
|
714
|
+
| `SafeLogFields` / `SafeLogValue` | The allowlisting filter, and the scalar types that survive it |
|
|
715
|
+
| `LogEvent` / `LogEventOptions` / `LogLevel` / `LogSink` | Structured-logging shapes |
|
|
716
|
+
| `SentryScrubber` | A `beforeSend`-compatible scrubber: `<T>(event: T) => T` |
|
|
717
|
+
| `MachineAuthResult` | `{ status: 'authorized' \| 'unauthorized' \| 'not_configured' }` |
|
|
718
|
+
| `MachineAuthOptions` | Bearer secret, signed-request verifier, and raw body |
|
|
719
|
+
| `SignedRequestVerifier` | Pluggable signed-request strategy (e.g. QStash), implemented by the app |
|
|
720
|
+
| `ReadinessCheck` / `ReadinessEnv` | `{ name, ready, detail }` and the environment shape checks read |
|
|
438
721
|
|
|
439
722
|
### Next.js Embedded App (`@fanvue/builder-sdk/nextjs/embedded-app`)
|
|
440
723
|
|
|
@@ -451,6 +734,7 @@ Error types: `OAuthError` (token exchange/refresh), `EmbeddedAuthError` (delegat
|
|
|
451
734
|
| Export | Description |
|
|
452
735
|
|---|---|
|
|
453
736
|
| `createConfig(opts?)` | Build a `ResolvedConfig` from env vars and/or explicit options |
|
|
737
|
+
| `createConfigSafe(opts?)` | Non-throwing variant: `{ ok: true, config }` or `{ ok: false, missing }` |
|
|
454
738
|
| `createLoginHandler(opts)` | `{ GET }` -- redirects to the OAuth provider |
|
|
455
739
|
| `createCallbackHandler(opts)` | `{ GET, POST }` -- completes the code exchange, sets the session cookie |
|
|
456
740
|
| `createLogoutHandler(opts)` | `{ POST }` -- clears the session cookie |
|
package/dist/bridge/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as bridgeCapabilitySchema, D as bridgeRequestSchema, E as bridgeReadyMessageSchema, O as bridgeResponseSchema, S as analyticsTrackPayloadSchema, T as bridgeHelloMessageSchema, _ as BridgeReadyMessage, a as FanvueBridge, b as analyticsEventNameSchema, c as ANALYTICS_TRACK_METHOD, d as BRIDGE_HELLO_TYPE, f as BRIDGE_READY_TYPE, g as BridgeHelloMessage, h as BridgeErrorCode, i as BridgeRequestError, l as AnalyticsProperties, m as BridgeCapability, n as ConnectFanvueBridgeOptions, o as FanvueBridgeAnalytics, p as BRIDGE_VERSION, r as connectFanvueBridge, s as ANALYTICS_CAPABILITY, t as BridgeConnectError, u as AnalyticsTrackPayload, v as BridgeRequest, w as bridgeErrorCodeSchema, x as analyticsPropertiesSchema, y as BridgeResponse } from "../index-
|
|
1
|
+
import { C as bridgeCapabilitySchema, D as bridgeRequestSchema, E as bridgeReadyMessageSchema, O as bridgeResponseSchema, S as analyticsTrackPayloadSchema, T as bridgeHelloMessageSchema, _ as BridgeReadyMessage, a as FanvueBridge, b as analyticsEventNameSchema, c as ANALYTICS_TRACK_METHOD, d as BRIDGE_HELLO_TYPE, f as BRIDGE_READY_TYPE, g as BridgeHelloMessage, h as BridgeErrorCode, i as BridgeRequestError, l as AnalyticsProperties, m as BridgeCapability, n as ConnectFanvueBridgeOptions, o as FanvueBridgeAnalytics, p as BRIDGE_VERSION, r as connectFanvueBridge, s as ANALYTICS_CAPABILITY, t as BridgeConnectError, u as AnalyticsTrackPayload, v as BridgeRequest, w as bridgeErrorCodeSchema, x as analyticsPropertiesSchema, y as BridgeResponse } from "../index-CEFYZtlb.js";
|
|
2
2
|
export { ANALYTICS_CAPABILITY, ANALYTICS_TRACK_METHOD, AnalyticsProperties, AnalyticsTrackPayload, BRIDGE_HELLO_TYPE, BRIDGE_READY_TYPE, BRIDGE_VERSION, BridgeCapability, BridgeConnectError, BridgeErrorCode, BridgeHelloMessage, BridgeReadyMessage, BridgeRequest, BridgeRequestError, BridgeResponse, ConnectFanvueBridgeOptions, FanvueBridge, FanvueBridgeAnalytics, analyticsEventNameSchema, analyticsPropertiesSchema, analyticsTrackPayloadSchema, bridgeCapabilitySchema, bridgeErrorCodeSchema, bridgeHelloMessageSchema, bridgeReadyMessageSchema, bridgeRequestSchema, bridgeResponseSchema, connectFanvueBridge };
|
package/dist/bridge/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import { a as BRIDGE_READY_TYPE, c as analyticsPropertiesSchema, d as bridgeErrorCodeSchema, f as bridgeHelloMessageSchema, h as bridgeResponseSchema, i as BRIDGE_HELLO_TYPE, l as analyticsTrackPayloadSchema, m as bridgeRequestSchema, n as ANALYTICS_CAPABILITY, o as BRIDGE_VERSION, p as bridgeReadyMessageSchema, r as ANALYTICS_TRACK_METHOD, s as analyticsEventNameSchema, t as connectFanvueBridge, u as bridgeCapabilitySchema } from "../bridge-
|
|
1
|
+
import "../index.cjs-teGsk6HB.js";
|
|
2
|
+
import { a as BRIDGE_READY_TYPE, c as analyticsPropertiesSchema, d as bridgeErrorCodeSchema, f as bridgeHelloMessageSchema, h as bridgeResponseSchema, i as BRIDGE_HELLO_TYPE, l as analyticsTrackPayloadSchema, m as bridgeRequestSchema, n as ANALYTICS_CAPABILITY, o as BRIDGE_VERSION, p as bridgeReadyMessageSchema, r as ANALYTICS_TRACK_METHOD, s as analyticsEventNameSchema, t as connectFanvueBridge, u as bridgeCapabilitySchema } from "../bridge-QnF7P4Co.js";
|
|
3
3
|
export { ANALYTICS_CAPABILITY, ANALYTICS_TRACK_METHOD, BRIDGE_HELLO_TYPE, BRIDGE_READY_TYPE, BRIDGE_VERSION, analyticsEventNameSchema, analyticsPropertiesSchema, analyticsTrackPayloadSchema, bridgeCapabilitySchema, bridgeErrorCodeSchema, bridgeHelloMessageSchema, bridgeReadyMessageSchema, bridgeRequestSchema, bridgeResponseSchema, connectFanvueBridge };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as number, d as record, f as string, g as prettifyError, i as boolean, l as object, m as unknown, n as _enum, o as literal, p as union, r as array, t as require_index_cjs } from "./index.cjs-teGsk6HB.js";
|
|
2
2
|
//#region src/bridge/protocol.ts
|
|
3
3
|
/**
|
|
4
4
|
* The bridge protocol version spoken by this SDK.
|
|
@@ -333,4 +333,4 @@ function connectFanvueBridge(options) {
|
|
|
333
333
|
//#endregion
|
|
334
334
|
export { BRIDGE_READY_TYPE as a, analyticsPropertiesSchema as c, bridgeErrorCodeSchema as d, bridgeHelloMessageSchema as f, bridgeResponseSchema as h, BRIDGE_HELLO_TYPE as i, analyticsTrackPayloadSchema as l, bridgeRequestSchema as m, ANALYTICS_CAPABILITY as n, BRIDGE_VERSION as o, bridgeReadyMessageSchema as p, ANALYTICS_TRACK_METHOD as r, analyticsEventNameSchema as s, connectFanvueBridge as t, bridgeCapabilitySchema as u };
|
|
335
335
|
|
|
336
|
-
//# sourceMappingURL=bridge-
|
|
336
|
+
//# sourceMappingURL=bridge-QnF7P4Co.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-CGVtI3hr.js","names":["z.enum","z.object","z.literal","z.array","z.string","z.unknown","z.boolean","z\n .record","z.union","z.number","z.prettifyError"],"sources":["../src/bridge/protocol.ts","../src/bridge/bridge.ts","../src/bridge/connect.ts"],"sourcesContent":["import { z } from 'zod';\n\n/**\n * The bridge protocol version spoken by this SDK.\n *\n * Both sides send it on every message. Changes within a version must be\n * additive; anything breaking increments the version, and the host answers the\n * handshake with the highest version both sides speak.\n */\nexport const BRIDGE_VERSION = 1;\n\n/**\n * Message type the embedded app posts to the host to open a handshake.\n *\n * An app may re-announce (it re-posts `ready` until a `hello` lands, in case\n * the host attached its listener late).\n */\nexport const BRIDGE_READY_TYPE = 'fanvue:bridge:ready';\n\n/**\n * Message type the host posts back, carrying the granted capabilities and the port.\n *\n * The host answers each verified `ready` with a fresh `hello` and port,\n * closing the prior port; the app adopts the newest `hello`.\n */\nexport const BRIDGE_HELLO_TYPE = 'fanvue:bridge:hello';\n\n/** Capability namespace for analytics (`analytics.track`). */\nexport const ANALYTICS_CAPABILITY = 'analytics';\n\n/** Method name for tracking an analytics event. */\nexport const ANALYTICS_TRACK_METHOD = 'track';\n\n/** The host capabilities an app can be granted. Grants are resolved server-side. */\nexport const bridgeCapabilitySchema = z.enum([ANALYTICS_CAPABILITY]);\n\n/** A host capability an app can be granted. */\nexport type BridgeCapability = z.infer<typeof bridgeCapabilitySchema>;\n\n/**\n * Error codes the host can return for a request.\n *\n * - `capability_denied` — the app was not granted the requested capability.\n * - `invalid_payload` — the payload failed the host's schema for the method.\n * - `rate_limited` — the app exceeded the host's budget for the method.\n * - `internal` — the host handler failed unexpectedly.\n */\nexport const bridgeErrorCodeSchema = z.enum([\n 'capability_denied',\n 'invalid_payload',\n 'rate_limited',\n 'internal',\n]);\n\n/** An error code the host can return for a request. */\nexport type BridgeErrorCode = z.infer<typeof bridgeErrorCodeSchema>;\n\n/** Schema for the `ready` message an app posts to `window.parent`. */\nexport const bridgeReadyMessageSchema = z.object({\n type: z.literal(BRIDGE_READY_TYPE),\n v: z.literal(BRIDGE_VERSION),\n});\n\n/** The `ready` message an app posts to `window.parent` to open a handshake. */\nexport type BridgeReadyMessage = z.infer<typeof bridgeReadyMessageSchema>;\n\n/** Schema for the `hello` message the host posts back with the transferred port. */\nexport const bridgeHelloMessageSchema = z.object({\n type: z.literal(BRIDGE_HELLO_TYPE),\n v: z.literal(BRIDGE_VERSION),\n capabilities: z.array(bridgeCapabilitySchema),\n});\n\n/** The `hello` message the host posts back, alongside the transferred port. */\nexport type BridgeHelloMessage = z.infer<typeof bridgeHelloMessageSchema>;\n\n/**\n * Schema for a request sent app → host over the port.\n *\n * Capability and method names are bounded so a misbehaving sender cannot make\n * either side hold or log arbitrarily large strings.\n */\nexport const bridgeRequestSchema = z.object({\n v: z.literal(BRIDGE_VERSION),\n id: z.string().min(1).max(64),\n kind: z.literal('request'),\n capability: z.string().min(1).max(64),\n method: z.string().min(1).max(64),\n payload: z.unknown(),\n});\n\n/** A request sent app → host over the port, correlated to a response by `id`. */\nexport type BridgeRequest = z.infer<typeof bridgeRequestSchema>;\n\n/**\n * Schema for a response sent host → app over the port.\n *\n * The `id` and `error.message` are bounded like the request fields, so a\n * misbehaving host cannot push oversized strings into the app.\n */\nexport const bridgeResponseSchema = z.object({\n v: z.literal(BRIDGE_VERSION),\n id: z.string().max(64),\n kind: z.literal('response'),\n ok: z.boolean(),\n result: z.unknown().optional(),\n error: z.object({ code: bridgeErrorCodeSchema, message: z.string().max(1024) }).optional(),\n});\n\n/** A response sent host → app over the port, correlated to a request by `id`. */\nexport type BridgeResponse = z.infer<typeof bridgeResponseSchema>;\n\n/**\n * Event names an app may emit.\n *\n * The host prefixes every name with `embedded_app_` before it reaches\n * Amplitude, so an app can never emit a core platform event. Names are\n * snake_case to match the platform's tracking-plan convention.\n */\nexport const analyticsEventNameSchema = z.string().regex(/^[a-z0-9_]{1,64}$/);\n\n/**\n * Property keys reserved by the host.\n *\n * Identity and revenue are host-owned: the host page's Amplitude client is\n * already identified as the viewer, and an app must not be able to overwrite\n * that or fabricate revenue.\n */\nconst ANALYTICS_RESERVED_PROPERTY_KEYS: readonly string[] = ['user_id', 'device_id', 'revenue'];\n\n/** Maximum number of properties on a single event. */\nconst ANALYTICS_MAX_PROPERTY_KEYS = 20;\n\n/** Whether a property key is one an app is allowed to set. */\nfunction isAllowedAnalyticsPropertyKey(key: string): boolean {\n return !ANALYTICS_RESERVED_PROPERTY_KEYS.includes(key) && !key.startsWith('$');\n}\n\n/**\n * Properties attached to an analytics event: a flat record of short scalars.\n *\n * Nesting is rejected so the payload stays cheap to validate and to read in\n * Amplitude, and `$`-prefixed keys are rejected because Amplitude reserves them.\n * Numbers must be finite — `Infinity` and `NaN` do not survive serialisation.\n */\nexport const analyticsPropertiesSchema = z\n .record(\n z.string().min(1).max(64),\n z.union([z.string().max(256), z.number().finite(), z.boolean()]),\n )\n .refine((properties) => Object.keys(properties).length <= ANALYTICS_MAX_PROPERTY_KEYS, {\n message: `An event may carry at most ${ANALYTICS_MAX_PROPERTY_KEYS} properties`,\n })\n .refine((properties) => Object.keys(properties).every(isAllowedAnalyticsPropertyKey), {\n message: `Properties may not use a reserved key (${ANALYTICS_RESERVED_PROPERTY_KEYS.join(', ')}) or a key starting with \"$\"`,\n });\n\n/** Schema for the payload of `analytics.track`. */\nexport const analyticsTrackPayloadSchema = z.object({\n eventName: analyticsEventNameSchema,\n properties: analyticsPropertiesSchema.optional(),\n});\n\n/** The payload of `analytics.track`. */\nexport type AnalyticsTrackPayload = z.infer<typeof analyticsTrackPayloadSchema>;\n\n/** Properties attached to an analytics event. */\nexport type AnalyticsProperties = z.infer<typeof analyticsPropertiesSchema>;\n","import { err, ok } from 'neverthrow';\nimport { z } from 'zod';\n\nimport {\n ANALYTICS_CAPABILITY,\n ANALYTICS_TRACK_METHOD,\n BRIDGE_VERSION,\n analyticsTrackPayloadSchema,\n bridgeResponseSchema,\n type AnalyticsProperties,\n type BridgeCapability,\n type BridgeErrorCode,\n type BridgeRequest,\n type BridgeResponse,\n} from './protocol.js';\n\nimport type { Result } from 'neverthrow';\n\n/**\n * Why a bridge request failed.\n *\n * Beyond the codes the host can return, the SDK adds `TIMEOUT` (the host never\n * answered) and `PORT_CLOSED` (the message could not be put on the port —\n * in practice the payload was not structured-cloneable). Note that\n * `postMessage` on a closed port silently drops rather than throwing, so a\n * torn-down host surfaces as `TIMEOUT`, not `PORT_CLOSED`.\n */\nexport interface BridgeRequestError {\n code: BridgeErrorCode | 'TIMEOUT' | 'PORT_CLOSED';\n message: string;\n}\n\n/** The analytics capability, as exposed on a connected bridge. */\nexport interface FanvueBridgeAnalytics {\n track: (\n eventName: string,\n properties?: AnalyticsProperties,\n ) => Promise<Result<void, BridgeRequestError>>;\n}\n\n/**\n * A live connection to the Fanvue host page.\n *\n * @property capabilities - The capabilities the host granted this app.\n * @property has - Whether a given capability was granted. Checking is\n * optional: calling a capability without checking is safe and returns\n * `capability_denied`.\n * @property analytics - Fires events through the host page's Amplitude client.\n */\nexport interface FanvueBridge {\n capabilities: readonly BridgeCapability[];\n has: (capability: BridgeCapability) => boolean;\n analytics: FanvueBridgeAnalytics;\n}\n\n/**\n * A {@link FanvueBridge} plus the internals `connectFanvueBridge` drives.\n *\n * Not exported from the public entry: apps only ever see {@link FanvueBridge}.\n *\n * @property adoptPort - Swaps the underlying port for the one carried by a\n * newer `hello`, closing the old port. Requests in flight on the old port\n * fail by their existing timeouts.\n */\nexport interface InternalFanvueBridge extends FanvueBridge {\n adoptPort: (port: MessagePort) => void;\n}\n\n/**\n * Everything {@link createFanvueBridge} needs to serve requests.\n *\n * @property port - The `MessagePort` transferred by the host in its `hello`.\n * @property capabilities - The capabilities named in the host's `hello`.\n * @property requestTimeoutMs - How long to wait for a response before failing.\n */\nexport interface FanvueBridgeContext {\n port: MessagePort;\n capabilities: readonly BridgeCapability[];\n requestTimeoutMs: number;\n}\n\ninterface PendingRequest {\n resolve: (response: BridgeResponse) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Builds the {@link FanvueBridge} served over an established port.\n *\n * Requests are correlated to responses by `id`; anything arriving on the port\n * that is not a well-formed response for a request still in flight is ignored,\n * so a misbehaving host cannot resolve a request twice or crash the app.\n *\n * Apps get this from {@link connectFanvueBridge} rather than calling it directly.\n *\n * @param ctx - The port, granted capabilities, and per-request timeout.\n * @returns The bridge exposed to the app.\n */\nexport function createFanvueBridge(ctx: FanvueBridgeContext): InternalFanvueBridge {\n const { capabilities, requestTimeoutMs } = ctx;\n const pending = new Map<string, PendingRequest>();\n let requestSequence = 0;\n let port = ctx.port;\n\n function onPortMessage(event: MessageEvent): void {\n const parsed = bridgeResponseSchema.safeParse(event.data);\n if (!parsed.success) return;\n\n const inFlight = pending.get(parsed.data.id);\n if (!inFlight) return;\n\n pending.delete(parsed.data.id);\n clearTimeout(inFlight.timer);\n inFlight.resolve(parsed.data);\n }\n\n function attach(next: MessagePort): void {\n next.addEventListener('message', onPortMessage);\n next.start();\n }\n attach(port);\n\n function adoptPort(next: MessagePort): void {\n // The host closes its end of the superseded port when it re-answers a\n // ready; close this end too and route all later requests over the new\n // port. Requests still in flight on the old port fail by their timeouts.\n port.close();\n port = next;\n attach(next);\n }\n\n function has(capability: BridgeCapability): boolean {\n return capabilities.includes(capability);\n }\n\n async function request(\n capability: BridgeCapability,\n method: string,\n payload: unknown,\n ): Promise<Result<unknown, BridgeRequestError>> {\n if (!has(capability)) {\n return err({\n code: 'capability_denied',\n message: `The host did not grant the \"${capability}\" capability to this app.`,\n });\n }\n\n requestSequence += 1;\n const id = `${capability}-${requestSequence}`;\n const message: BridgeRequest = {\n v: BRIDGE_VERSION,\n id,\n kind: 'request',\n capability,\n method,\n payload,\n };\n\n try {\n port.postMessage(message);\n } catch (error) {\n return err({\n code: 'PORT_CLOSED',\n message: `Could not send the request to the host: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n\n // Registered after the send: port delivery is always queued as a task, so\n // no response can arrive before this synchronous block finishes.\n const settled = await new Promise<BridgeResponse | null>((resolve) => {\n const timer = setTimeout(() => {\n pending.delete(id);\n resolve(null);\n }, requestTimeoutMs);\n pending.set(id, { resolve, timer });\n });\n if (settled === null) {\n return err({\n code: 'TIMEOUT',\n message: `The host did not answer \"${capability}.${method}\" within ${requestTimeoutMs}ms.`,\n });\n }\n\n if (!settled.ok) {\n return err({\n code: settled.error?.code ?? 'internal',\n message: settled.error?.message ?? 'The host rejected the request without a reason.',\n });\n }\n\n return ok(settled.result);\n }\n\n async function track(\n eventName: string,\n properties?: AnalyticsProperties,\n ): Promise<Result<void, BridgeRequestError>> {\n // Validate before sending so a malformed call fails immediately with a\n // readable message; the host validates again as the authoritative check.\n const payload = analyticsTrackPayloadSchema.safeParse(\n properties === undefined ? { eventName } : { eventName, properties },\n );\n if (!payload.success) {\n return err({ code: 'invalid_payload', message: z.prettifyError(payload.error) });\n }\n\n const result = await request(ANALYTICS_CAPABILITY, ANALYTICS_TRACK_METHOD, payload.data);\n return result.map((): void => undefined);\n }\n\n return {\n capabilities,\n has,\n analytics: { track },\n adoptPort,\n };\n}\n","import { err, ok } from 'neverthrow';\n\nimport { createFanvueBridge, type FanvueBridge, type InternalFanvueBridge } from './bridge.js';\nimport {\n BRIDGE_READY_TYPE,\n BRIDGE_VERSION,\n bridgeHelloMessageSchema,\n type BridgeReadyMessage,\n} from './protocol.js';\n\nimport type { Result } from 'neverthrow';\n\n/** How long to wait for the host's `hello` before giving up. */\nconst DEFAULT_CONNECT_TIMEOUT_MS = 3000;\n\n/** How long to wait for a response to a single request once connected. */\nconst REQUEST_TIMEOUT_MS = 3000;\n\n/**\n * How often to re-post `ready` until the handshake settles.\n *\n * The host attaches its listener asynchronously (its capability grants come\n * from a query), so a single `ready` posted right after the iframe loads can\n * arrive before anyone is listening. Re-announcing until a `hello` lands (or\n * the timeout fires) closes that race.\n */\nconst READY_REANNOUNCE_INTERVAL_MS = 500;\n\n/**\n * Why connecting to the host failed.\n *\n * - `NOT_EMBEDDED` — the app is not running in an iframe, so there is no host.\n * - `TIMEOUT` — no `hello` arrived. The page is framed by something other than\n * Fanvue, or Fanvue refused the handshake (unregistered embed origin).\n */\nexport interface BridgeConnectError {\n code: 'NOT_EMBEDDED' | 'TIMEOUT';\n message: string;\n}\n\n/**\n * Options for {@link connectFanvueBridge}.\n *\n * @property timeoutMs - How long to wait for the host's `hello`. Defaults to 3000.\n */\nexport interface ConnectFanvueBridgeOptions {\n timeoutMs?: number;\n}\n\n/**\n * Opens the capability bridge to the Fanvue page hosting this app.\n *\n * Posts `fanvue:bridge:ready` to `window.parent` and waits for the host's\n * `fanvue:bridge:hello`, which names the granted capabilities and transfers the\n * `MessagePort` all later traffic flows over. The host verifies the frame's\n * origin before replying, so a page framed by anything other than its\n * registered Fanvue surface simply times out.\n *\n * `ready` is re-posted every 500ms until a `hello` lands (or the timeout\n * fires), because the host may attach its listener after the app's first\n * announce. The host answers each verified `ready` with a fresh `hello` and\n * port, closing the prior port — so the bridge keeps listening for the\n * lifetime of the connection and adopts the newest `hello` by swapping onto\n * its port. Requests in flight on a superseded port fail by their timeouts.\n *\n * Calling this again is safe and is how an app reconnects after the iframe\n * reloads: the host closes the previous port and re-handshakes.\n *\n * Failure is not exceptional — apps are expected to keep working standalone\n * (local dev, previews) where the connection never succeeds.\n *\n * @param options - Optional overrides (e.g. a shorter `timeoutMs`).\n * @returns A `Result` with the connected {@link FanvueBridge}, or a\n * {@link BridgeConnectError} when there is no host to talk to.\n *\n * @example\n * const result = await connectFanvueBridge();\n * if (result.isOk() && result.value.has('analytics')) {\n * await result.value.analytics.track('course_created', { chapters: 4 });\n * }\n */\nexport function connectFanvueBridge(\n options?: ConnectFanvueBridgeOptions,\n): Promise<Result<FanvueBridge, BridgeConnectError>> {\n const timeoutMs = options?.timeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;\n\n if (typeof window === 'undefined' || window.parent === window) {\n return Promise.resolve(\n err({\n code: 'NOT_EMBEDDED',\n message:\n 'The app is not running inside a Fanvue iframe, so there is no host to connect to.',\n }),\n );\n }\n const host = window.parent;\n\n return new Promise((resolve) => {\n let settled = false;\n let bridge: InternalFanvueBridge | null = null;\n\n const timer = setTimeout(() => {\n finish(\n err({\n code: 'TIMEOUT',\n message: `The Fanvue host did not complete the handshake within ${timeoutMs}ms.`,\n }),\n );\n }, timeoutMs);\n\n // Re-announce until the handshake settles, in case the host attached its\n // listener after the first `ready`.\n const reannounce = setInterval(postReady, READY_REANNOUNCE_INTERVAL_MS);\n\n function finish(result: Result<FanvueBridge, BridgeConnectError>): void {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n clearInterval(reannounce);\n // Only a failed handshake stops listening. After a `hello` the listener\n // lives for the lifetime of the bridge: the host may answer more than\n // one `ready` (it closes the prior port each time), and the bridge must\n // adopt the newest port to stay on the one the host is serving.\n if (result.isErr()) window.removeEventListener('message', onMessage);\n resolve(result);\n }\n\n function onMessage(event: MessageEvent): void {\n // The host is the only window that can hold this frame's port; anything\n // else on the window channel belongs to another protocol.\n if (event.source !== host) return;\n\n const hello = bridgeHelloMessageSchema.safeParse(event.data);\n if (!hello.success) return;\n\n const port = event.ports[0];\n if (!port) return;\n\n if (bridge) {\n bridge.adoptPort(port);\n return;\n }\n\n bridge = createFanvueBridge({\n port,\n capabilities: hello.data.capabilities,\n requestTimeoutMs: REQUEST_TIMEOUT_MS,\n });\n finish(ok(bridge));\n }\n\n window.addEventListener('message', onMessage);\n\n function postReady(): void {\n const ready: BridgeReadyMessage = { type: BRIDGE_READY_TYPE, v: BRIDGE_VERSION };\n // Targets \"*\" because the app cannot know the host's origin before the\n // handshake, and `ready` carries nothing but the protocol version.\n host.postMessage(ready, '*');\n }\n postReady();\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bridge-QnF7P4Co.js","names":["z.enum","z.object","z.literal","z.array","z.string","z.unknown","z.boolean","z\n .record","z.union","z.number","z.prettifyError"],"sources":["../src/bridge/protocol.ts","../src/bridge/bridge.ts","../src/bridge/connect.ts"],"sourcesContent":["import { z } from 'zod';\n\n/**\n * The bridge protocol version spoken by this SDK.\n *\n * Both sides send it on every message. Changes within a version must be\n * additive; anything breaking increments the version, and the host answers the\n * handshake with the highest version both sides speak.\n */\nexport const BRIDGE_VERSION = 1;\n\n/**\n * Message type the embedded app posts to the host to open a handshake.\n *\n * An app may re-announce (it re-posts `ready` until a `hello` lands, in case\n * the host attached its listener late).\n */\nexport const BRIDGE_READY_TYPE = 'fanvue:bridge:ready';\n\n/**\n * Message type the host posts back, carrying the granted capabilities and the port.\n *\n * The host answers each verified `ready` with a fresh `hello` and port,\n * closing the prior port; the app adopts the newest `hello`.\n */\nexport const BRIDGE_HELLO_TYPE = 'fanvue:bridge:hello';\n\n/** Capability namespace for analytics (`analytics.track`). */\nexport const ANALYTICS_CAPABILITY = 'analytics';\n\n/** Method name for tracking an analytics event. */\nexport const ANALYTICS_TRACK_METHOD = 'track';\n\n/** The host capabilities an app can be granted. Grants are resolved server-side. */\nexport const bridgeCapabilitySchema = z.enum([ANALYTICS_CAPABILITY]);\n\n/** A host capability an app can be granted. */\nexport type BridgeCapability = z.infer<typeof bridgeCapabilitySchema>;\n\n/**\n * Error codes the host can return for a request.\n *\n * - `capability_denied` — the app was not granted the requested capability.\n * - `invalid_payload` — the payload failed the host's schema for the method.\n * - `rate_limited` — the app exceeded the host's budget for the method.\n * - `internal` — the host handler failed unexpectedly.\n */\nexport const bridgeErrorCodeSchema = z.enum([\n 'capability_denied',\n 'invalid_payload',\n 'rate_limited',\n 'internal',\n]);\n\n/** An error code the host can return for a request. */\nexport type BridgeErrorCode = z.infer<typeof bridgeErrorCodeSchema>;\n\n/** Schema for the `ready` message an app posts to `window.parent`. */\nexport const bridgeReadyMessageSchema = z.object({\n type: z.literal(BRIDGE_READY_TYPE),\n v: z.literal(BRIDGE_VERSION),\n});\n\n/** The `ready` message an app posts to `window.parent` to open a handshake. */\nexport type BridgeReadyMessage = z.infer<typeof bridgeReadyMessageSchema>;\n\n/** Schema for the `hello` message the host posts back with the transferred port. */\nexport const bridgeHelloMessageSchema = z.object({\n type: z.literal(BRIDGE_HELLO_TYPE),\n v: z.literal(BRIDGE_VERSION),\n capabilities: z.array(bridgeCapabilitySchema),\n});\n\n/** The `hello` message the host posts back, alongside the transferred port. */\nexport type BridgeHelloMessage = z.infer<typeof bridgeHelloMessageSchema>;\n\n/**\n * Schema for a request sent app → host over the port.\n *\n * Capability and method names are bounded so a misbehaving sender cannot make\n * either side hold or log arbitrarily large strings.\n */\nexport const bridgeRequestSchema = z.object({\n v: z.literal(BRIDGE_VERSION),\n id: z.string().min(1).max(64),\n kind: z.literal('request'),\n capability: z.string().min(1).max(64),\n method: z.string().min(1).max(64),\n payload: z.unknown(),\n});\n\n/** A request sent app → host over the port, correlated to a response by `id`. */\nexport type BridgeRequest = z.infer<typeof bridgeRequestSchema>;\n\n/**\n * Schema for a response sent host → app over the port.\n *\n * The `id` and `error.message` are bounded like the request fields, so a\n * misbehaving host cannot push oversized strings into the app.\n */\nexport const bridgeResponseSchema = z.object({\n v: z.literal(BRIDGE_VERSION),\n id: z.string().max(64),\n kind: z.literal('response'),\n ok: z.boolean(),\n result: z.unknown().optional(),\n error: z.object({ code: bridgeErrorCodeSchema, message: z.string().max(1024) }).optional(),\n});\n\n/** A response sent host → app over the port, correlated to a request by `id`. */\nexport type BridgeResponse = z.infer<typeof bridgeResponseSchema>;\n\n/**\n * Event names an app may emit.\n *\n * The host prefixes every name with `embedded_app_` before it reaches\n * Amplitude, so an app can never emit a core platform event. Names are\n * snake_case to match the platform's tracking-plan convention.\n */\nexport const analyticsEventNameSchema = z.string().regex(/^[a-z0-9_]{1,64}$/);\n\n/**\n * Property keys reserved by the host.\n *\n * Identity and revenue are host-owned: the host page's Amplitude client is\n * already identified as the viewer, and an app must not be able to overwrite\n * that or fabricate revenue.\n */\nconst ANALYTICS_RESERVED_PROPERTY_KEYS: readonly string[] = ['user_id', 'device_id', 'revenue'];\n\n/** Maximum number of properties on a single event. */\nconst ANALYTICS_MAX_PROPERTY_KEYS = 20;\n\n/** Whether a property key is one an app is allowed to set. */\nfunction isAllowedAnalyticsPropertyKey(key: string): boolean {\n return !ANALYTICS_RESERVED_PROPERTY_KEYS.includes(key) && !key.startsWith('$');\n}\n\n/**\n * Properties attached to an analytics event: a flat record of short scalars.\n *\n * Nesting is rejected so the payload stays cheap to validate and to read in\n * Amplitude, and `$`-prefixed keys are rejected because Amplitude reserves them.\n * Numbers must be finite — `Infinity` and `NaN` do not survive serialisation.\n */\nexport const analyticsPropertiesSchema = z\n .record(\n z.string().min(1).max(64),\n z.union([z.string().max(256), z.number().finite(), z.boolean()]),\n )\n .refine((properties) => Object.keys(properties).length <= ANALYTICS_MAX_PROPERTY_KEYS, {\n message: `An event may carry at most ${ANALYTICS_MAX_PROPERTY_KEYS} properties`,\n })\n .refine((properties) => Object.keys(properties).every(isAllowedAnalyticsPropertyKey), {\n message: `Properties may not use a reserved key (${ANALYTICS_RESERVED_PROPERTY_KEYS.join(', ')}) or a key starting with \"$\"`,\n });\n\n/** Schema for the payload of `analytics.track`. */\nexport const analyticsTrackPayloadSchema = z.object({\n eventName: analyticsEventNameSchema,\n properties: analyticsPropertiesSchema.optional(),\n});\n\n/** The payload of `analytics.track`. */\nexport type AnalyticsTrackPayload = z.infer<typeof analyticsTrackPayloadSchema>;\n\n/** Properties attached to an analytics event. */\nexport type AnalyticsProperties = z.infer<typeof analyticsPropertiesSchema>;\n","import { err, ok } from 'neverthrow';\nimport { z } from 'zod';\n\nimport {\n ANALYTICS_CAPABILITY,\n ANALYTICS_TRACK_METHOD,\n BRIDGE_VERSION,\n analyticsTrackPayloadSchema,\n bridgeResponseSchema,\n type AnalyticsProperties,\n type BridgeCapability,\n type BridgeErrorCode,\n type BridgeRequest,\n type BridgeResponse,\n} from './protocol.js';\n\nimport type { Result } from 'neverthrow';\n\n/**\n * Why a bridge request failed.\n *\n * Beyond the codes the host can return, the SDK adds `TIMEOUT` (the host never\n * answered) and `PORT_CLOSED` (the message could not be put on the port —\n * in practice the payload was not structured-cloneable). Note that\n * `postMessage` on a closed port silently drops rather than throwing, so a\n * torn-down host surfaces as `TIMEOUT`, not `PORT_CLOSED`.\n */\nexport interface BridgeRequestError {\n code: BridgeErrorCode | 'TIMEOUT' | 'PORT_CLOSED';\n message: string;\n}\n\n/** The analytics capability, as exposed on a connected bridge. */\nexport interface FanvueBridgeAnalytics {\n track: (\n eventName: string,\n properties?: AnalyticsProperties,\n ) => Promise<Result<void, BridgeRequestError>>;\n}\n\n/**\n * A live connection to the Fanvue host page.\n *\n * @property capabilities - The capabilities the host granted this app.\n * @property has - Whether a given capability was granted. Checking is\n * optional: calling a capability without checking is safe and returns\n * `capability_denied`.\n * @property analytics - Fires events through the host page's Amplitude client.\n */\nexport interface FanvueBridge {\n capabilities: readonly BridgeCapability[];\n has: (capability: BridgeCapability) => boolean;\n analytics: FanvueBridgeAnalytics;\n}\n\n/**\n * A {@link FanvueBridge} plus the internals `connectFanvueBridge` drives.\n *\n * Not exported from the public entry: apps only ever see {@link FanvueBridge}.\n *\n * @property adoptPort - Swaps the underlying port for the one carried by a\n * newer `hello`, closing the old port. Requests in flight on the old port\n * fail by their existing timeouts.\n */\nexport interface InternalFanvueBridge extends FanvueBridge {\n adoptPort: (port: MessagePort) => void;\n}\n\n/**\n * Everything {@link createFanvueBridge} needs to serve requests.\n *\n * @property port - The `MessagePort` transferred by the host in its `hello`.\n * @property capabilities - The capabilities named in the host's `hello`.\n * @property requestTimeoutMs - How long to wait for a response before failing.\n */\nexport interface FanvueBridgeContext {\n port: MessagePort;\n capabilities: readonly BridgeCapability[];\n requestTimeoutMs: number;\n}\n\ninterface PendingRequest {\n resolve: (response: BridgeResponse) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Builds the {@link FanvueBridge} served over an established port.\n *\n * Requests are correlated to responses by `id`; anything arriving on the port\n * that is not a well-formed response for a request still in flight is ignored,\n * so a misbehaving host cannot resolve a request twice or crash the app.\n *\n * Apps get this from {@link connectFanvueBridge} rather than calling it directly.\n *\n * @param ctx - The port, granted capabilities, and per-request timeout.\n * @returns The bridge exposed to the app.\n */\nexport function createFanvueBridge(ctx: FanvueBridgeContext): InternalFanvueBridge {\n const { capabilities, requestTimeoutMs } = ctx;\n const pending = new Map<string, PendingRequest>();\n let requestSequence = 0;\n let port = ctx.port;\n\n function onPortMessage(event: MessageEvent): void {\n const parsed = bridgeResponseSchema.safeParse(event.data);\n if (!parsed.success) return;\n\n const inFlight = pending.get(parsed.data.id);\n if (!inFlight) return;\n\n pending.delete(parsed.data.id);\n clearTimeout(inFlight.timer);\n inFlight.resolve(parsed.data);\n }\n\n function attach(next: MessagePort): void {\n next.addEventListener('message', onPortMessage);\n next.start();\n }\n attach(port);\n\n function adoptPort(next: MessagePort): void {\n // The host closes its end of the superseded port when it re-answers a\n // ready; close this end too and route all later requests over the new\n // port. Requests still in flight on the old port fail by their timeouts.\n port.close();\n port = next;\n attach(next);\n }\n\n function has(capability: BridgeCapability): boolean {\n return capabilities.includes(capability);\n }\n\n async function request(\n capability: BridgeCapability,\n method: string,\n payload: unknown,\n ): Promise<Result<unknown, BridgeRequestError>> {\n if (!has(capability)) {\n return err({\n code: 'capability_denied',\n message: `The host did not grant the \"${capability}\" capability to this app.`,\n });\n }\n\n requestSequence += 1;\n const id = `${capability}-${requestSequence}`;\n const message: BridgeRequest = {\n v: BRIDGE_VERSION,\n id,\n kind: 'request',\n capability,\n method,\n payload,\n };\n\n try {\n port.postMessage(message);\n } catch (error) {\n return err({\n code: 'PORT_CLOSED',\n message: `Could not send the request to the host: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n\n // Registered after the send: port delivery is always queued as a task, so\n // no response can arrive before this synchronous block finishes.\n const settled = await new Promise<BridgeResponse | null>((resolve) => {\n const timer = setTimeout(() => {\n pending.delete(id);\n resolve(null);\n }, requestTimeoutMs);\n pending.set(id, { resolve, timer });\n });\n if (settled === null) {\n return err({\n code: 'TIMEOUT',\n message: `The host did not answer \"${capability}.${method}\" within ${requestTimeoutMs}ms.`,\n });\n }\n\n if (!settled.ok) {\n return err({\n code: settled.error?.code ?? 'internal',\n message: settled.error?.message ?? 'The host rejected the request without a reason.',\n });\n }\n\n return ok(settled.result);\n }\n\n async function track(\n eventName: string,\n properties?: AnalyticsProperties,\n ): Promise<Result<void, BridgeRequestError>> {\n // Validate before sending so a malformed call fails immediately with a\n // readable message; the host validates again as the authoritative check.\n const payload = analyticsTrackPayloadSchema.safeParse(\n properties === undefined ? { eventName } : { eventName, properties },\n );\n if (!payload.success) {\n return err({ code: 'invalid_payload', message: z.prettifyError(payload.error) });\n }\n\n const result = await request(ANALYTICS_CAPABILITY, ANALYTICS_TRACK_METHOD, payload.data);\n return result.map((): void => undefined);\n }\n\n return {\n capabilities,\n has,\n analytics: { track },\n adoptPort,\n };\n}\n","import { err, ok } from 'neverthrow';\n\nimport { createFanvueBridge, type FanvueBridge, type InternalFanvueBridge } from './bridge.js';\nimport {\n BRIDGE_READY_TYPE,\n BRIDGE_VERSION,\n bridgeHelloMessageSchema,\n type BridgeReadyMessage,\n} from './protocol.js';\n\nimport type { Result } from 'neverthrow';\n\n/** How long to wait for the host's `hello` before giving up. */\nconst DEFAULT_CONNECT_TIMEOUT_MS = 3000;\n\n/** How long to wait for a response to a single request once connected. */\nconst REQUEST_TIMEOUT_MS = 3000;\n\n/**\n * How often to re-post `ready` until the handshake settles.\n *\n * The host attaches its listener asynchronously (its capability grants come\n * from a query), so a single `ready` posted right after the iframe loads can\n * arrive before anyone is listening. Re-announcing until a `hello` lands (or\n * the timeout fires) closes that race.\n */\nconst READY_REANNOUNCE_INTERVAL_MS = 500;\n\n/**\n * Why connecting to the host failed.\n *\n * - `NOT_EMBEDDED` — the app is not running in an iframe, so there is no host.\n * - `TIMEOUT` — no `hello` arrived. The page is framed by something other than\n * Fanvue, or Fanvue refused the handshake (unregistered embed origin).\n */\nexport interface BridgeConnectError {\n code: 'NOT_EMBEDDED' | 'TIMEOUT';\n message: string;\n}\n\n/**\n * Options for {@link connectFanvueBridge}.\n *\n * @property timeoutMs - How long to wait for the host's `hello`. Defaults to 3000.\n */\nexport interface ConnectFanvueBridgeOptions {\n timeoutMs?: number;\n}\n\n/**\n * Opens the capability bridge to the Fanvue page hosting this app.\n *\n * Posts `fanvue:bridge:ready` to `window.parent` and waits for the host's\n * `fanvue:bridge:hello`, which names the granted capabilities and transfers the\n * `MessagePort` all later traffic flows over. The host verifies the frame's\n * origin before replying, so a page framed by anything other than its\n * registered Fanvue surface simply times out.\n *\n * `ready` is re-posted every 500ms until a `hello` lands (or the timeout\n * fires), because the host may attach its listener after the app's first\n * announce. The host answers each verified `ready` with a fresh `hello` and\n * port, closing the prior port — so the bridge keeps listening for the\n * lifetime of the connection and adopts the newest `hello` by swapping onto\n * its port. Requests in flight on a superseded port fail by their timeouts.\n *\n * Calling this again is safe and is how an app reconnects after the iframe\n * reloads: the host closes the previous port and re-handshakes.\n *\n * Failure is not exceptional — apps are expected to keep working standalone\n * (local dev, previews) where the connection never succeeds.\n *\n * @param options - Optional overrides (e.g. a shorter `timeoutMs`).\n * @returns A `Result` with the connected {@link FanvueBridge}, or a\n * {@link BridgeConnectError} when there is no host to talk to.\n *\n * @example\n * const result = await connectFanvueBridge();\n * if (result.isOk() && result.value.has('analytics')) {\n * await result.value.analytics.track('course_created', { chapters: 4 });\n * }\n */\nexport function connectFanvueBridge(\n options?: ConnectFanvueBridgeOptions,\n): Promise<Result<FanvueBridge, BridgeConnectError>> {\n const timeoutMs = options?.timeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;\n\n if (typeof window === 'undefined' || window.parent === window) {\n return Promise.resolve(\n err({\n code: 'NOT_EMBEDDED',\n message:\n 'The app is not running inside a Fanvue iframe, so there is no host to connect to.',\n }),\n );\n }\n const host = window.parent;\n\n return new Promise((resolve) => {\n let settled = false;\n let bridge: InternalFanvueBridge | null = null;\n\n const timer = setTimeout(() => {\n finish(\n err({\n code: 'TIMEOUT',\n message: `The Fanvue host did not complete the handshake within ${timeoutMs}ms.`,\n }),\n );\n }, timeoutMs);\n\n // Re-announce until the handshake settles, in case the host attached its\n // listener after the first `ready`.\n const reannounce = setInterval(postReady, READY_REANNOUNCE_INTERVAL_MS);\n\n function finish(result: Result<FanvueBridge, BridgeConnectError>): void {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n clearInterval(reannounce);\n // Only a failed handshake stops listening. After a `hello` the listener\n // lives for the lifetime of the bridge: the host may answer more than\n // one `ready` (it closes the prior port each time), and the bridge must\n // adopt the newest port to stay on the one the host is serving.\n if (result.isErr()) window.removeEventListener('message', onMessage);\n resolve(result);\n }\n\n function onMessage(event: MessageEvent): void {\n // The host is the only window that can hold this frame's port; anything\n // else on the window channel belongs to another protocol.\n if (event.source !== host) return;\n\n const hello = bridgeHelloMessageSchema.safeParse(event.data);\n if (!hello.success) return;\n\n const port = event.ports[0];\n if (!port) return;\n\n if (bridge) {\n bridge.adoptPort(port);\n return;\n }\n\n bridge = createFanvueBridge({\n port,\n capabilities: hello.data.capabilities,\n requestTimeoutMs: REQUEST_TIMEOUT_MS,\n });\n finish(ok(bridge));\n }\n\n window.addEventListener('message', onMessage);\n\n function postReady(): void {\n const ready: BridgeReadyMessage = { type: BRIDGE_READY_TYPE, v: BRIDGE_VERSION };\n // Targets \"*\" because the app cannot know the host's origin before the\n // handshake, and `ready` carries nothing but the protocol version.\n host.postMessage(ready, '*');\n }\n postReady();\n });\n}\n"],"mappings":"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"}
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { API_VERSION, ApiError, AuthorizeOnBehalfResponseSchema, BEARER_PREFIX, DEFAULT_API_BASE_URL, DEFAULT_ISSUER_URL, DEFAULT_PLATFORM_URL, DEFAULT_SCOPES, EmbeddedAuthConfig, EmbeddedAuthError, FanvueClient, FanvueTheme, FanvueUser, FanvueUserSchema, HEADER_UPDATED_SESSION, JsonParseError, OAuthConfig, OAuthError, SessionPayload, SessionPayloadSchema, SessionVerifyError, TokenResponse, TokenResponseSchema, assertFanvueDomain, createAuthorizationUrl, createFanvueClient, createSessionJwt, exchangeCodeForToken, exchangeSessionToken, getSessionTokenFromUrl, getThemeFromUrl, refreshAccessToken, requestAuthorizationCodeOnBehalf, safeJsonParse, verifySessionJwt };
|
|
1
|
+
import { $ as DEFAULT_ISSUER_URL, $t as resetFanvueEnvCache, A as safeJsonParse, At as isUnpublishResultMessage, B as refreshAccessToken, Bt as FanvueStringListErrorBodySchema, C as requireMachineAuth, Ct as UNPUBLISH_RESULT_MESSAGE, D as FanvueUserSchema, Dt as UnpublishResultMessageSchema, E as AuthorizeOnBehalfResponseSchema, Et as UnpublishResultMessage, F as requestAuthorizationCodeOnBehalf, Ft as FanvueErrorBody, G as JsonParseError, Gt as parseFanvueErrorBody, H as EmbeddedAuthConfig, Ht as NormalisedFanvueError, I as createSessionJwt, It as FanvueErrorBodySchema, J as SessionPayload, Jt as FanvueEnvSchema, K as OAuthConfig, Kt as EnvSource, L as verifySessionJwt, Lt as FanvueErrorFieldBodySchema, M as exchangeSessionToken, Mt as AppErrorEnvelopeSchema, N as getSessionTokenFromUrl, Nt as FANVUE_APP_ERROR_CODES, O as SessionPayloadSchema, Ot as isFanvueOrigin, P as getThemeFromUrl, Pt as FanvueAppErrorCode, Q as DEFAULT_API_BASE_URL, Qt as parseFanvueEnv, R as createAuthorizationUrl, Rt as FanvueIssueListErrorBodySchema, S as boundedBatchSize, St as UNPUBLISH_REQUEST_MESSAGE, T as createFanvueClient, Tt as UnpublishRequestMessageSchema, U as EmbeddedAuthError, Ut as OAuthErrorBody, V as ApiError, Vt as NON_SESSION_401_CODES, W as FanvueUser, Wt as OAuthErrorBodySchema, X as TokenResponse, Xt as flagEnabled, Y as SessionVerifyError, Yt as fanvueEnv, Z as API_VERSION, Zt as isFanvueConfigured, _ as logEvent, _t as PUBLISH_RESULT_MESSAGE, a as ReadinessEnv, an as FanvueAccessMode, at as HybridPaginationSchema, b as MachineAuthResult, bt as PublishResultMessage, c as LogEvent, cn as BEARER_PREFIX, ct as OffsetPaginationSchema, d as LogSink, dt as offsetPageSchema, en as EXPERIENCE_DENIAL_REASONS, et as DEFAULT_PLATFORM_URL, f as SafeLogFields, ft as EXPERIENCE_MESSAGE_TYPES, g as errorName, gt as PUBLISH_REQUEST_MESSAGE, h as createSafeLogFields, ht as ExperienceMessageType, i as ReadinessCheck, in as FANVUE_ACCESS_MODES, it as HybridPagination, j as FanvueTheme, jt as AppErrorEnvelope, k as TokenResponseSchema, kt as isPublishResultMessage, l as LogEventOptions, ln as HEADER_UPDATED_SESSION, lt as clampPageSize, m as createLogEvent, mt as ExperienceMessageSchema, n as SentryScrubber, nn as ExperienceDenialReason, nt as assertFanvueDomain, o as configurationReadiness, on as FanvueAccessModeSchema, ot as MAX_PAGE_SIZE, p as SafeLogValue, pt as ExperienceMessage, q as OAuthError, qt as FanvueEnv, r as createSentryScrubber, rn as ExperienceEntitledReason, rt as DEFAULT_PAGE_SIZE, s as BASE_ALLOWED_LOG_KEYS, sn as accessModeFromDenialReason, st as OffsetPagination, t as BASE_SENSITIVE_KEY_PATTERN, tn as EXPERIENCE_ENTITLED_REASONS, tt as DEFAULT_SCOPES, u as LogLevel, ut as cursorPageSchema, v as MINIMUM_BEARER_SECRET_LENGTH, vt as PublishRequestMessage, w as FanvueClient, wt as UnpublishRequestMessage, x as SignedRequestVerifier, xt as PublishResultMessageSchema, y as MachineAuthOptions, yt as PublishRequestMessageSchema, z as exchangeCodeForToken, zt as FanvueMessageErrorBodySchema } from "../index-C3CXrRiw.js";
|
|
2
|
+
export { API_VERSION, ApiError, AppErrorEnvelope, AppErrorEnvelopeSchema, AuthorizeOnBehalfResponseSchema, BASE_ALLOWED_LOG_KEYS, BASE_SENSITIVE_KEY_PATTERN, BEARER_PREFIX, DEFAULT_API_BASE_URL, DEFAULT_ISSUER_URL, DEFAULT_PAGE_SIZE, DEFAULT_PLATFORM_URL, DEFAULT_SCOPES, EXPERIENCE_DENIAL_REASONS, EXPERIENCE_ENTITLED_REASONS, EXPERIENCE_MESSAGE_TYPES, EmbeddedAuthConfig, EmbeddedAuthError, EnvSource, ExperienceDenialReason, ExperienceEntitledReason, ExperienceMessage, ExperienceMessageSchema, ExperienceMessageType, FANVUE_ACCESS_MODES, FANVUE_APP_ERROR_CODES, FanvueAccessMode, FanvueAccessModeSchema, FanvueAppErrorCode, FanvueClient, FanvueEnv, FanvueEnvSchema, FanvueErrorBody, FanvueErrorBodySchema, FanvueErrorFieldBodySchema, FanvueIssueListErrorBodySchema, FanvueMessageErrorBodySchema, FanvueStringListErrorBodySchema, FanvueTheme, FanvueUser, FanvueUserSchema, HEADER_UPDATED_SESSION, HybridPagination, HybridPaginationSchema, JsonParseError, LogEvent, LogEventOptions, LogLevel, LogSink, MAX_PAGE_SIZE, MINIMUM_BEARER_SECRET_LENGTH, MachineAuthOptions, MachineAuthResult, NON_SESSION_401_CODES, NormalisedFanvueError, OAuthConfig, OAuthError, OAuthErrorBody, OAuthErrorBodySchema, OffsetPagination, OffsetPaginationSchema, PUBLISH_REQUEST_MESSAGE, PUBLISH_RESULT_MESSAGE, PublishRequestMessage, PublishRequestMessageSchema, PublishResultMessage, PublishResultMessageSchema, ReadinessCheck, ReadinessEnv, SafeLogFields, SafeLogValue, SentryScrubber, SessionPayload, SessionPayloadSchema, SessionVerifyError, SignedRequestVerifier, TokenResponse, TokenResponseSchema, UNPUBLISH_REQUEST_MESSAGE, UNPUBLISH_RESULT_MESSAGE, UnpublishRequestMessage, UnpublishRequestMessageSchema, UnpublishResultMessage, UnpublishResultMessageSchema, accessModeFromDenialReason, assertFanvueDomain, boundedBatchSize, clampPageSize, configurationReadiness, createAuthorizationUrl, createFanvueClient, createLogEvent, createSafeLogFields, createSentryScrubber, createSessionJwt, cursorPageSchema, errorName, exchangeCodeForToken, exchangeSessionToken, fanvueEnv, flagEnabled, getSessionTokenFromUrl, getThemeFromUrl, isFanvueConfigured, isFanvueOrigin, isPublishResultMessage, isUnpublishResultMessage, logEvent, offsetPageSchema, parseFanvueEnv, parseFanvueErrorBody, refreshAccessToken, requestAuthorizationCodeOnBehalf, requireMachineAuth, resetFanvueEnvCache, safeJsonParse, verifySessionJwt };
|
package/dist/core/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "../
|
|
2
|
-
import { C as
|
|
3
|
-
export { API_VERSION, AuthorizeOnBehalfResponseSchema, BEARER_PREFIX, DEFAULT_API_BASE_URL, DEFAULT_ISSUER_URL, DEFAULT_PLATFORM_URL, DEFAULT_SCOPES, FanvueUserSchema, HEADER_UPDATED_SESSION, SessionPayloadSchema, TokenResponseSchema, assertFanvueDomain, createAuthorizationUrl, createFanvueClient, createSessionJwt, exchangeCodeForToken, exchangeSessionToken, getSessionTokenFromUrl, getThemeFromUrl, refreshAccessToken, requestAuthorizationCodeOnBehalf, safeJsonParse, verifySessionJwt };
|
|
1
|
+
import "../index.cjs-teGsk6HB.js";
|
|
2
|
+
import { $ as FanvueStringListErrorBodySchema, A as OffsetPaginationSchema, B as UNPUBLISH_REQUEST_MESSAGE, C as FanvueUserSchema, D as DEFAULT_PAGE_SIZE, E as safeJsonParse, F as ExperienceMessageSchema, G as isPublishResultMessage, H as UnpublishRequestMessageSchema, I as PUBLISH_REQUEST_MESSAGE, J as FANVUE_APP_ERROR_CODES, K as isUnpublishResultMessage, L as PUBLISH_RESULT_MESSAGE, M as cursorPageSchema, N as offsetPageSchema, O as HybridPaginationSchema, P as EXPERIENCE_MESSAGE_TYPES, Q as FanvueMessageErrorBodySchema, R as PublishRequestMessageSchema, S as AuthorizeOnBehalfResponseSchema, T as TokenResponseSchema, U as UnpublishResultMessageSchema, V as UNPUBLISH_RESULT_MESSAGE, W as isFanvueOrigin, X as FanvueErrorFieldBodySchema, Y as FanvueErrorBodySchema, Z as FanvueIssueListErrorBodySchema, _ as createSessionJwt, _t as FANVUE_ACCESS_MODES, a as createLogEvent, at as flagEnabled, b as exchangeCodeForToken, bt as BEARER_PREFIX, c as logEvent, ct as resetFanvueEnvCache, d as requireMachineAuth, dt as DEFAULT_ISSUER_URL, et as NON_SESSION_401_CODES, f as createFanvueClient, ft as DEFAULT_PLATFORM_URL, g as requestAuthorizationCodeOnBehalf, gt as EXPERIENCE_ENTITLED_REASONS, h as getThemeFromUrl, ht as EXPERIENCE_DENIAL_REASONS, i as BASE_ALLOWED_LOG_KEYS, it as fanvueEnv, j as clampPageSize, k as MAX_PAGE_SIZE, l as MINIMUM_BEARER_SECRET_LENGTH, lt as API_VERSION, m as getSessionTokenFromUrl, mt as assertFanvueDomain, n as createSentryScrubber, nt as parseFanvueErrorBody, o as createSafeLogFields, ot as isFanvueConfigured, p as exchangeSessionToken, pt as DEFAULT_SCOPES, q as AppErrorEnvelopeSchema, r as configurationReadiness, rt as FanvueEnvSchema, s as errorName, st as parseFanvueEnv, t as BASE_SENSITIVE_KEY_PATTERN, tt as OAuthErrorBodySchema, u as boundedBatchSize, ut as DEFAULT_API_BASE_URL, v as verifySessionJwt, vt as FanvueAccessModeSchema, w as SessionPayloadSchema, x as refreshAccessToken, xt as HEADER_UPDATED_SESSION, y as createAuthorizationUrl, yt as accessModeFromDenialReason, z as PublishResultMessageSchema } from "../core-BhKiA55a.js";
|
|
3
|
+
export { API_VERSION, AppErrorEnvelopeSchema, AuthorizeOnBehalfResponseSchema, BASE_ALLOWED_LOG_KEYS, BASE_SENSITIVE_KEY_PATTERN, BEARER_PREFIX, DEFAULT_API_BASE_URL, DEFAULT_ISSUER_URL, DEFAULT_PAGE_SIZE, DEFAULT_PLATFORM_URL, DEFAULT_SCOPES, EXPERIENCE_DENIAL_REASONS, EXPERIENCE_ENTITLED_REASONS, EXPERIENCE_MESSAGE_TYPES, ExperienceMessageSchema, FANVUE_ACCESS_MODES, FANVUE_APP_ERROR_CODES, FanvueAccessModeSchema, FanvueEnvSchema, FanvueErrorBodySchema, FanvueErrorFieldBodySchema, FanvueIssueListErrorBodySchema, FanvueMessageErrorBodySchema, FanvueStringListErrorBodySchema, FanvueUserSchema, HEADER_UPDATED_SESSION, HybridPaginationSchema, MAX_PAGE_SIZE, MINIMUM_BEARER_SECRET_LENGTH, NON_SESSION_401_CODES, OAuthErrorBodySchema, OffsetPaginationSchema, PUBLISH_REQUEST_MESSAGE, PUBLISH_RESULT_MESSAGE, PublishRequestMessageSchema, PublishResultMessageSchema, SessionPayloadSchema, TokenResponseSchema, UNPUBLISH_REQUEST_MESSAGE, UNPUBLISH_RESULT_MESSAGE, UnpublishRequestMessageSchema, UnpublishResultMessageSchema, accessModeFromDenialReason, assertFanvueDomain, boundedBatchSize, clampPageSize, configurationReadiness, createAuthorizationUrl, createFanvueClient, createLogEvent, createSafeLogFields, createSentryScrubber, createSessionJwt, cursorPageSchema, errorName, exchangeCodeForToken, exchangeSessionToken, fanvueEnv, flagEnabled, getSessionTokenFromUrl, getThemeFromUrl, isFanvueConfigured, isFanvueOrigin, isPublishResultMessage, isUnpublishResultMessage, logEvent, offsetPageSchema, parseFanvueEnv, parseFanvueErrorBody, refreshAccessToken, requestAuthorizationCodeOnBehalf, requireMachineAuth, resetFanvueEnvCache, safeJsonParse, verifySessionJwt };
|