wp-native-client 0.0.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.
Files changed (59) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +41 -0
  3. package/dist/abilities/catalog.d.ts +49 -0
  4. package/dist/abilities/catalog.d.ts.map +1 -0
  5. package/dist/abilities/catalog.js +65 -0
  6. package/dist/abilities/catalog.js.map +1 -0
  7. package/dist/abilities/discovery.d.ts +43 -0
  8. package/dist/abilities/discovery.d.ts.map +1 -0
  9. package/dist/abilities/discovery.js +82 -0
  10. package/dist/abilities/discovery.js.map +1 -0
  11. package/dist/abilities/index.d.ts +7 -0
  12. package/dist/abilities/index.d.ts.map +1 -0
  13. package/dist/abilities/index.js +6 -0
  14. package/dist/abilities/index.js.map +1 -0
  15. package/dist/abilities/types.d.ts +88 -0
  16. package/dist/abilities/types.d.ts.map +1 -0
  17. package/dist/abilities/types.js +15 -0
  18. package/dist/abilities/types.js.map +1 -0
  19. package/dist/client.d.ts +110 -0
  20. package/dist/client.d.ts.map +1 -0
  21. package/dist/client.js +142 -0
  22. package/dist/client.js.map +1 -0
  23. package/dist/index.d.ts +16 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +13 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/transports/auth-fetch.d.ts +132 -0
  28. package/dist/transports/auth-fetch.d.ts.map +1 -0
  29. package/dist/transports/auth-fetch.js +217 -0
  30. package/dist/transports/auth-fetch.js.map +1 -0
  31. package/dist/transports/fetch.d.ts +42 -0
  32. package/dist/transports/fetch.d.ts.map +1 -0
  33. package/dist/transports/fetch.js +68 -0
  34. package/dist/transports/fetch.js.map +1 -0
  35. package/dist/transports/types.d.ts +22 -0
  36. package/dist/transports/types.d.ts.map +1 -0
  37. package/dist/transports/types.js +9 -0
  38. package/dist/transports/types.js.map +1 -0
  39. package/dist/transports/wp-api-fetch.d.ts +25 -0
  40. package/dist/transports/wp-api-fetch.d.ts.map +1 -0
  41. package/dist/transports/wp-api-fetch.js +40 -0
  42. package/dist/transports/wp-api-fetch.js.map +1 -0
  43. package/dist/wordpress.d.ts +8 -0
  44. package/dist/wordpress.d.ts.map +1 -0
  45. package/dist/wordpress.js +8 -0
  46. package/dist/wordpress.js.map +1 -0
  47. package/package.json +45 -0
  48. package/src/__smoke__/discovery.smoke.ts +66 -0
  49. package/src/abilities/catalog.ts +75 -0
  50. package/src/abilities/discovery.ts +111 -0
  51. package/src/abilities/index.ts +18 -0
  52. package/src/abilities/types.ts +98 -0
  53. package/src/client.ts +191 -0
  54. package/src/index.ts +32 -0
  55. package/src/transports/auth-fetch.ts +310 -0
  56. package/src/transports/fetch.ts +107 -0
  57. package/src/transports/types.ts +24 -0
  58. package/src/transports/wp-api-fetch.ts +58 -0
  59. package/src/wordpress.ts +8 -0
package/dist/client.js ADDED
@@ -0,0 +1,142 @@
1
+ /**
2
+ * WPNativeClient — universal WordPress client built on the Abilities API.
3
+ *
4
+ * One client. No subclasses. No per-site wrappers.
5
+ *
6
+ * The client wraps a Transport (FetchTransport, AuthFetchTransport, or
7
+ * WpApiFetchTransport) and exposes two surfaces:
8
+ *
9
+ * 1. Discovery — fetch the site's ability catalog at startup
10
+ * 2. Execution — call abilities by name with typed input/output
11
+ *
12
+ * Site-specific concerns (extrachill/* abilities, woocommerce/* abilities,
13
+ * etc.) are addressed via ability namespacing on the server side. The
14
+ * client itself never knows the difference between a core WP ability and
15
+ * a plugin-registered one — they're all just strings + JSON Schemas.
16
+ *
17
+ * Example:
18
+ *
19
+ * import { WPNativeClient, AuthFetchTransport } from 'wp-native-client';
20
+ *
21
+ * const client = new WPNativeClient(
22
+ * new AuthFetchTransport({ baseUrl: 'https://example.com/wp-json', ... })
23
+ * );
24
+ *
25
+ * await client.discover();
26
+ *
27
+ * const posts = await client.execute<Post[]>('wp/post.list', { per_page: 20 });
28
+ * const me = await client.execute<User>('wp-native/user.me');
29
+ */
30
+ import { ApiError } from './transports/fetch';
31
+ import { discoverAbilities } from './abilities/discovery';
32
+ const RUN_PATH_PREFIX = 'wp-abilities/v1/abilities';
33
+ export class WPNativeClient {
34
+ transport;
35
+ config;
36
+ _catalog = null;
37
+ constructor(transport, config = {}) {
38
+ this.transport = transport;
39
+ this.config = {
40
+ validateAbilityNames: config.validateAbilityNames ?? true,
41
+ };
42
+ }
43
+ /**
44
+ * Walk the Abilities API and populate the in-memory catalog.
45
+ *
46
+ * Call once at app startup, after auth is established. Subsequent
47
+ * calls replace the catalog (cheap to re-run if the server registers
48
+ * new abilities at runtime).
49
+ *
50
+ * Optionally filter to a subset by category — useful when bootstrapping
51
+ * a minimal client that only needs auth abilities.
52
+ */
53
+ async discover(options = {}) {
54
+ const filter = {};
55
+ if (options.category !== undefined) {
56
+ filter.category = options.category;
57
+ }
58
+ const catalog = await discoverAbilities(this.transport, filter);
59
+ this._catalog = catalog;
60
+ return catalog;
61
+ }
62
+ /**
63
+ * The current catalog. Throws if discover() has not been called.
64
+ *
65
+ * Use catalogOrNull() if you want to feature-detect without throwing.
66
+ */
67
+ get catalog() {
68
+ if (!this._catalog) {
69
+ throw new Error('WPNativeClient: catalog not loaded. Call discover() before accessing catalog.');
70
+ }
71
+ return this._catalog;
72
+ }
73
+ /**
74
+ * Non-throwing accessor for the catalog. Returns null if discover()
75
+ * has not been called.
76
+ */
77
+ catalogOrNull() {
78
+ return this._catalog;
79
+ }
80
+ /**
81
+ * Whether discover() has populated the catalog.
82
+ */
83
+ hasCatalog() {
84
+ return this._catalog !== null;
85
+ }
86
+ /**
87
+ * Execute an ability by name.
88
+ *
89
+ * The ability is looked up in the catalog (when validateAbilityNames is
90
+ * enabled), then POSTed to /wp-abilities/v1/abilities/{name}/run with
91
+ * the given input as the request body's `input` field.
92
+ *
93
+ * The response shape `{ result: TResult }` is unwrapped — callers receive
94
+ * the result value directly.
95
+ *
96
+ * Throws:
97
+ * - Error if the ability is not in the catalog (and validation enabled)
98
+ * - ApiError if the server returns a non-2xx response
99
+ */
100
+ async execute(name, input) {
101
+ if (this.config.validateAbilityNames && this._catalog) {
102
+ if (!this._catalog.has(name)) {
103
+ throw new Error(`WPNativeClient: ability "${name}" is not registered on this site. ` +
104
+ `Run discover() to refresh the catalog, or use executeUnchecked() ` +
105
+ `to bypass validation.`);
106
+ }
107
+ }
108
+ return this.executeUnchecked(name, input);
109
+ }
110
+ /**
111
+ * Execute an ability without checking the catalog.
112
+ *
113
+ * Use this for the auth bootstrap path (login → discover) where the
114
+ * client must call abilities before the catalog exists. For all other
115
+ * call sites, prefer execute() so missing abilities fail loudly at
116
+ * the call site.
117
+ */
118
+ async executeUnchecked(name, input) {
119
+ const path = `${RUN_PATH_PREFIX}/${encodeURIComponent(name)}/run`;
120
+ const body = {
121
+ input: input === undefined ? null : input,
122
+ };
123
+ const response = await this.transport.request({
124
+ path,
125
+ method: 'POST',
126
+ body: body,
127
+ });
128
+ if (!response || typeof response !== 'object' || !('result' in response)) {
129
+ throw new ApiError(`WPNativeClient: malformed ability response for "${name}". ` +
130
+ `Expected { result: ... }.`, 'malformed_ability_response', 500);
131
+ }
132
+ return response.result;
133
+ }
134
+ /**
135
+ * Get a single ability descriptor from the catalog.
136
+ * Returns undefined if not registered or catalog not loaded.
137
+ */
138
+ describe(name) {
139
+ return this._catalog?.get(name);
140
+ }
141
+ }
142
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * wp-native-client — universal WordPress client built on the Abilities API.
3
+ *
4
+ * One client. Discovery + execution. No per-site wrappers.
5
+ */
6
+ export { WPNativeClient } from './client';
7
+ export type { WPNativeClientConfig } from './client';
8
+ export { AbilityCatalog } from './abilities/catalog';
9
+ export { discoverAbilities, fetchAbilitiesPage, fetchAbility, fetchAbilityCategories, } from './abilities/discovery';
10
+ export type { AbilityDescriptor, AbilityCategory, AbilityExecutionResponse, AbilityListPage, AbilityListParams, } from './abilities/types';
11
+ export type { Transport, TransportRequest, TransportResponse } from './transports/types';
12
+ export { FetchTransport, ApiError } from './transports/fetch';
13
+ export type { FetchTransportConfig } from './transports/fetch';
14
+ export { AuthFetchTransport } from './transports/auth-fetch';
15
+ export type { AuthFetchTransportConfig, StoredTokens } from './transports/auth-fetch';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,YAAY,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAGrD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,iBAAiB,EACjB,eAAe,EACf,wBAAwB,EACxB,eAAe,EACf,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAG3B,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACzF,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9D,YAAY,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,wBAAwB,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * wp-native-client — universal WordPress client built on the Abilities API.
3
+ *
4
+ * One client. Discovery + execution. No per-site wrappers.
5
+ */
6
+ // Client
7
+ export { WPNativeClient } from './client';
8
+ // Abilities subsystem
9
+ export { AbilityCatalog } from './abilities/catalog';
10
+ export { discoverAbilities, fetchAbilitiesPage, fetchAbility, fetchAbilityCategories, } from './abilities/discovery';
11
+ export { FetchTransport, ApiError } from './transports/fetch';
12
+ export { AuthFetchTransport } from './transports/auth-fetch';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,SAAS;AACT,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAG1C,sBAAsB;AACtB,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAW/B,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Authenticated fetch transport with token lifecycle.
3
+ *
4
+ * Wraps FetchTransport and adds:
5
+ * - Bearer token injection via getAuthHeaders
6
+ * - Proactive token refresh before expiry
7
+ * - 401 retry with refresh lock (prevents thundering herd)
8
+ * - Auth failure callback for logout/navigation
9
+ *
10
+ * Platform-specific storage is injected via callbacks:
11
+ * - loadTokens: read persisted tokens on init
12
+ * - saveTokens: persist after login/refresh
13
+ * - clearTokens: wipe on logout/auth failure
14
+ *
15
+ * Usage:
16
+ *
17
+ * const transport = new AuthFetchTransport({
18
+ * baseUrl: 'https://wordpress.test/wp-json',
19
+ * refreshPath: 'wp-native/v1/auth/refresh',
20
+ * getDeviceId: () => getSecureItem('device_id'),
21
+ * loadTokens: () => getSecureItem('tokens').then(JSON.parse),
22
+ * saveTokens: (t) => setSecureItem('tokens', JSON.stringify(t)),
23
+ * clearTokens: () => deleteSecureItem('tokens'),
24
+ * onAuthFailure: () => router.replace('/login'),
25
+ * });
26
+ *
27
+ * // Inject your platform's secure storage via the callbacks above.
28
+ * await transport.initialize(); // loads stored tokens
29
+ */
30
+ import type { Transport, TransportRequest } from './types';
31
+ /** Token data stored by consumers. */
32
+ export interface StoredTokens {
33
+ accessToken: string;
34
+ refreshToken: string;
35
+ /** Unix timestamp (seconds) when the access token expires. */
36
+ accessExpiresAt: number;
37
+ }
38
+ export interface AuthFetchTransportConfig {
39
+ /** Base URL for the REST API, e.g. "https://example.com/wp-json" */
40
+ baseUrl: string;
41
+ /**
42
+ * REST path for the token refresh endpoint.
43
+ * @default 'wp-native/v1/auth/refresh'
44
+ */
45
+ refreshPath?: string;
46
+ /**
47
+ * How many milliseconds before expiry to proactively refresh.
48
+ * @default 60000 (1 minute)
49
+ */
50
+ refreshBufferMs?: number;
51
+ /**
52
+ * Return a device ID for refresh requests.
53
+ * Mobile apps generate a UUID and persist it; Node scripts can return a fixed string.
54
+ */
55
+ getDeviceId: () => string | Promise<string>;
56
+ /**
57
+ * Load persisted tokens on initialize().
58
+ * Return null if no tokens are stored.
59
+ */
60
+ loadTokens: () => StoredTokens | null | Promise<StoredTokens | null>;
61
+ /**
62
+ * Persist tokens after login, register, or refresh.
63
+ */
64
+ saveTokens: (tokens: StoredTokens) => void | Promise<void>;
65
+ /**
66
+ * Clear persisted tokens on logout or auth failure.
67
+ */
68
+ clearTokens: () => void | Promise<void>;
69
+ /**
70
+ * Called when authentication cannot be recovered (refresh failed, no tokens).
71
+ * Use this to navigate to a login screen or reset app state.
72
+ */
73
+ onAuthFailure?: () => void;
74
+ /**
75
+ * Extra headers to include on every request.
76
+ * Useful for custom client identification headers.
77
+ */
78
+ defaultHeaders?: Record<string, string>;
79
+ }
80
+ export declare class AuthFetchTransport implements Transport {
81
+ private config;
82
+ private accessToken;
83
+ private refreshToken;
84
+ private accessExpiresAt;
85
+ private refreshPromise;
86
+ private initialized;
87
+ constructor(config: AuthFetchTransportConfig);
88
+ /**
89
+ * Load stored tokens into memory. Call once at app startup.
90
+ */
91
+ initialize(): Promise<void>;
92
+ /**
93
+ * Whether initialize() has been called.
94
+ */
95
+ isInitialized(): boolean;
96
+ /**
97
+ * Whether tokens are currently loaded in memory.
98
+ */
99
+ hasTokens(): boolean;
100
+ /**
101
+ * Store new tokens in memory and persist via saveTokens callback.
102
+ * Call after login, register, or any auth flow that returns tokens.
103
+ */
104
+ setTokens(tokens: StoredTokens): Promise<void>;
105
+ /**
106
+ * Set or replace the auth failure callback.
107
+ * Useful when the callback depends on framework state or navigation
108
+ * and must be wired after transport construction.
109
+ */
110
+ setOnAuthFailure(callback: () => void): void;
111
+ /**
112
+ * Clear tokens from memory and storage. Call on logout.
113
+ */
114
+ clearAuth(): Promise<void>;
115
+ /**
116
+ * Execute an HTTP request with automatic auth handling.
117
+ *
118
+ * - Injects Bearer token if available
119
+ * - Proactively refreshes before expiry
120
+ * - Retries once on 401 after refreshing
121
+ */
122
+ request<T>(req: TransportRequest): Promise<T>;
123
+ private executeRequest;
124
+ private isAccessExpiringSoon;
125
+ /**
126
+ * Refresh the access token. Uses a lock to deduplicate concurrent refreshes.
127
+ */
128
+ private refreshAccessToken;
129
+ private executeRefresh;
130
+ private handleAuthFailure;
131
+ }
132
+ //# sourceMappingURL=auth-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-fetch.d.ts","sourceRoot":"","sources":["../../src/transports/auth-fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAG3D,sCAAsC;AACtC,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,wBAAwB;IACvC,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,WAAW,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE5C;;;OAGG;IACH,UAAU,EAAE,MAAM,YAAY,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAErE;;OAEG;IACH,UAAU,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE3D;;OAEG;IACH,WAAW,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAE3B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED,qBAAa,kBAAmB,YAAW,SAAS;IAClD,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,cAAc,CAAiC;IACvD,OAAO,CAAC,WAAW,CAAS;gBAEhB,MAAM,EAAE,wBAAwB;IAI5C;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAUjC;;OAEG;IACH,aAAa,IAAI,OAAO;IAIxB;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;;OAGG;IACG,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAOpD;;;;OAIG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAI5C;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAOhC;;;;;;OAMG;IACG,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,CAAC,CAAC;YA4BrC,cAAc;IA+C5B,OAAO,CAAC,oBAAoB;IAM5B;;OAEG;YACW,kBAAkB;YAclB,cAAc;YAuCd,iBAAiB;CAIhC"}
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Authenticated fetch transport with token lifecycle.
3
+ *
4
+ * Wraps FetchTransport and adds:
5
+ * - Bearer token injection via getAuthHeaders
6
+ * - Proactive token refresh before expiry
7
+ * - 401 retry with refresh lock (prevents thundering herd)
8
+ * - Auth failure callback for logout/navigation
9
+ *
10
+ * Platform-specific storage is injected via callbacks:
11
+ * - loadTokens: read persisted tokens on init
12
+ * - saveTokens: persist after login/refresh
13
+ * - clearTokens: wipe on logout/auth failure
14
+ *
15
+ * Usage:
16
+ *
17
+ * const transport = new AuthFetchTransport({
18
+ * baseUrl: 'https://wordpress.test/wp-json',
19
+ * refreshPath: 'wp-native/v1/auth/refresh',
20
+ * getDeviceId: () => getSecureItem('device_id'),
21
+ * loadTokens: () => getSecureItem('tokens').then(JSON.parse),
22
+ * saveTokens: (t) => setSecureItem('tokens', JSON.stringify(t)),
23
+ * clearTokens: () => deleteSecureItem('tokens'),
24
+ * onAuthFailure: () => router.replace('/login'),
25
+ * });
26
+ *
27
+ * // Inject your platform's secure storage via the callbacks above.
28
+ * await transport.initialize(); // loads stored tokens
29
+ */
30
+ import { ApiError } from './fetch';
31
+ export class AuthFetchTransport {
32
+ config;
33
+ accessToken = null;
34
+ refreshToken = null;
35
+ accessExpiresAt = null;
36
+ refreshPromise = null;
37
+ initialized = false;
38
+ constructor(config) {
39
+ this.config = config;
40
+ }
41
+ /**
42
+ * Load stored tokens into memory. Call once at app startup.
43
+ */
44
+ async initialize() {
45
+ const tokens = await this.config.loadTokens();
46
+ if (tokens) {
47
+ this.accessToken = tokens.accessToken;
48
+ this.refreshToken = tokens.refreshToken;
49
+ this.accessExpiresAt = tokens.accessExpiresAt;
50
+ }
51
+ this.initialized = true;
52
+ }
53
+ /**
54
+ * Whether initialize() has been called.
55
+ */
56
+ isInitialized() {
57
+ return this.initialized;
58
+ }
59
+ /**
60
+ * Whether tokens are currently loaded in memory.
61
+ */
62
+ hasTokens() {
63
+ return this.accessToken !== null && this.refreshToken !== null;
64
+ }
65
+ /**
66
+ * Store new tokens in memory and persist via saveTokens callback.
67
+ * Call after login, register, or any auth flow that returns tokens.
68
+ */
69
+ async setTokens(tokens) {
70
+ this.accessToken = tokens.accessToken;
71
+ this.refreshToken = tokens.refreshToken;
72
+ this.accessExpiresAt = tokens.accessExpiresAt;
73
+ await this.config.saveTokens(tokens);
74
+ }
75
+ /**
76
+ * Set or replace the auth failure callback.
77
+ * Useful when the callback depends on framework state or navigation
78
+ * and must be wired after transport construction.
79
+ */
80
+ setOnAuthFailure(callback) {
81
+ this.config.onAuthFailure = callback;
82
+ }
83
+ /**
84
+ * Clear tokens from memory and storage. Call on logout.
85
+ */
86
+ async clearAuth() {
87
+ this.accessToken = null;
88
+ this.refreshToken = null;
89
+ this.accessExpiresAt = null;
90
+ await this.config.clearTokens();
91
+ }
92
+ /**
93
+ * Execute an HTTP request with automatic auth handling.
94
+ *
95
+ * - Injects Bearer token if available
96
+ * - Proactively refreshes before expiry
97
+ * - Retries once on 401 after refreshing
98
+ */
99
+ async request(req) {
100
+ // Proactive refresh before the request
101
+ if (this.hasTokens() && this.isAccessExpiringSoon()) {
102
+ const refreshed = await this.refreshAccessToken();
103
+ if (!refreshed) {
104
+ await this.handleAuthFailure();
105
+ throw new ApiError('Session expired', 'session_expired', 401);
106
+ }
107
+ }
108
+ try {
109
+ return await this.executeRequest(req);
110
+ }
111
+ catch (error) {
112
+ // Retry once on 401 after refresh
113
+ if (error instanceof ApiError && error.status === 401 && this.refreshToken) {
114
+ const refreshed = await this.refreshAccessToken();
115
+ if (!refreshed) {
116
+ await this.handleAuthFailure();
117
+ throw new ApiError('Session expired', 'session_expired', 401);
118
+ }
119
+ return await this.executeRequest(req);
120
+ }
121
+ throw error;
122
+ }
123
+ }
124
+ // ─── Private ───────────────────────────────────────────────────────────
125
+ async executeRequest(req) {
126
+ const url = `${this.config.baseUrl}/${req.path}`;
127
+ const isFormData = typeof FormData !== 'undefined' && req.body instanceof FormData;
128
+ const headers = {
129
+ ...(!isFormData ? { 'Content-Type': 'application/json' } : {}),
130
+ ...(this.config.defaultHeaders ?? {}),
131
+ ...(this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : {}),
132
+ ...req.headers,
133
+ };
134
+ const response = await fetch(url, {
135
+ method: req.method,
136
+ headers,
137
+ body: req.body
138
+ ? isFormData
139
+ ? req.body
140
+ : JSON.stringify(req.body)
141
+ : null,
142
+ });
143
+ if (response.status === 401) {
144
+ throw new ApiError('Unauthorized', 'unauthorized', 401);
145
+ }
146
+ if (!response.ok) {
147
+ let errorData = {};
148
+ try {
149
+ errorData = await response.json();
150
+ }
151
+ catch {
152
+ // Response wasn't JSON
153
+ }
154
+ throw new ApiError(errorData.message || `Request failed with status ${response.status}`, errorData.code || 'request_failed', response.status);
155
+ }
156
+ if (response.status === 204) {
157
+ return undefined;
158
+ }
159
+ return (await response.json());
160
+ }
161
+ isAccessExpiringSoon() {
162
+ if (!this.accessExpiresAt)
163
+ return true;
164
+ const bufferMs = this.config.refreshBufferMs ?? 60_000;
165
+ return Date.now() >= this.accessExpiresAt * 1000 - bufferMs;
166
+ }
167
+ /**
168
+ * Refresh the access token. Uses a lock to deduplicate concurrent refreshes.
169
+ */
170
+ async refreshAccessToken() {
171
+ if (this.refreshPromise) {
172
+ return this.refreshPromise;
173
+ }
174
+ this.refreshPromise = this.executeRefresh();
175
+ try {
176
+ return await this.refreshPromise;
177
+ }
178
+ finally {
179
+ this.refreshPromise = null;
180
+ }
181
+ }
182
+ async executeRefresh() {
183
+ if (!this.refreshToken)
184
+ return false;
185
+ try {
186
+ const deviceId = await this.config.getDeviceId();
187
+ const refreshPath = this.config.refreshPath ?? 'wp-native/v1/auth/refresh';
188
+ const url = `${this.config.baseUrl}/${refreshPath}`;
189
+ const response = await fetch(url, {
190
+ method: 'POST',
191
+ headers: { 'Content-Type': 'application/json' },
192
+ body: JSON.stringify({
193
+ refresh_token: this.refreshToken,
194
+ device_id: deviceId,
195
+ }),
196
+ });
197
+ if (!response.ok)
198
+ return false;
199
+ const data = (await response.json());
200
+ const expiresAt = Math.floor(new Date(data.access_expires_at).getTime() / 1000);
201
+ await this.setTokens({
202
+ accessToken: data.access_token,
203
+ refreshToken: data.refresh_token,
204
+ accessExpiresAt: expiresAt,
205
+ });
206
+ return true;
207
+ }
208
+ catch {
209
+ return false;
210
+ }
211
+ }
212
+ async handleAuthFailure() {
213
+ await this.clearAuth();
214
+ this.config.onAuthFailure?.();
215
+ }
216
+ }
217
+ //# sourceMappingURL=auth-fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-fetch.js","sourceRoot":"","sources":["../../src/transports/auth-fetch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Native fetch transport.
3
+ *
4
+ * Works in:
5
+ * - React Native (built-in fetch)
6
+ * - Modern browsers (without @wordpress/api-fetch)
7
+ * - Node 18+ (built-in fetch)
8
+ *
9
+ * Auth is handled via a configurable header function.
10
+ * Mobile apps pass Bearer tokens, Node scripts pass Basic auth, etc.
11
+ */
12
+ import type { Transport, TransportRequest } from './types';
13
+ export interface FetchTransportConfig {
14
+ /** Base URL for the REST API, e.g. "https://example.com/wp-json" */
15
+ baseUrl: string;
16
+ /**
17
+ * Return auth headers for each request.
18
+ * Return empty object for public endpoints.
19
+ *
20
+ * Examples:
21
+ * Bearer: () => ({ Authorization: `Bearer ${token}` })
22
+ * Basic: () => ({ Authorization: `Basic ${btoa('user:pass')}` })
23
+ * Nonce: () => ({ 'X-WP-Nonce': nonce })
24
+ */
25
+ getAuthHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
26
+ /**
27
+ * Called when a request returns 401.
28
+ * Use this to trigger token refresh or logout.
29
+ */
30
+ onUnauthorized?: () => void | Promise<void>;
31
+ }
32
+ export declare class FetchTransport implements Transport {
33
+ private config;
34
+ constructor(config: FetchTransportConfig);
35
+ request<T>(req: TransportRequest): Promise<T>;
36
+ }
37
+ export declare class ApiError extends Error {
38
+ code: string;
39
+ status: number;
40
+ constructor(message: string, code: string, status: number);
41
+ }
42
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/transports/fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE3D,MAAM,WAAW,oBAAoB;IACnC,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAEhB;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAEhF;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C;AAED,qBAAa,cAAe,YAAW,SAAS;IAC9C,OAAO,CAAC,MAAM,CAAuB;gBAEzB,MAAM,EAAE,oBAAoB;IAIlC,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,CAAC,CAAC;CAmDpD;AAED,qBAAa,QAAS,SAAQ,KAAK;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;gBAEH,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAM1D"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Native fetch transport.
3
+ *
4
+ * Works in:
5
+ * - React Native (built-in fetch)
6
+ * - Modern browsers (without @wordpress/api-fetch)
7
+ * - Node 18+ (built-in fetch)
8
+ *
9
+ * Auth is handled via a configurable header function.
10
+ * Mobile apps pass Bearer tokens, Node scripts pass Basic auth, etc.
11
+ */
12
+ export class FetchTransport {
13
+ config;
14
+ constructor(config) {
15
+ this.config = config;
16
+ }
17
+ async request(req) {
18
+ const url = `${this.config.baseUrl}/${req.path}`;
19
+ const authHeaders = this.config.getAuthHeaders
20
+ ? await this.config.getAuthHeaders()
21
+ : {};
22
+ const isFormData = typeof FormData !== 'undefined' && req.body instanceof FormData;
23
+ const headers = {
24
+ ...(!isFormData ? { 'Content-Type': 'application/json' } : {}),
25
+ ...authHeaders,
26
+ ...req.headers,
27
+ };
28
+ const response = await fetch(url, {
29
+ method: req.method,
30
+ headers,
31
+ body: req.body
32
+ ? isFormData
33
+ ? req.body
34
+ : JSON.stringify(req.body)
35
+ : null,
36
+ });
37
+ if (response.status === 401) {
38
+ await this.config.onUnauthorized?.();
39
+ throw new ApiError('Unauthorized', 'unauthorized', 401);
40
+ }
41
+ if (!response.ok) {
42
+ let errorData = {};
43
+ try {
44
+ errorData = await response.json();
45
+ }
46
+ catch {
47
+ // Response wasn't JSON
48
+ }
49
+ throw new ApiError(errorData.message || `Request failed with status ${response.status}`, errorData.code || 'request_failed', response.status);
50
+ }
51
+ // Handle 204 No Content
52
+ if (response.status === 204) {
53
+ return undefined;
54
+ }
55
+ return (await response.json());
56
+ }
57
+ }
58
+ export class ApiError extends Error {
59
+ code;
60
+ status;
61
+ constructor(message, code, status) {
62
+ super(message);
63
+ this.name = 'ApiError';
64
+ this.code = code;
65
+ this.status = status;
66
+ }
67
+ }
68
+ //# sourceMappingURL=fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch.js","sourceRoot":"","sources":["../../src/transports/fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AA0BH,MAAM,OAAO,cAAc;IACjB,MAAM,CAAuB;IAErC,YAAY,MAA4B;QACtC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,OAAO,CAAI,GAAqB;QACpC,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QAEjD,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc;YAC5C,CAAC,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE;YACpC,CAAC,CAAC,EAAE,CAAC;QAEP,MAAM,UAAU,GAAG,OAAO,QAAQ,KAAK,WAAW,IAAI,GAAG,CAAC,IAAI,YAAY,QAAQ,CAAC;QAEnF,MAAM,OAAO,GAA2B;YACtC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9D,GAAG,WAAW;YACd,GAAG,GAAG,CAAC,OAAO;SACf,CAAC;QAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,OAAO;YACP,IAAI,EAAE,GAAG,CAAC,IAAI;gBACZ,CAAC,CAAC,UAAU;oBACV,CAAC,CAAE,GAAG,CAAC,IAAiB;oBACxB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;gBAC5B,CAAC,CAAC,IAAI;SACT,CAAC,CAAC;QAEH,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,EAAE,CAAC;YACrC,MAAM,IAAI,QAAQ,CAAC,cAAc,EAAE,cAAc,EAAE,GAAG,CAAC,CAAC;QAC1D,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,IAAI,SAAS,GAAwC,EAAE,CAAC;YACxD,IAAI,CAAC;gBACH,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACpC,CAAC;YAAC,MAAM,CAAC;gBACP,uBAAuB;YACzB,CAAC;YACD,MAAM,IAAI,QAAQ,CAChB,SAAS,CAAC,OAAO,IAAI,8BAA8B,QAAQ,CAAC,MAAM,EAAE,EACpE,SAAS,CAAC,IAAI,IAAI,gBAAgB,EAClC,QAAQ,CAAC,MAAM,CAChB,CAAC;QACJ,CAAC;QAED,wBAAwB;QACxB,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,OAAO,SAAc,CAAC;QACxB,CAAC;QAED,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAM,CAAC;IACtC,CAAC;CACF;AAED,MAAM,OAAO,QAAS,SAAQ,KAAK;IACjC,IAAI,CAAS;IACb,MAAM,CAAS;IAEf,YAAY,OAAe,EAAE,IAAY,EAAE,MAAc;QACvD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF"}