@superwall/core 0.1.5 → 0.2.1
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/errors.d.ts +49 -0
- package/dist/errors.js +69 -0
- package/dist/hosts.d.ts +3 -0
- package/dist/hosts.js +33 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.js +4 -0
- package/dist/wire.d.ts +26 -0
- package/dist/wire.js +28 -0
- package/package.json +17 -5
- package/.turbo/turbo-build.log +0 -3
- package/.turbo/turbo-test.log +0 -12
- package/.turbo/turbo-typecheck.log +0 -1
- package/src/errors.ts +0 -88
- package/src/hosts.ts +0 -40
- package/src/index.ts +0 -4
- package/src/types.ts +0 -92
- package/src/wire.test.ts +0 -45
- package/src/wire.ts +0 -58
- package/tsconfig.json +0 -7
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export type SuperwallErrorCode = "NETWORK" | "AUTH" | "NOT_FOUND" | "TIMEOUT" | "DECODING";
|
|
2
|
+
export declare class SuperwallError extends Error {
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly code: SuperwallErrorCode;
|
|
5
|
+
constructor(message: string, code: SuperwallErrorCode);
|
|
6
|
+
}
|
|
7
|
+
export declare class SuperwallNetworkError extends SuperwallError {
|
|
8
|
+
readonly name = "SuperwallNetworkError";
|
|
9
|
+
readonly status?: number;
|
|
10
|
+
readonly url?: string;
|
|
11
|
+
readonly cause?: unknown;
|
|
12
|
+
constructor(message: string, opts?: {
|
|
13
|
+
status?: number;
|
|
14
|
+
url?: string;
|
|
15
|
+
cause?: unknown;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export declare class SuperwallAuthError extends SuperwallError {
|
|
19
|
+
readonly name = "SuperwallAuthError";
|
|
20
|
+
readonly url?: string;
|
|
21
|
+
constructor(message: string, opts?: {
|
|
22
|
+
url?: string;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export declare class SuperwallNotFoundError extends SuperwallError {
|
|
26
|
+
readonly name = "SuperwallNotFoundError";
|
|
27
|
+
readonly url?: string;
|
|
28
|
+
constructor(message: string, opts?: {
|
|
29
|
+
url?: string;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export declare class SuperwallTimeoutError extends SuperwallError {
|
|
33
|
+
readonly name = "SuperwallTimeoutError";
|
|
34
|
+
readonly url?: string;
|
|
35
|
+
readonly timeoutMs?: number;
|
|
36
|
+
constructor(message: string, opts?: {
|
|
37
|
+
url?: string;
|
|
38
|
+
timeoutMs?: number;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
export declare class SuperwallDecodingError extends SuperwallError {
|
|
42
|
+
readonly name = "SuperwallDecodingError";
|
|
43
|
+
readonly url?: string;
|
|
44
|
+
readonly cause?: unknown;
|
|
45
|
+
constructor(message: string, opts?: {
|
|
46
|
+
url?: string;
|
|
47
|
+
cause?: unknown;
|
|
48
|
+
});
|
|
49
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Plain error classes shared between browser and server SDKs. The browser
|
|
2
|
+
// SDK has additional Effect-flavored variants in its own `internal/errors.ts`
|
|
3
|
+
// — those translate to these classes at the runtime boundary. Server SDK
|
|
4
|
+
// uses these directly.
|
|
5
|
+
export class SuperwallError extends Error {
|
|
6
|
+
name = "SuperwallError";
|
|
7
|
+
code;
|
|
8
|
+
constructor(message, code) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.code = code;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class SuperwallNetworkError extends SuperwallError {
|
|
14
|
+
name = "SuperwallNetworkError";
|
|
15
|
+
status;
|
|
16
|
+
url;
|
|
17
|
+
cause;
|
|
18
|
+
constructor(message, opts = {}) {
|
|
19
|
+
super(message, "NETWORK");
|
|
20
|
+
if (opts.status !== undefined)
|
|
21
|
+
this.status = opts.status;
|
|
22
|
+
if (opts.url !== undefined)
|
|
23
|
+
this.url = opts.url;
|
|
24
|
+
if (opts.cause !== undefined)
|
|
25
|
+
this.cause = opts.cause;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class SuperwallAuthError extends SuperwallError {
|
|
29
|
+
name = "SuperwallAuthError";
|
|
30
|
+
url;
|
|
31
|
+
constructor(message, opts = {}) {
|
|
32
|
+
super(message, "AUTH");
|
|
33
|
+
if (opts.url !== undefined)
|
|
34
|
+
this.url = opts.url;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export class SuperwallNotFoundError extends SuperwallError {
|
|
38
|
+
name = "SuperwallNotFoundError";
|
|
39
|
+
url;
|
|
40
|
+
constructor(message, opts = {}) {
|
|
41
|
+
super(message, "NOT_FOUND");
|
|
42
|
+
if (opts.url !== undefined)
|
|
43
|
+
this.url = opts.url;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export class SuperwallTimeoutError extends SuperwallError {
|
|
47
|
+
name = "SuperwallTimeoutError";
|
|
48
|
+
url;
|
|
49
|
+
timeoutMs;
|
|
50
|
+
constructor(message, opts = {}) {
|
|
51
|
+
super(message, "TIMEOUT");
|
|
52
|
+
if (opts.url !== undefined)
|
|
53
|
+
this.url = opts.url;
|
|
54
|
+
if (opts.timeoutMs !== undefined)
|
|
55
|
+
this.timeoutMs = opts.timeoutMs;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export class SuperwallDecodingError extends SuperwallError {
|
|
59
|
+
name = "SuperwallDecodingError";
|
|
60
|
+
url;
|
|
61
|
+
cause;
|
|
62
|
+
constructor(message, opts = {}) {
|
|
63
|
+
super(message, "DECODING");
|
|
64
|
+
if (opts.url !== undefined)
|
|
65
|
+
this.url = opts.url;
|
|
66
|
+
if (opts.cause !== undefined)
|
|
67
|
+
this.cause = opts.cause;
|
|
68
|
+
}
|
|
69
|
+
}
|
package/dist/hosts.d.ts
ADDED
package/dist/hosts.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const RELEASE_HOSTS = {
|
|
2
|
+
base: "api.superwall.me",
|
|
3
|
+
collector: "collector.superwall.com",
|
|
4
|
+
enrichment: "enrichment-api.superwall.com",
|
|
5
|
+
subscriptions: "subscriptions-api.superwall.com",
|
|
6
|
+
};
|
|
7
|
+
const RC_HOSTS = {
|
|
8
|
+
base: "api.superwallcanary.com",
|
|
9
|
+
collector: "collector.superwallcanary.com",
|
|
10
|
+
enrichment: "enrichment-api.superwall.dev",
|
|
11
|
+
subscriptions: "subscriptions-api.superwall.dev",
|
|
12
|
+
};
|
|
13
|
+
const DEV_HOSTS = {
|
|
14
|
+
base: "api.superwall.dev",
|
|
15
|
+
collector: "collector.superwall.com",
|
|
16
|
+
enrichment: "enrichment-api.superwall.dev",
|
|
17
|
+
subscriptions: "subscriptions-api.superwall.dev",
|
|
18
|
+
};
|
|
19
|
+
export const resolveHosts = (env) => {
|
|
20
|
+
if (typeof env === "string") {
|
|
21
|
+
switch (env) {
|
|
22
|
+
case "release":
|
|
23
|
+
return RELEASE_HOSTS;
|
|
24
|
+
case "releaseCandidate":
|
|
25
|
+
return RC_HOSTS;
|
|
26
|
+
case "developer":
|
|
27
|
+
return DEV_HOSTS;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return env.custom;
|
|
31
|
+
};
|
|
32
|
+
// Custom environments are typically internal proxies → assume production.
|
|
33
|
+
export const isSandbox = (env) => typeof env === "string" ? env !== "release" : false;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
2
|
+
[key: string]: JsonValue;
|
|
3
|
+
};
|
|
4
|
+
export type SubscriptionStatus = {
|
|
5
|
+
status: "UNKNOWN";
|
|
6
|
+
} | {
|
|
7
|
+
status: "INACTIVE";
|
|
8
|
+
} | {
|
|
9
|
+
status: "ACTIVE";
|
|
10
|
+
entitlements: Entitlement[];
|
|
11
|
+
};
|
|
12
|
+
export type ProductStore = "appStore" | "stripe" | "paddle" | "playStore" | "superwall" | "other";
|
|
13
|
+
export type LatestSubscriptionState = "inGracePeriod" | "subscribed" | "expired" | "inBillingRetryPeriod" | "revoked";
|
|
14
|
+
export type LatestSubscriptionOfferType = "trial" | "code" | "promotional" | "winback";
|
|
15
|
+
export interface Entitlement {
|
|
16
|
+
id: string;
|
|
17
|
+
type: "SERVICE_LEVEL";
|
|
18
|
+
isActive: boolean;
|
|
19
|
+
productIds: string[];
|
|
20
|
+
latestProductId?: string;
|
|
21
|
+
store?: ProductStore;
|
|
22
|
+
startsAt?: number;
|
|
23
|
+
renewedAt?: number;
|
|
24
|
+
expiresAt?: number;
|
|
25
|
+
isLifetime?: boolean;
|
|
26
|
+
willRenew?: boolean;
|
|
27
|
+
state?: LatestSubscriptionState;
|
|
28
|
+
offerType?: LatestSubscriptionOfferType;
|
|
29
|
+
}
|
|
30
|
+
export interface Entitlements {
|
|
31
|
+
active: Entitlement[];
|
|
32
|
+
inactive: Entitlement[];
|
|
33
|
+
all: Entitlement[];
|
|
34
|
+
}
|
|
35
|
+
export type PaywallPresentationStyle = {
|
|
36
|
+
type: "MODAL";
|
|
37
|
+
} | {
|
|
38
|
+
type: "FULLSCREEN";
|
|
39
|
+
} | {
|
|
40
|
+
type: "NO_ANIMATION";
|
|
41
|
+
} | {
|
|
42
|
+
type: "PUSH";
|
|
43
|
+
} | {
|
|
44
|
+
type: "DRAWER";
|
|
45
|
+
height: number;
|
|
46
|
+
cornerRadius: number;
|
|
47
|
+
} | {
|
|
48
|
+
type: "POPUP";
|
|
49
|
+
height: number;
|
|
50
|
+
width: number;
|
|
51
|
+
cornerRadius: number;
|
|
52
|
+
} | {
|
|
53
|
+
type: "NONE";
|
|
54
|
+
};
|
|
55
|
+
export interface CustomEnvironmentHosts {
|
|
56
|
+
base: string;
|
|
57
|
+
collector: string;
|
|
58
|
+
enrichment: string;
|
|
59
|
+
subscriptions: string;
|
|
60
|
+
}
|
|
61
|
+
export type NetworkEnvironment = "release" | "releaseCandidate" | "developer" | {
|
|
62
|
+
custom: CustomEnvironmentHosts;
|
|
63
|
+
};
|
|
64
|
+
export interface EnvironmentHosts {
|
|
65
|
+
readonly base: string;
|
|
66
|
+
readonly collector: string;
|
|
67
|
+
readonly enrichment: string;
|
|
68
|
+
readonly subscriptions: string;
|
|
69
|
+
}
|
package/dist/types.js
ADDED
package/dist/wire.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Entitlements } from "./types.js";
|
|
2
|
+
export interface WebEntitlementsResponse {
|
|
3
|
+
readonly customerInfo?: {
|
|
4
|
+
entitlements?: ReadonlyArray<WireEntitlement>;
|
|
5
|
+
};
|
|
6
|
+
readonly entitlements?: ReadonlyArray<WireEntitlement>;
|
|
7
|
+
/** Short-lived (≈1h) Superwall-signed JWT asserting the active entitlements,
|
|
8
|
+
* for offline server-side verification via `@superwall/verify`. Best-effort:
|
|
9
|
+
* the backend omits it when signing is unavailable (e.g. no key configured
|
|
10
|
+
* in that environment), so treat its absence gracefully. */
|
|
11
|
+
readonly entitlementsToken?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface WireEntitlement {
|
|
14
|
+
/** BE wire shape uses `identifier`; older/other shapes use `id`. */
|
|
15
|
+
identifier?: string;
|
|
16
|
+
id?: string;
|
|
17
|
+
isActive?: boolean;
|
|
18
|
+
productIds?: string[];
|
|
19
|
+
type?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Normalize a `WebEntitlementsResponse` into a domain `Entitlements`
|
|
23
|
+
* bucket. Prefers the `entitlements` top-level array if present, falls
|
|
24
|
+
* back to `customerInfo.entitlements`.
|
|
25
|
+
*/
|
|
26
|
+
export declare const parseEntitlements: (res: WebEntitlementsResponse) => Entitlements;
|
package/dist/wire.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const toDomain = (e) => ({
|
|
2
|
+
id: e.identifier ?? e.id ?? "",
|
|
3
|
+
type: "SERVICE_LEVEL",
|
|
4
|
+
isActive: e.isActive ?? false,
|
|
5
|
+
productIds: e.productIds ?? [],
|
|
6
|
+
});
|
|
7
|
+
/**
|
|
8
|
+
* Normalize a `WebEntitlementsResponse` into a domain `Entitlements`
|
|
9
|
+
* bucket. Prefers the `entitlements` top-level array if present, falls
|
|
10
|
+
* back to `customerInfo.entitlements`.
|
|
11
|
+
*/
|
|
12
|
+
export const parseEntitlements = (res) => {
|
|
13
|
+
// The BE sends `entitlements: []` (empty) at the top level and the real
|
|
14
|
+
// ones under `customerInfo.entitlements`. A plain `??` would pick the
|
|
15
|
+
// empty top-level array (it isn't nullish) and drop the real ones — so
|
|
16
|
+
// only fall back to the top level when customerInfo has nothing.
|
|
17
|
+
const wire = res.customerInfo?.entitlements && res.customerInfo.entitlements.length > 0
|
|
18
|
+
? res.customerInfo.entitlements
|
|
19
|
+
: res.entitlements && res.entitlements.length > 0
|
|
20
|
+
? res.entitlements
|
|
21
|
+
: (res.customerInfo?.entitlements ?? res.entitlements ?? []);
|
|
22
|
+
const all = wire.map(toDomain);
|
|
23
|
+
return {
|
|
24
|
+
active: all.filter((e) => e.isActive),
|
|
25
|
+
inactive: all.filter((e) => !e.isActive),
|
|
26
|
+
all,
|
|
27
|
+
};
|
|
28
|
+
};
|
package/package.json
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superwall/core",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"sideEffects": false,
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"module": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
6
16
|
"exports": {
|
|
7
17
|
".": {
|
|
8
|
-
"
|
|
9
|
-
"
|
|
18
|
+
"@superwall/source": "./src/index.ts",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js",
|
|
21
|
+
"default": "./dist/index.js"
|
|
10
22
|
}
|
|
11
23
|
},
|
|
12
24
|
"scripts": {
|
|
13
25
|
"test": "bun test",
|
|
14
26
|
"typecheck": "tsc --noEmit",
|
|
15
|
-
"build": "
|
|
16
|
-
"clean": "rm -rf node_modules .turbo *.tsbuildinfo"
|
|
27
|
+
"build": "bun run ../../scripts/build-package.ts",
|
|
28
|
+
"clean": "rm -rf dist node_modules .turbo *.tsbuildinfo"
|
|
17
29
|
},
|
|
18
30
|
"devDependencies": {
|
|
19
31
|
"@types/bun": "latest",
|
package/.turbo/turbo-build.log
DELETED
package/.turbo/turbo-test.log
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
$ bun test
|
|
2
|
-
bun test v1.3.11 (af24e281)
|
|
3
|
-
|
|
4
|
-
src/wire.test.ts:
|
|
5
|
-
(pass) parseEntitlements reads the BE `identifier` field (not `id`) [0.40ms]
|
|
6
|
-
(pass) parseEntitlements: isActive:false → inactive bucket, id still resolved [0.03ms]
|
|
7
|
-
(pass) parseEntitlements: legacy `id` field still works [0.02ms]
|
|
8
|
-
|
|
9
|
-
3 pass
|
|
10
|
-
0 fail
|
|
11
|
-
7 expect() calls
|
|
12
|
-
Ran 3 tests across 1 file. [14.00ms]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
$ tsc --noEmit
|
package/src/errors.ts
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
// Plain error classes shared between browser and server SDKs. The browser
|
|
2
|
-
// SDK has additional Effect-flavored variants in its own `internal/errors.ts`
|
|
3
|
-
// — those translate to these classes at the runtime boundary. Server SDK
|
|
4
|
-
// uses these directly.
|
|
5
|
-
|
|
6
|
-
export type SuperwallErrorCode =
|
|
7
|
-
| "NETWORK"
|
|
8
|
-
| "AUTH"
|
|
9
|
-
| "NOT_FOUND"
|
|
10
|
-
| "TIMEOUT"
|
|
11
|
-
| "DECODING";
|
|
12
|
-
|
|
13
|
-
export class SuperwallError extends Error {
|
|
14
|
-
override readonly name: string = "SuperwallError";
|
|
15
|
-
readonly code: SuperwallErrorCode;
|
|
16
|
-
|
|
17
|
-
constructor(message: string, code: SuperwallErrorCode) {
|
|
18
|
-
super(message);
|
|
19
|
-
this.code = code;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export class SuperwallNetworkError extends SuperwallError {
|
|
24
|
-
override readonly name = "SuperwallNetworkError";
|
|
25
|
-
readonly status?: number;
|
|
26
|
-
readonly url?: string;
|
|
27
|
-
override readonly cause?: unknown;
|
|
28
|
-
|
|
29
|
-
constructor(
|
|
30
|
-
message: string,
|
|
31
|
-
opts: { status?: number; url?: string; cause?: unknown } = {},
|
|
32
|
-
) {
|
|
33
|
-
super(message, "NETWORK");
|
|
34
|
-
if (opts.status !== undefined) this.status = opts.status;
|
|
35
|
-
if (opts.url !== undefined) this.url = opts.url;
|
|
36
|
-
if (opts.cause !== undefined) this.cause = opts.cause;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export class SuperwallAuthError extends SuperwallError {
|
|
41
|
-
override readonly name = "SuperwallAuthError";
|
|
42
|
-
readonly url?: string;
|
|
43
|
-
|
|
44
|
-
constructor(message: string, opts: { url?: string } = {}) {
|
|
45
|
-
super(message, "AUTH");
|
|
46
|
-
if (opts.url !== undefined) this.url = opts.url;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export class SuperwallNotFoundError extends SuperwallError {
|
|
51
|
-
override readonly name = "SuperwallNotFoundError";
|
|
52
|
-
readonly url?: string;
|
|
53
|
-
|
|
54
|
-
constructor(message: string, opts: { url?: string } = {}) {
|
|
55
|
-
super(message, "NOT_FOUND");
|
|
56
|
-
if (opts.url !== undefined) this.url = opts.url;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export class SuperwallTimeoutError extends SuperwallError {
|
|
61
|
-
override readonly name = "SuperwallTimeoutError";
|
|
62
|
-
readonly url?: string;
|
|
63
|
-
readonly timeoutMs?: number;
|
|
64
|
-
|
|
65
|
-
constructor(
|
|
66
|
-
message: string,
|
|
67
|
-
opts: { url?: string; timeoutMs?: number } = {},
|
|
68
|
-
) {
|
|
69
|
-
super(message, "TIMEOUT");
|
|
70
|
-
if (opts.url !== undefined) this.url = opts.url;
|
|
71
|
-
if (opts.timeoutMs !== undefined) this.timeoutMs = opts.timeoutMs;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export class SuperwallDecodingError extends SuperwallError {
|
|
76
|
-
override readonly name = "SuperwallDecodingError";
|
|
77
|
-
readonly url?: string;
|
|
78
|
-
override readonly cause?: unknown;
|
|
79
|
-
|
|
80
|
-
constructor(
|
|
81
|
-
message: string,
|
|
82
|
-
opts: { url?: string; cause?: unknown } = {},
|
|
83
|
-
) {
|
|
84
|
-
super(message, "DECODING");
|
|
85
|
-
if (opts.url !== undefined) this.url = opts.url;
|
|
86
|
-
if (opts.cause !== undefined) this.cause = opts.cause;
|
|
87
|
-
}
|
|
88
|
-
}
|
package/src/hosts.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import type { EnvironmentHosts, NetworkEnvironment } from "./types.ts";
|
|
2
|
-
|
|
3
|
-
const RELEASE_HOSTS: EnvironmentHosts = {
|
|
4
|
-
base: "api.superwall.me",
|
|
5
|
-
collector: "collector.superwall.com",
|
|
6
|
-
enrichment: "enrichment-api.superwall.com",
|
|
7
|
-
subscriptions: "subscriptions-api.superwall.com",
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
const RC_HOSTS: EnvironmentHosts = {
|
|
11
|
-
base: "api.superwallcanary.com",
|
|
12
|
-
collector: "collector.superwallcanary.com",
|
|
13
|
-
enrichment: "enrichment-api.superwall.dev",
|
|
14
|
-
subscriptions: "subscriptions-api.superwall.dev",
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const DEV_HOSTS: EnvironmentHosts = {
|
|
18
|
-
base: "api.superwall.dev",
|
|
19
|
-
collector: "collector.superwall.dev",
|
|
20
|
-
enrichment: "enrichment-api.superwall.dev",
|
|
21
|
-
subscriptions: "subscriptions-api.superwall.dev",
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export const resolveHosts = (env: NetworkEnvironment): EnvironmentHosts => {
|
|
25
|
-
if (typeof env === "string") {
|
|
26
|
-
switch (env) {
|
|
27
|
-
case "release":
|
|
28
|
-
return RELEASE_HOSTS;
|
|
29
|
-
case "releaseCandidate":
|
|
30
|
-
return RC_HOSTS;
|
|
31
|
-
case "developer":
|
|
32
|
-
return DEV_HOSTS;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return env.custom;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
// Custom environments are typically internal proxies → assume production.
|
|
39
|
-
export const isSandbox = (env: NetworkEnvironment): boolean =>
|
|
40
|
-
typeof env === "string" ? env !== "release" : false;
|
package/src/index.ts
DELETED
package/src/types.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
// Cross-SDK domain types. Anything shared between @superwall/paywalls-js
|
|
2
|
-
// (browser) and @superwall/server lives here. Browser-only and server-only
|
|
3
|
-
// types stay in their respective packages.
|
|
4
|
-
|
|
5
|
-
export type JsonValue =
|
|
6
|
-
| string
|
|
7
|
-
| number
|
|
8
|
-
| boolean
|
|
9
|
-
| null
|
|
10
|
-
| JsonValue[]
|
|
11
|
-
| { [key: string]: JsonValue };
|
|
12
|
-
|
|
13
|
-
// Subscription & entitlements
|
|
14
|
-
|
|
15
|
-
export type SubscriptionStatus =
|
|
16
|
-
| { status: "UNKNOWN" }
|
|
17
|
-
| { status: "INACTIVE" }
|
|
18
|
-
| { status: "ACTIVE"; entitlements: Entitlement[] };
|
|
19
|
-
|
|
20
|
-
export type ProductStore =
|
|
21
|
-
| "appStore"
|
|
22
|
-
| "stripe"
|
|
23
|
-
| "paddle"
|
|
24
|
-
| "playStore"
|
|
25
|
-
| "superwall"
|
|
26
|
-
| "other";
|
|
27
|
-
|
|
28
|
-
export type LatestSubscriptionState =
|
|
29
|
-
| "inGracePeriod"
|
|
30
|
-
| "subscribed"
|
|
31
|
-
| "expired"
|
|
32
|
-
| "inBillingRetryPeriod"
|
|
33
|
-
| "revoked";
|
|
34
|
-
|
|
35
|
-
export type LatestSubscriptionOfferType =
|
|
36
|
-
| "trial"
|
|
37
|
-
| "code"
|
|
38
|
-
| "promotional"
|
|
39
|
-
| "winback";
|
|
40
|
-
|
|
41
|
-
export interface Entitlement {
|
|
42
|
-
id: string;
|
|
43
|
-
type: "SERVICE_LEVEL";
|
|
44
|
-
isActive: boolean;
|
|
45
|
-
productIds: string[];
|
|
46
|
-
latestProductId?: string;
|
|
47
|
-
store?: ProductStore;
|
|
48
|
-
startsAt?: number;
|
|
49
|
-
renewedAt?: number;
|
|
50
|
-
expiresAt?: number;
|
|
51
|
-
isLifetime?: boolean;
|
|
52
|
-
willRenew?: boolean;
|
|
53
|
-
state?: LatestSubscriptionState;
|
|
54
|
-
offerType?: LatestSubscriptionOfferType;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export interface Entitlements {
|
|
58
|
-
active: Entitlement[];
|
|
59
|
-
inactive: Entitlement[];
|
|
60
|
-
all: Entitlement[];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export type PaywallPresentationStyle =
|
|
64
|
-
| { type: "MODAL" }
|
|
65
|
-
| { type: "FULLSCREEN" }
|
|
66
|
-
| { type: "NO_ANIMATION" }
|
|
67
|
-
| { type: "PUSH" }
|
|
68
|
-
| { type: "DRAWER"; height: number; cornerRadius: number }
|
|
69
|
-
| { type: "POPUP"; height: number; width: number; cornerRadius: number }
|
|
70
|
-
| { type: "NONE" };
|
|
71
|
-
|
|
72
|
-
// Network environment selector. Both SDKs accept this on construction.
|
|
73
|
-
|
|
74
|
-
export interface CustomEnvironmentHosts {
|
|
75
|
-
base: string;
|
|
76
|
-
collector: string;
|
|
77
|
-
enrichment: string;
|
|
78
|
-
subscriptions: string;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export type NetworkEnvironment =
|
|
82
|
-
| "release"
|
|
83
|
-
| "releaseCandidate"
|
|
84
|
-
| "developer"
|
|
85
|
-
| { custom: CustomEnvironmentHosts };
|
|
86
|
-
|
|
87
|
-
export interface EnvironmentHosts {
|
|
88
|
-
readonly base: string;
|
|
89
|
-
readonly collector: string;
|
|
90
|
-
readonly enrichment: string;
|
|
91
|
-
readonly subscriptions: string;
|
|
92
|
-
}
|
package/src/wire.test.ts
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { test, expect } from "bun:test";
|
|
2
|
-
import { parseEntitlements, type WebEntitlementsResponse } from "./wire.ts";
|
|
3
|
-
|
|
4
|
-
test("parseEntitlements reads the BE `identifier` field (not `id`)", () => {
|
|
5
|
-
// Real BE shape: customerInfo.entitlements[].identifier + isActive.
|
|
6
|
-
const res: WebEntitlementsResponse = {
|
|
7
|
-
entitlements: [],
|
|
8
|
-
customerInfo: {
|
|
9
|
-
entitlements: [
|
|
10
|
-
{
|
|
11
|
-
identifier: "best",
|
|
12
|
-
type: "SERVICE_LEVEL",
|
|
13
|
-
isActive: true,
|
|
14
|
-
productIds: ["test:price_a", "test:price_b"],
|
|
15
|
-
},
|
|
16
|
-
],
|
|
17
|
-
},
|
|
18
|
-
};
|
|
19
|
-
const out = parseEntitlements(res);
|
|
20
|
-
expect(out.all).toHaveLength(1);
|
|
21
|
-
expect(out.all[0]!.id).toBe("best");
|
|
22
|
-
expect(out.active.map((e) => e.id)).toEqual(["best"]);
|
|
23
|
-
expect(out.inactive).toHaveLength(0);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test("parseEntitlements: isActive:false → inactive bucket, id still resolved", () => {
|
|
27
|
-
const res: WebEntitlementsResponse = {
|
|
28
|
-
customerInfo: {
|
|
29
|
-
entitlements: [
|
|
30
|
-
{ identifier: "best", isActive: false, productIds: ["p"] },
|
|
31
|
-
],
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
const out = parseEntitlements(res);
|
|
35
|
-
expect(out.active).toHaveLength(0);
|
|
36
|
-
expect(out.inactive[0]!.id).toBe("best");
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test("parseEntitlements: legacy `id` field still works", () => {
|
|
40
|
-
const res: WebEntitlementsResponse = {
|
|
41
|
-
entitlements: [{ id: "pro", isActive: true, productIds: [] }],
|
|
42
|
-
};
|
|
43
|
-
const out = parseEntitlements(res);
|
|
44
|
-
expect(out.all[0]!.id).toBe("pro");
|
|
45
|
-
});
|
package/src/wire.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import type { Entitlement, Entitlements } from "./types.ts";
|
|
2
|
-
|
|
3
|
-
// Wire shape returned by GET /subscriptions-api/public/v1/users/{id}/entitlements.
|
|
4
|
-
// All fields optional — backend may return either a `customerInfo` envelope
|
|
5
|
-
// or a flat `entitlements` array. Domain types tighten these up.
|
|
6
|
-
export interface WebEntitlementsResponse {
|
|
7
|
-
readonly customerInfo?: {
|
|
8
|
-
entitlements?: ReadonlyArray<WireEntitlement>;
|
|
9
|
-
};
|
|
10
|
-
readonly entitlements?: ReadonlyArray<WireEntitlement>;
|
|
11
|
-
/** Short-lived (≈1h) Superwall-signed JWT asserting the active entitlements,
|
|
12
|
-
* for offline server-side verification via `@superwall/verify`. Best-effort:
|
|
13
|
-
* the backend omits it when signing is unavailable (e.g. no key configured
|
|
14
|
-
* in that environment), so treat its absence gracefully. */
|
|
15
|
-
readonly entitlementsToken?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface WireEntitlement {
|
|
19
|
-
/** BE wire shape uses `identifier`; older/other shapes use `id`. */
|
|
20
|
-
identifier?: string;
|
|
21
|
-
id?: string;
|
|
22
|
-
isActive?: boolean;
|
|
23
|
-
productIds?: string[];
|
|
24
|
-
type?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const toDomain = (e: WireEntitlement): Entitlement => ({
|
|
28
|
-
id: e.identifier ?? e.id ?? "",
|
|
29
|
-
type: "SERVICE_LEVEL",
|
|
30
|
-
isActive: e.isActive ?? false,
|
|
31
|
-
productIds: e.productIds ?? [],
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Normalize a `WebEntitlementsResponse` into a domain `Entitlements`
|
|
36
|
-
* bucket. Prefers the `entitlements` top-level array if present, falls
|
|
37
|
-
* back to `customerInfo.entitlements`.
|
|
38
|
-
*/
|
|
39
|
-
export const parseEntitlements = (
|
|
40
|
-
res: WebEntitlementsResponse,
|
|
41
|
-
): Entitlements => {
|
|
42
|
-
// The BE sends `entitlements: []` (empty) at the top level and the real
|
|
43
|
-
// ones under `customerInfo.entitlements`. A plain `??` would pick the
|
|
44
|
-
// empty top-level array (it isn't nullish) and drop the real ones — so
|
|
45
|
-
// only fall back to the top level when customerInfo has nothing.
|
|
46
|
-
const wire =
|
|
47
|
-
res.customerInfo?.entitlements && res.customerInfo.entitlements.length > 0
|
|
48
|
-
? res.customerInfo.entitlements
|
|
49
|
-
: res.entitlements && res.entitlements.length > 0
|
|
50
|
-
? res.entitlements
|
|
51
|
-
: (res.customerInfo?.entitlements ?? res.entitlements ?? []);
|
|
52
|
-
const all = wire.map(toDomain);
|
|
53
|
-
return {
|
|
54
|
-
active: all.filter((e) => e.isActive),
|
|
55
|
-
inactive: all.filter((e) => !e.isActive),
|
|
56
|
-
all,
|
|
57
|
-
};
|
|
58
|
-
};
|