@headroom-cms/api 0.1.4 → 0.1.6
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/astro.js +159 -0
- package/dist/index.cjs +161 -0
- package/dist/index.d.cts +86 -3
- package/dist/index.d.ts +86 -3
- package/dist/index.js +160 -0
- package/dist/next.cjs +68 -0
- package/dist/next.d.cts +11 -0
- package/dist/next.d.ts +11 -0
- package/dist/next.js +42 -0
- package/package.json +15 -5
package/dist/astro.js
CHANGED
|
@@ -1,5 +1,73 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
2
|
import { createHmac } from "crypto";
|
|
3
|
+
|
|
4
|
+
// src/auth.ts
|
|
5
|
+
var HeadroomAuth = class {
|
|
6
|
+
/** @internal */
|
|
7
|
+
constructor(client) {
|
|
8
|
+
this.client = client;
|
|
9
|
+
}
|
|
10
|
+
/** Request an OTP code be sent to the email address */
|
|
11
|
+
async requestOTP(email) {
|
|
12
|
+
await this.client._fetchPost("/auth/otp/request", { email });
|
|
13
|
+
}
|
|
14
|
+
/** Verify OTP code and receive a long-lived session token */
|
|
15
|
+
async verifyOTP(email, code) {
|
|
16
|
+
return this.client._fetchPost("/auth/otp/verify", {
|
|
17
|
+
email,
|
|
18
|
+
code
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validate a session token and return the user.
|
|
23
|
+
* Automatically refreshes the token if it's past 75% of its lifetime,
|
|
24
|
+
* so callers can update their cookie with the new token if wasRefreshed is true.
|
|
25
|
+
*/
|
|
26
|
+
async getSession(token) {
|
|
27
|
+
const res = await this.client._fetchWithAuth(
|
|
28
|
+
"/auth/session",
|
|
29
|
+
token
|
|
30
|
+
);
|
|
31
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
32
|
+
const remaining = res.expiresAt - now;
|
|
33
|
+
const lifetime = res.expiresAt - res.issuedAt;
|
|
34
|
+
if (remaining < lifetime * 0.25) {
|
|
35
|
+
try {
|
|
36
|
+
const refreshed = await this.refreshSession(token);
|
|
37
|
+
return { ...refreshed, wasRefreshed: true };
|
|
38
|
+
} catch {
|
|
39
|
+
return {
|
|
40
|
+
user: res.user,
|
|
41
|
+
expiresAt: res.expiresAt,
|
|
42
|
+
token,
|
|
43
|
+
wasRefreshed: false
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
user: res.user,
|
|
49
|
+
expiresAt: res.expiresAt,
|
|
50
|
+
token,
|
|
51
|
+
wasRefreshed: false
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Refresh a session token for a new expiry period */
|
|
55
|
+
async refreshSession(token) {
|
|
56
|
+
const res = await this.client._fetchPostWithAuth(
|
|
57
|
+
"/auth/session/refresh",
|
|
58
|
+
token,
|
|
59
|
+
{}
|
|
60
|
+
);
|
|
61
|
+
return {
|
|
62
|
+
user: res.user,
|
|
63
|
+
expiresAt: res.expiresAt,
|
|
64
|
+
token: res.token,
|
|
65
|
+
wasRefreshed: true
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// src/client.ts
|
|
3
71
|
var HeadroomError = class extends Error {
|
|
4
72
|
status;
|
|
5
73
|
code;
|
|
@@ -12,11 +80,14 @@ var HeadroomError = class extends Error {
|
|
|
12
80
|
};
|
|
13
81
|
var HeadroomClient = class {
|
|
14
82
|
config;
|
|
83
|
+
/** Site user authentication methods */
|
|
84
|
+
auth;
|
|
15
85
|
constructor(config) {
|
|
16
86
|
this.config = {
|
|
17
87
|
...config,
|
|
18
88
|
url: config.url.replace(/\/+$/, "")
|
|
19
89
|
};
|
|
90
|
+
this.auth = new HeadroomAuth(this);
|
|
20
91
|
}
|
|
21
92
|
/** Build the full URL for a public API path */
|
|
22
93
|
apiUrl(path) {
|
|
@@ -105,6 +176,57 @@ var HeadroomClient = class {
|
|
|
105
176
|
}
|
|
106
177
|
return res.json();
|
|
107
178
|
}
|
|
179
|
+
/** @internal Used by HeadroomAuth */
|
|
180
|
+
async _fetchPost(path, body) {
|
|
181
|
+
return this.fetchPost(path, body);
|
|
182
|
+
}
|
|
183
|
+
/** @internal Used by HeadroomAuth — GET with Bearer token */
|
|
184
|
+
async _fetchWithAuth(path, token) {
|
|
185
|
+
const url = this.apiUrl(path);
|
|
186
|
+
const res = await fetch(url, {
|
|
187
|
+
headers: {
|
|
188
|
+
"X-Headroom-Key": this.config.apiKey,
|
|
189
|
+
Authorization: `Bearer ${token}`
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
if (!res.ok) {
|
|
193
|
+
let code = "UNKNOWN";
|
|
194
|
+
let message = `HTTP ${res.status}`;
|
|
195
|
+
try {
|
|
196
|
+
const body = await res.json();
|
|
197
|
+
code = body.code || code;
|
|
198
|
+
message = body.error || message;
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
throw new HeadroomError(res.status, code, `${message} (GET ${url})`);
|
|
202
|
+
}
|
|
203
|
+
return res.json();
|
|
204
|
+
}
|
|
205
|
+
/** @internal Used by HeadroomAuth — POST with Bearer token */
|
|
206
|
+
async _fetchPostWithAuth(path, token, body) {
|
|
207
|
+
const url = this.apiUrl(path);
|
|
208
|
+
const res = await fetch(url, {
|
|
209
|
+
method: "POST",
|
|
210
|
+
headers: {
|
|
211
|
+
"X-Headroom-Key": this.config.apiKey,
|
|
212
|
+
Authorization: `Bearer ${token}`,
|
|
213
|
+
"Content-Type": "application/json"
|
|
214
|
+
},
|
|
215
|
+
body: JSON.stringify(body)
|
|
216
|
+
});
|
|
217
|
+
if (!res.ok) {
|
|
218
|
+
let code = "UNKNOWN";
|
|
219
|
+
let message = `HTTP ${res.status}`;
|
|
220
|
+
try {
|
|
221
|
+
const errBody = await res.json();
|
|
222
|
+
code = errBody.code || code;
|
|
223
|
+
message = errBody.error || message;
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
throw new HeadroomError(res.status, code, `${message} (POST ${url})`);
|
|
227
|
+
}
|
|
228
|
+
return res.json();
|
|
229
|
+
}
|
|
108
230
|
// --- Content ---
|
|
109
231
|
async listContent(collection, opts) {
|
|
110
232
|
const params = new URLSearchParams({ collection });
|
|
@@ -163,6 +285,43 @@ var HeadroomClient = class {
|
|
|
163
285
|
const result = await this.fetch("/version");
|
|
164
286
|
return result.contentVersion;
|
|
165
287
|
}
|
|
288
|
+
// --- Submissions ---
|
|
289
|
+
/**
|
|
290
|
+
* Submit content to a submission collection.
|
|
291
|
+
* Sends a POST to `/v1/{site}/submit/{collection}`.
|
|
292
|
+
* If a sessionToken is provided, it is sent as the `X-Headroom-Session` header
|
|
293
|
+
* to associate the submission with an authenticated site user.
|
|
294
|
+
*/
|
|
295
|
+
async submit(options) {
|
|
296
|
+
const url = this.apiUrl(`/submit/${encodeURIComponent(options.collection)}`);
|
|
297
|
+
const headers = {
|
|
298
|
+
"X-Headroom-Key": this.config.apiKey,
|
|
299
|
+
"Content-Type": "application/json"
|
|
300
|
+
};
|
|
301
|
+
if (options.sessionToken) {
|
|
302
|
+
headers["X-Headroom-Session"] = options.sessionToken;
|
|
303
|
+
}
|
|
304
|
+
const res = await fetch(url, {
|
|
305
|
+
method: "POST",
|
|
306
|
+
headers,
|
|
307
|
+
body: JSON.stringify({
|
|
308
|
+
fields: options.fields,
|
|
309
|
+
...options.relationships && { relationships: options.relationships }
|
|
310
|
+
})
|
|
311
|
+
});
|
|
312
|
+
if (!res.ok) {
|
|
313
|
+
let code = "UNKNOWN";
|
|
314
|
+
let message = `HTTP ${res.status}`;
|
|
315
|
+
try {
|
|
316
|
+
const errBody = await res.json();
|
|
317
|
+
code = errBody.code || code;
|
|
318
|
+
message = errBody.error || message;
|
|
319
|
+
} catch {
|
|
320
|
+
}
|
|
321
|
+
throw new HeadroomError(res.status, code, `${message} (POST ${url})`);
|
|
322
|
+
}
|
|
323
|
+
return res.json();
|
|
324
|
+
}
|
|
166
325
|
};
|
|
167
326
|
|
|
168
327
|
// src/astro/env.ts
|
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
|
+
HeadroomAuth: () => HeadroomAuth,
|
|
23
24
|
HeadroomClient: () => HeadroomClient,
|
|
24
25
|
HeadroomError: () => HeadroomError
|
|
25
26
|
});
|
|
@@ -27,6 +28,74 @@ module.exports = __toCommonJS(src_exports);
|
|
|
27
28
|
|
|
28
29
|
// src/client.ts
|
|
29
30
|
var import_node_crypto = require("crypto");
|
|
31
|
+
|
|
32
|
+
// src/auth.ts
|
|
33
|
+
var HeadroomAuth = class {
|
|
34
|
+
/** @internal */
|
|
35
|
+
constructor(client) {
|
|
36
|
+
this.client = client;
|
|
37
|
+
}
|
|
38
|
+
/** Request an OTP code be sent to the email address */
|
|
39
|
+
async requestOTP(email) {
|
|
40
|
+
await this.client._fetchPost("/auth/otp/request", { email });
|
|
41
|
+
}
|
|
42
|
+
/** Verify OTP code and receive a long-lived session token */
|
|
43
|
+
async verifyOTP(email, code) {
|
|
44
|
+
return this.client._fetchPost("/auth/otp/verify", {
|
|
45
|
+
email,
|
|
46
|
+
code
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Validate a session token and return the user.
|
|
51
|
+
* Automatically refreshes the token if it's past 75% of its lifetime,
|
|
52
|
+
* so callers can update their cookie with the new token if wasRefreshed is true.
|
|
53
|
+
*/
|
|
54
|
+
async getSession(token) {
|
|
55
|
+
const res = await this.client._fetchWithAuth(
|
|
56
|
+
"/auth/session",
|
|
57
|
+
token
|
|
58
|
+
);
|
|
59
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
60
|
+
const remaining = res.expiresAt - now;
|
|
61
|
+
const lifetime = res.expiresAt - res.issuedAt;
|
|
62
|
+
if (remaining < lifetime * 0.25) {
|
|
63
|
+
try {
|
|
64
|
+
const refreshed = await this.refreshSession(token);
|
|
65
|
+
return { ...refreshed, wasRefreshed: true };
|
|
66
|
+
} catch {
|
|
67
|
+
return {
|
|
68
|
+
user: res.user,
|
|
69
|
+
expiresAt: res.expiresAt,
|
|
70
|
+
token,
|
|
71
|
+
wasRefreshed: false
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
user: res.user,
|
|
77
|
+
expiresAt: res.expiresAt,
|
|
78
|
+
token,
|
|
79
|
+
wasRefreshed: false
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** Refresh a session token for a new expiry period */
|
|
83
|
+
async refreshSession(token) {
|
|
84
|
+
const res = await this.client._fetchPostWithAuth(
|
|
85
|
+
"/auth/session/refresh",
|
|
86
|
+
token,
|
|
87
|
+
{}
|
|
88
|
+
);
|
|
89
|
+
return {
|
|
90
|
+
user: res.user,
|
|
91
|
+
expiresAt: res.expiresAt,
|
|
92
|
+
token: res.token,
|
|
93
|
+
wasRefreshed: true
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/client.ts
|
|
30
99
|
var HeadroomError = class extends Error {
|
|
31
100
|
status;
|
|
32
101
|
code;
|
|
@@ -39,11 +108,14 @@ var HeadroomError = class extends Error {
|
|
|
39
108
|
};
|
|
40
109
|
var HeadroomClient = class {
|
|
41
110
|
config;
|
|
111
|
+
/** Site user authentication methods */
|
|
112
|
+
auth;
|
|
42
113
|
constructor(config) {
|
|
43
114
|
this.config = {
|
|
44
115
|
...config,
|
|
45
116
|
url: config.url.replace(/\/+$/, "")
|
|
46
117
|
};
|
|
118
|
+
this.auth = new HeadroomAuth(this);
|
|
47
119
|
}
|
|
48
120
|
/** Build the full URL for a public API path */
|
|
49
121
|
apiUrl(path) {
|
|
@@ -132,6 +204,57 @@ var HeadroomClient = class {
|
|
|
132
204
|
}
|
|
133
205
|
return res.json();
|
|
134
206
|
}
|
|
207
|
+
/** @internal Used by HeadroomAuth */
|
|
208
|
+
async _fetchPost(path, body) {
|
|
209
|
+
return this.fetchPost(path, body);
|
|
210
|
+
}
|
|
211
|
+
/** @internal Used by HeadroomAuth — GET with Bearer token */
|
|
212
|
+
async _fetchWithAuth(path, token) {
|
|
213
|
+
const url = this.apiUrl(path);
|
|
214
|
+
const res = await fetch(url, {
|
|
215
|
+
headers: {
|
|
216
|
+
"X-Headroom-Key": this.config.apiKey,
|
|
217
|
+
Authorization: `Bearer ${token}`
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
if (!res.ok) {
|
|
221
|
+
let code = "UNKNOWN";
|
|
222
|
+
let message = `HTTP ${res.status}`;
|
|
223
|
+
try {
|
|
224
|
+
const body = await res.json();
|
|
225
|
+
code = body.code || code;
|
|
226
|
+
message = body.error || message;
|
|
227
|
+
} catch {
|
|
228
|
+
}
|
|
229
|
+
throw new HeadroomError(res.status, code, `${message} (GET ${url})`);
|
|
230
|
+
}
|
|
231
|
+
return res.json();
|
|
232
|
+
}
|
|
233
|
+
/** @internal Used by HeadroomAuth — POST with Bearer token */
|
|
234
|
+
async _fetchPostWithAuth(path, token, body) {
|
|
235
|
+
const url = this.apiUrl(path);
|
|
236
|
+
const res = await fetch(url, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
headers: {
|
|
239
|
+
"X-Headroom-Key": this.config.apiKey,
|
|
240
|
+
Authorization: `Bearer ${token}`,
|
|
241
|
+
"Content-Type": "application/json"
|
|
242
|
+
},
|
|
243
|
+
body: JSON.stringify(body)
|
|
244
|
+
});
|
|
245
|
+
if (!res.ok) {
|
|
246
|
+
let code = "UNKNOWN";
|
|
247
|
+
let message = `HTTP ${res.status}`;
|
|
248
|
+
try {
|
|
249
|
+
const errBody = await res.json();
|
|
250
|
+
code = errBody.code || code;
|
|
251
|
+
message = errBody.error || message;
|
|
252
|
+
} catch {
|
|
253
|
+
}
|
|
254
|
+
throw new HeadroomError(res.status, code, `${message} (POST ${url})`);
|
|
255
|
+
}
|
|
256
|
+
return res.json();
|
|
257
|
+
}
|
|
135
258
|
// --- Content ---
|
|
136
259
|
async listContent(collection, opts) {
|
|
137
260
|
const params = new URLSearchParams({ collection });
|
|
@@ -190,9 +313,47 @@ var HeadroomClient = class {
|
|
|
190
313
|
const result = await this.fetch("/version");
|
|
191
314
|
return result.contentVersion;
|
|
192
315
|
}
|
|
316
|
+
// --- Submissions ---
|
|
317
|
+
/**
|
|
318
|
+
* Submit content to a submission collection.
|
|
319
|
+
* Sends a POST to `/v1/{site}/submit/{collection}`.
|
|
320
|
+
* If a sessionToken is provided, it is sent as the `X-Headroom-Session` header
|
|
321
|
+
* to associate the submission with an authenticated site user.
|
|
322
|
+
*/
|
|
323
|
+
async submit(options) {
|
|
324
|
+
const url = this.apiUrl(`/submit/${encodeURIComponent(options.collection)}`);
|
|
325
|
+
const headers = {
|
|
326
|
+
"X-Headroom-Key": this.config.apiKey,
|
|
327
|
+
"Content-Type": "application/json"
|
|
328
|
+
};
|
|
329
|
+
if (options.sessionToken) {
|
|
330
|
+
headers["X-Headroom-Session"] = options.sessionToken;
|
|
331
|
+
}
|
|
332
|
+
const res = await fetch(url, {
|
|
333
|
+
method: "POST",
|
|
334
|
+
headers,
|
|
335
|
+
body: JSON.stringify({
|
|
336
|
+
fields: options.fields,
|
|
337
|
+
...options.relationships && { relationships: options.relationships }
|
|
338
|
+
})
|
|
339
|
+
});
|
|
340
|
+
if (!res.ok) {
|
|
341
|
+
let code = "UNKNOWN";
|
|
342
|
+
let message = `HTTP ${res.status}`;
|
|
343
|
+
try {
|
|
344
|
+
const errBody = await res.json();
|
|
345
|
+
code = errBody.code || code;
|
|
346
|
+
message = errBody.error || message;
|
|
347
|
+
} catch {
|
|
348
|
+
}
|
|
349
|
+
throw new HeadroomError(res.status, code, `${message} (POST ${url})`);
|
|
350
|
+
}
|
|
351
|
+
return res.json();
|
|
352
|
+
}
|
|
193
353
|
};
|
|
194
354
|
// Annotate the CommonJS export names for ESM import in node:
|
|
195
355
|
0 && (module.exports = {
|
|
356
|
+
HeadroomAuth,
|
|
196
357
|
HeadroomClient,
|
|
197
358
|
HeadroomError
|
|
198
359
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -61,6 +61,12 @@ interface ContentMetadata {
|
|
|
61
61
|
coverUrl?: string;
|
|
62
62
|
coverMediaId?: string;
|
|
63
63
|
lastPublishedAt?: number;
|
|
64
|
+
/** Set to "submission" for submission content, omitted for regular content */
|
|
65
|
+
mode?: string;
|
|
66
|
+
/** Site user ID of the submitter (submissions only) */
|
|
67
|
+
siteUserId?: string;
|
|
68
|
+
/** Submission field values (submissions only) */
|
|
69
|
+
fields?: Record<string, unknown>;
|
|
64
70
|
}
|
|
65
71
|
interface ContentItem extends ContentMetadata {
|
|
66
72
|
body?: {
|
|
@@ -151,11 +157,73 @@ interface TransformOptions {
|
|
|
151
157
|
/** Quality (1-100) */
|
|
152
158
|
quality?: number;
|
|
153
159
|
}
|
|
160
|
+
/** Options for submitting content to a submission collection. */
|
|
161
|
+
interface SubmitOptions {
|
|
162
|
+
/** The submission collection name */
|
|
163
|
+
collection: string;
|
|
164
|
+
/** Field values for the submission */
|
|
165
|
+
fields: Record<string, unknown>;
|
|
166
|
+
/** Optional site user session token (adds X-Headroom-Session header) */
|
|
167
|
+
sessionToken?: string;
|
|
168
|
+
/** Optional relationships to associate with the submission */
|
|
169
|
+
relationships?: Record<string, {
|
|
170
|
+
contentId: string;
|
|
171
|
+
}[]>;
|
|
172
|
+
}
|
|
173
|
+
/** Result returned from a successful submission. */
|
|
174
|
+
interface SubmitResult {
|
|
175
|
+
/** The ID of the created content item */
|
|
176
|
+
contentId: string;
|
|
177
|
+
/** Unix timestamp of creation */
|
|
178
|
+
createdAt: number;
|
|
179
|
+
/** Site user ID if the submission was authenticated */
|
|
180
|
+
siteUserId?: string;
|
|
181
|
+
}
|
|
154
182
|
interface HeadroomErrorBody {
|
|
155
183
|
error: string;
|
|
156
184
|
code?: string;
|
|
157
185
|
}
|
|
158
186
|
|
|
187
|
+
interface AuthResult {
|
|
188
|
+
token: string;
|
|
189
|
+
expiresAt: number;
|
|
190
|
+
user: AuthUser;
|
|
191
|
+
}
|
|
192
|
+
interface AuthUser {
|
|
193
|
+
userId: string;
|
|
194
|
+
email: string;
|
|
195
|
+
name?: string;
|
|
196
|
+
tags?: string[];
|
|
197
|
+
fields?: Record<string, unknown>;
|
|
198
|
+
}
|
|
199
|
+
interface AuthSession {
|
|
200
|
+
user: AuthUser;
|
|
201
|
+
expiresAt: number;
|
|
202
|
+
token: string;
|
|
203
|
+
wasRefreshed: boolean;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* HeadroomAuth provides site user authentication methods.
|
|
207
|
+
* Access via `client.auth`.
|
|
208
|
+
*/
|
|
209
|
+
declare class HeadroomAuth {
|
|
210
|
+
private client;
|
|
211
|
+
/** @internal */
|
|
212
|
+
constructor(client: HeadroomClient);
|
|
213
|
+
/** Request an OTP code be sent to the email address */
|
|
214
|
+
requestOTP(email: string): Promise<void>;
|
|
215
|
+
/** Verify OTP code and receive a long-lived session token */
|
|
216
|
+
verifyOTP(email: string, code: string): Promise<AuthResult>;
|
|
217
|
+
/**
|
|
218
|
+
* Validate a session token and return the user.
|
|
219
|
+
* Automatically refreshes the token if it's past 75% of its lifetime,
|
|
220
|
+
* so callers can update their cookie with the new token if wasRefreshed is true.
|
|
221
|
+
*/
|
|
222
|
+
getSession(token: string): Promise<AuthSession>;
|
|
223
|
+
/** Refresh a session token for a new expiry period */
|
|
224
|
+
refreshSession(token: string): Promise<AuthSession>;
|
|
225
|
+
}
|
|
226
|
+
|
|
159
227
|
declare class HeadroomError extends Error {
|
|
160
228
|
status: number;
|
|
161
229
|
code: string;
|
|
@@ -163,6 +231,8 @@ declare class HeadroomError extends Error {
|
|
|
163
231
|
}
|
|
164
232
|
declare class HeadroomClient {
|
|
165
233
|
private config;
|
|
234
|
+
/** Site user authentication methods */
|
|
235
|
+
readonly auth: HeadroomAuth;
|
|
166
236
|
constructor(config: HeadroomConfig);
|
|
167
237
|
/** Build the full URL for a public API path */
|
|
168
238
|
private apiUrl;
|
|
@@ -189,13 +259,19 @@ declare class HeadroomClient {
|
|
|
189
259
|
transformUrl(path: string | undefined, opts?: TransformOptions): string;
|
|
190
260
|
private fetch;
|
|
191
261
|
private fetchPost;
|
|
262
|
+
/** @internal Used by HeadroomAuth */
|
|
263
|
+
_fetchPost<T>(path: string, body: unknown): Promise<T>;
|
|
264
|
+
/** @internal Used by HeadroomAuth — GET with Bearer token */
|
|
265
|
+
_fetchWithAuth<T>(path: string, token: string): Promise<T>;
|
|
266
|
+
/** @internal Used by HeadroomAuth — POST with Bearer token */
|
|
267
|
+
_fetchPostWithAuth<T>(path: string, token: string, body: unknown): Promise<T>;
|
|
192
268
|
listContent(collection: string, opts?: {
|
|
193
269
|
limit?: number;
|
|
194
270
|
cursor?: string;
|
|
195
271
|
before?: number;
|
|
196
272
|
after?: number;
|
|
197
|
-
/** Sort order: "published_desc" (default), "published_asc", "title_asc", "title_desc" */
|
|
198
|
-
sort?: "published_desc" | "published_asc" | "title_asc" | "title_desc";
|
|
273
|
+
/** Sort order. For regular queries: "published_desc" (default), "published_asc", "title_asc", "title_desc". For relatedTo queries: "created_desc" (default), "created_asc". */
|
|
274
|
+
sort?: "published_desc" | "published_asc" | "title_asc" | "title_desc" | "created_desc" | "created_asc";
|
|
199
275
|
/** Find content that has a relationship pointing to this content ID (reverse query) */
|
|
200
276
|
relatedTo?: string;
|
|
201
277
|
/** Filter reverse query to a specific relationship field name */
|
|
@@ -218,6 +294,13 @@ declare class HeadroomClient {
|
|
|
218
294
|
getCollection(name: string): Promise<Collection>;
|
|
219
295
|
listBlockTypes(): Promise<BlockTypeListResult>;
|
|
220
296
|
getVersion(): Promise<number>;
|
|
297
|
+
/**
|
|
298
|
+
* Submit content to a submission collection.
|
|
299
|
+
* Sends a POST to `/v1/{site}/submit/{collection}`.
|
|
300
|
+
* If a sessionToken is provided, it is sent as the `X-Headroom-Session` header
|
|
301
|
+
* to associate the submission with an authenticated site user.
|
|
302
|
+
*/
|
|
303
|
+
submit(options: SubmitOptions): Promise<SubmitResult>;
|
|
221
304
|
}
|
|
222
305
|
|
|
223
|
-
export { type BatchContentResult, type Block, type BlockTypeDef, type BlockTypeListResult, type Collection, type CollectionListResult, type CollectionSummary, type ContentItem, type ContentListResult, type ContentMetadata, type ContentRef, type FieldDef, HeadroomClient, type HeadroomConfig, HeadroomError, type HeadroomErrorBody, type InlineContent, type LinkContent, type PublicContentRef, type RefsMap, type RelationshipDef, type TableContent, type TableRow, type TextContent, type TextStyles, type TransformOptions };
|
|
306
|
+
export { type AuthResult, type AuthSession, type AuthUser, type BatchContentResult, type Block, type BlockTypeDef, type BlockTypeListResult, type Collection, type CollectionListResult, type CollectionSummary, type ContentItem, type ContentListResult, type ContentMetadata, type ContentRef, type FieldDef, HeadroomAuth, HeadroomClient, type HeadroomConfig, HeadroomError, type HeadroomErrorBody, type InlineContent, type LinkContent, type PublicContentRef, type RefsMap, type RelationshipDef, type SubmitOptions, type SubmitResult, type TableContent, type TableRow, type TextContent, type TextStyles, type TransformOptions };
|
package/dist/index.d.ts
CHANGED
|
@@ -61,6 +61,12 @@ interface ContentMetadata {
|
|
|
61
61
|
coverUrl?: string;
|
|
62
62
|
coverMediaId?: string;
|
|
63
63
|
lastPublishedAt?: number;
|
|
64
|
+
/** Set to "submission" for submission content, omitted for regular content */
|
|
65
|
+
mode?: string;
|
|
66
|
+
/** Site user ID of the submitter (submissions only) */
|
|
67
|
+
siteUserId?: string;
|
|
68
|
+
/** Submission field values (submissions only) */
|
|
69
|
+
fields?: Record<string, unknown>;
|
|
64
70
|
}
|
|
65
71
|
interface ContentItem extends ContentMetadata {
|
|
66
72
|
body?: {
|
|
@@ -151,11 +157,73 @@ interface TransformOptions {
|
|
|
151
157
|
/** Quality (1-100) */
|
|
152
158
|
quality?: number;
|
|
153
159
|
}
|
|
160
|
+
/** Options for submitting content to a submission collection. */
|
|
161
|
+
interface SubmitOptions {
|
|
162
|
+
/** The submission collection name */
|
|
163
|
+
collection: string;
|
|
164
|
+
/** Field values for the submission */
|
|
165
|
+
fields: Record<string, unknown>;
|
|
166
|
+
/** Optional site user session token (adds X-Headroom-Session header) */
|
|
167
|
+
sessionToken?: string;
|
|
168
|
+
/** Optional relationships to associate with the submission */
|
|
169
|
+
relationships?: Record<string, {
|
|
170
|
+
contentId: string;
|
|
171
|
+
}[]>;
|
|
172
|
+
}
|
|
173
|
+
/** Result returned from a successful submission. */
|
|
174
|
+
interface SubmitResult {
|
|
175
|
+
/** The ID of the created content item */
|
|
176
|
+
contentId: string;
|
|
177
|
+
/** Unix timestamp of creation */
|
|
178
|
+
createdAt: number;
|
|
179
|
+
/** Site user ID if the submission was authenticated */
|
|
180
|
+
siteUserId?: string;
|
|
181
|
+
}
|
|
154
182
|
interface HeadroomErrorBody {
|
|
155
183
|
error: string;
|
|
156
184
|
code?: string;
|
|
157
185
|
}
|
|
158
186
|
|
|
187
|
+
interface AuthResult {
|
|
188
|
+
token: string;
|
|
189
|
+
expiresAt: number;
|
|
190
|
+
user: AuthUser;
|
|
191
|
+
}
|
|
192
|
+
interface AuthUser {
|
|
193
|
+
userId: string;
|
|
194
|
+
email: string;
|
|
195
|
+
name?: string;
|
|
196
|
+
tags?: string[];
|
|
197
|
+
fields?: Record<string, unknown>;
|
|
198
|
+
}
|
|
199
|
+
interface AuthSession {
|
|
200
|
+
user: AuthUser;
|
|
201
|
+
expiresAt: number;
|
|
202
|
+
token: string;
|
|
203
|
+
wasRefreshed: boolean;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* HeadroomAuth provides site user authentication methods.
|
|
207
|
+
* Access via `client.auth`.
|
|
208
|
+
*/
|
|
209
|
+
declare class HeadroomAuth {
|
|
210
|
+
private client;
|
|
211
|
+
/** @internal */
|
|
212
|
+
constructor(client: HeadroomClient);
|
|
213
|
+
/** Request an OTP code be sent to the email address */
|
|
214
|
+
requestOTP(email: string): Promise<void>;
|
|
215
|
+
/** Verify OTP code and receive a long-lived session token */
|
|
216
|
+
verifyOTP(email: string, code: string): Promise<AuthResult>;
|
|
217
|
+
/**
|
|
218
|
+
* Validate a session token and return the user.
|
|
219
|
+
* Automatically refreshes the token if it's past 75% of its lifetime,
|
|
220
|
+
* so callers can update their cookie with the new token if wasRefreshed is true.
|
|
221
|
+
*/
|
|
222
|
+
getSession(token: string): Promise<AuthSession>;
|
|
223
|
+
/** Refresh a session token for a new expiry period */
|
|
224
|
+
refreshSession(token: string): Promise<AuthSession>;
|
|
225
|
+
}
|
|
226
|
+
|
|
159
227
|
declare class HeadroomError extends Error {
|
|
160
228
|
status: number;
|
|
161
229
|
code: string;
|
|
@@ -163,6 +231,8 @@ declare class HeadroomError extends Error {
|
|
|
163
231
|
}
|
|
164
232
|
declare class HeadroomClient {
|
|
165
233
|
private config;
|
|
234
|
+
/** Site user authentication methods */
|
|
235
|
+
readonly auth: HeadroomAuth;
|
|
166
236
|
constructor(config: HeadroomConfig);
|
|
167
237
|
/** Build the full URL for a public API path */
|
|
168
238
|
private apiUrl;
|
|
@@ -189,13 +259,19 @@ declare class HeadroomClient {
|
|
|
189
259
|
transformUrl(path: string | undefined, opts?: TransformOptions): string;
|
|
190
260
|
private fetch;
|
|
191
261
|
private fetchPost;
|
|
262
|
+
/** @internal Used by HeadroomAuth */
|
|
263
|
+
_fetchPost<T>(path: string, body: unknown): Promise<T>;
|
|
264
|
+
/** @internal Used by HeadroomAuth — GET with Bearer token */
|
|
265
|
+
_fetchWithAuth<T>(path: string, token: string): Promise<T>;
|
|
266
|
+
/** @internal Used by HeadroomAuth — POST with Bearer token */
|
|
267
|
+
_fetchPostWithAuth<T>(path: string, token: string, body: unknown): Promise<T>;
|
|
192
268
|
listContent(collection: string, opts?: {
|
|
193
269
|
limit?: number;
|
|
194
270
|
cursor?: string;
|
|
195
271
|
before?: number;
|
|
196
272
|
after?: number;
|
|
197
|
-
/** Sort order: "published_desc" (default), "published_asc", "title_asc", "title_desc" */
|
|
198
|
-
sort?: "published_desc" | "published_asc" | "title_asc" | "title_desc";
|
|
273
|
+
/** Sort order. For regular queries: "published_desc" (default), "published_asc", "title_asc", "title_desc". For relatedTo queries: "created_desc" (default), "created_asc". */
|
|
274
|
+
sort?: "published_desc" | "published_asc" | "title_asc" | "title_desc" | "created_desc" | "created_asc";
|
|
199
275
|
/** Find content that has a relationship pointing to this content ID (reverse query) */
|
|
200
276
|
relatedTo?: string;
|
|
201
277
|
/** Filter reverse query to a specific relationship field name */
|
|
@@ -218,6 +294,13 @@ declare class HeadroomClient {
|
|
|
218
294
|
getCollection(name: string): Promise<Collection>;
|
|
219
295
|
listBlockTypes(): Promise<BlockTypeListResult>;
|
|
220
296
|
getVersion(): Promise<number>;
|
|
297
|
+
/**
|
|
298
|
+
* Submit content to a submission collection.
|
|
299
|
+
* Sends a POST to `/v1/{site}/submit/{collection}`.
|
|
300
|
+
* If a sessionToken is provided, it is sent as the `X-Headroom-Session` header
|
|
301
|
+
* to associate the submission with an authenticated site user.
|
|
302
|
+
*/
|
|
303
|
+
submit(options: SubmitOptions): Promise<SubmitResult>;
|
|
221
304
|
}
|
|
222
305
|
|
|
223
|
-
export { type BatchContentResult, type Block, type BlockTypeDef, type BlockTypeListResult, type Collection, type CollectionListResult, type CollectionSummary, type ContentItem, type ContentListResult, type ContentMetadata, type ContentRef, type FieldDef, HeadroomClient, type HeadroomConfig, HeadroomError, type HeadroomErrorBody, type InlineContent, type LinkContent, type PublicContentRef, type RefsMap, type RelationshipDef, type TableContent, type TableRow, type TextContent, type TextStyles, type TransformOptions };
|
|
306
|
+
export { type AuthResult, type AuthSession, type AuthUser, type BatchContentResult, type Block, type BlockTypeDef, type BlockTypeListResult, type Collection, type CollectionListResult, type CollectionSummary, type ContentItem, type ContentListResult, type ContentMetadata, type ContentRef, type FieldDef, HeadroomAuth, HeadroomClient, type HeadroomConfig, HeadroomError, type HeadroomErrorBody, type InlineContent, type LinkContent, type PublicContentRef, type RefsMap, type RelationshipDef, type SubmitOptions, type SubmitResult, type TableContent, type TableRow, type TextContent, type TextStyles, type TransformOptions };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,73 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
2
|
import { createHmac } from "crypto";
|
|
3
|
+
|
|
4
|
+
// src/auth.ts
|
|
5
|
+
var HeadroomAuth = class {
|
|
6
|
+
/** @internal */
|
|
7
|
+
constructor(client) {
|
|
8
|
+
this.client = client;
|
|
9
|
+
}
|
|
10
|
+
/** Request an OTP code be sent to the email address */
|
|
11
|
+
async requestOTP(email) {
|
|
12
|
+
await this.client._fetchPost("/auth/otp/request", { email });
|
|
13
|
+
}
|
|
14
|
+
/** Verify OTP code and receive a long-lived session token */
|
|
15
|
+
async verifyOTP(email, code) {
|
|
16
|
+
return this.client._fetchPost("/auth/otp/verify", {
|
|
17
|
+
email,
|
|
18
|
+
code
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validate a session token and return the user.
|
|
23
|
+
* Automatically refreshes the token if it's past 75% of its lifetime,
|
|
24
|
+
* so callers can update their cookie with the new token if wasRefreshed is true.
|
|
25
|
+
*/
|
|
26
|
+
async getSession(token) {
|
|
27
|
+
const res = await this.client._fetchWithAuth(
|
|
28
|
+
"/auth/session",
|
|
29
|
+
token
|
|
30
|
+
);
|
|
31
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
32
|
+
const remaining = res.expiresAt - now;
|
|
33
|
+
const lifetime = res.expiresAt - res.issuedAt;
|
|
34
|
+
if (remaining < lifetime * 0.25) {
|
|
35
|
+
try {
|
|
36
|
+
const refreshed = await this.refreshSession(token);
|
|
37
|
+
return { ...refreshed, wasRefreshed: true };
|
|
38
|
+
} catch {
|
|
39
|
+
return {
|
|
40
|
+
user: res.user,
|
|
41
|
+
expiresAt: res.expiresAt,
|
|
42
|
+
token,
|
|
43
|
+
wasRefreshed: false
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
user: res.user,
|
|
49
|
+
expiresAt: res.expiresAt,
|
|
50
|
+
token,
|
|
51
|
+
wasRefreshed: false
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Refresh a session token for a new expiry period */
|
|
55
|
+
async refreshSession(token) {
|
|
56
|
+
const res = await this.client._fetchPostWithAuth(
|
|
57
|
+
"/auth/session/refresh",
|
|
58
|
+
token,
|
|
59
|
+
{}
|
|
60
|
+
);
|
|
61
|
+
return {
|
|
62
|
+
user: res.user,
|
|
63
|
+
expiresAt: res.expiresAt,
|
|
64
|
+
token: res.token,
|
|
65
|
+
wasRefreshed: true
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// src/client.ts
|
|
3
71
|
var HeadroomError = class extends Error {
|
|
4
72
|
status;
|
|
5
73
|
code;
|
|
@@ -12,11 +80,14 @@ var HeadroomError = class extends Error {
|
|
|
12
80
|
};
|
|
13
81
|
var HeadroomClient = class {
|
|
14
82
|
config;
|
|
83
|
+
/** Site user authentication methods */
|
|
84
|
+
auth;
|
|
15
85
|
constructor(config) {
|
|
16
86
|
this.config = {
|
|
17
87
|
...config,
|
|
18
88
|
url: config.url.replace(/\/+$/, "")
|
|
19
89
|
};
|
|
90
|
+
this.auth = new HeadroomAuth(this);
|
|
20
91
|
}
|
|
21
92
|
/** Build the full URL for a public API path */
|
|
22
93
|
apiUrl(path) {
|
|
@@ -105,6 +176,57 @@ var HeadroomClient = class {
|
|
|
105
176
|
}
|
|
106
177
|
return res.json();
|
|
107
178
|
}
|
|
179
|
+
/** @internal Used by HeadroomAuth */
|
|
180
|
+
async _fetchPost(path, body) {
|
|
181
|
+
return this.fetchPost(path, body);
|
|
182
|
+
}
|
|
183
|
+
/** @internal Used by HeadroomAuth — GET with Bearer token */
|
|
184
|
+
async _fetchWithAuth(path, token) {
|
|
185
|
+
const url = this.apiUrl(path);
|
|
186
|
+
const res = await fetch(url, {
|
|
187
|
+
headers: {
|
|
188
|
+
"X-Headroom-Key": this.config.apiKey,
|
|
189
|
+
Authorization: `Bearer ${token}`
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
if (!res.ok) {
|
|
193
|
+
let code = "UNKNOWN";
|
|
194
|
+
let message = `HTTP ${res.status}`;
|
|
195
|
+
try {
|
|
196
|
+
const body = await res.json();
|
|
197
|
+
code = body.code || code;
|
|
198
|
+
message = body.error || message;
|
|
199
|
+
} catch {
|
|
200
|
+
}
|
|
201
|
+
throw new HeadroomError(res.status, code, `${message} (GET ${url})`);
|
|
202
|
+
}
|
|
203
|
+
return res.json();
|
|
204
|
+
}
|
|
205
|
+
/** @internal Used by HeadroomAuth — POST with Bearer token */
|
|
206
|
+
async _fetchPostWithAuth(path, token, body) {
|
|
207
|
+
const url = this.apiUrl(path);
|
|
208
|
+
const res = await fetch(url, {
|
|
209
|
+
method: "POST",
|
|
210
|
+
headers: {
|
|
211
|
+
"X-Headroom-Key": this.config.apiKey,
|
|
212
|
+
Authorization: `Bearer ${token}`,
|
|
213
|
+
"Content-Type": "application/json"
|
|
214
|
+
},
|
|
215
|
+
body: JSON.stringify(body)
|
|
216
|
+
});
|
|
217
|
+
if (!res.ok) {
|
|
218
|
+
let code = "UNKNOWN";
|
|
219
|
+
let message = `HTTP ${res.status}`;
|
|
220
|
+
try {
|
|
221
|
+
const errBody = await res.json();
|
|
222
|
+
code = errBody.code || code;
|
|
223
|
+
message = errBody.error || message;
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
throw new HeadroomError(res.status, code, `${message} (POST ${url})`);
|
|
227
|
+
}
|
|
228
|
+
return res.json();
|
|
229
|
+
}
|
|
108
230
|
// --- Content ---
|
|
109
231
|
async listContent(collection, opts) {
|
|
110
232
|
const params = new URLSearchParams({ collection });
|
|
@@ -163,8 +285,46 @@ var HeadroomClient = class {
|
|
|
163
285
|
const result = await this.fetch("/version");
|
|
164
286
|
return result.contentVersion;
|
|
165
287
|
}
|
|
288
|
+
// --- Submissions ---
|
|
289
|
+
/**
|
|
290
|
+
* Submit content to a submission collection.
|
|
291
|
+
* Sends a POST to `/v1/{site}/submit/{collection}`.
|
|
292
|
+
* If a sessionToken is provided, it is sent as the `X-Headroom-Session` header
|
|
293
|
+
* to associate the submission with an authenticated site user.
|
|
294
|
+
*/
|
|
295
|
+
async submit(options) {
|
|
296
|
+
const url = this.apiUrl(`/submit/${encodeURIComponent(options.collection)}`);
|
|
297
|
+
const headers = {
|
|
298
|
+
"X-Headroom-Key": this.config.apiKey,
|
|
299
|
+
"Content-Type": "application/json"
|
|
300
|
+
};
|
|
301
|
+
if (options.sessionToken) {
|
|
302
|
+
headers["X-Headroom-Session"] = options.sessionToken;
|
|
303
|
+
}
|
|
304
|
+
const res = await fetch(url, {
|
|
305
|
+
method: "POST",
|
|
306
|
+
headers,
|
|
307
|
+
body: JSON.stringify({
|
|
308
|
+
fields: options.fields,
|
|
309
|
+
...options.relationships && { relationships: options.relationships }
|
|
310
|
+
})
|
|
311
|
+
});
|
|
312
|
+
if (!res.ok) {
|
|
313
|
+
let code = "UNKNOWN";
|
|
314
|
+
let message = `HTTP ${res.status}`;
|
|
315
|
+
try {
|
|
316
|
+
const errBody = await res.json();
|
|
317
|
+
code = errBody.code || code;
|
|
318
|
+
message = errBody.error || message;
|
|
319
|
+
} catch {
|
|
320
|
+
}
|
|
321
|
+
throw new HeadroomError(res.status, code, `${message} (POST ${url})`);
|
|
322
|
+
}
|
|
323
|
+
return res.json();
|
|
324
|
+
}
|
|
166
325
|
};
|
|
167
326
|
export {
|
|
327
|
+
HeadroomAuth,
|
|
168
328
|
HeadroomClient,
|
|
169
329
|
HeadroomError
|
|
170
330
|
};
|
package/dist/next.cjs
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/next.ts
|
|
22
|
+
var next_exports = {};
|
|
23
|
+
__export(next_exports, {
|
|
24
|
+
DevRefresh: () => DevRefresh
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(next_exports);
|
|
27
|
+
|
|
28
|
+
// src/next/DevRefresh.tsx
|
|
29
|
+
var import_react = require("react");
|
|
30
|
+
var import_navigation = require("next/navigation");
|
|
31
|
+
function DevRefresh({
|
|
32
|
+
versionUrl,
|
|
33
|
+
apiKey,
|
|
34
|
+
interval = 2e3
|
|
35
|
+
}) {
|
|
36
|
+
const router = (0, import_navigation.useRouter)();
|
|
37
|
+
const lastVersion = (0, import_react.useRef)(null);
|
|
38
|
+
(0, import_react.useEffect)(() => {
|
|
39
|
+
const controller = new AbortController();
|
|
40
|
+
const poll = async () => {
|
|
41
|
+
try {
|
|
42
|
+
const res = await fetch(versionUrl, {
|
|
43
|
+
headers: { "X-Headroom-Key": apiKey },
|
|
44
|
+
signal: controller.signal
|
|
45
|
+
});
|
|
46
|
+
if (!res.ok) return;
|
|
47
|
+
const data = await res.json();
|
|
48
|
+
const version = data.contentVersion;
|
|
49
|
+
if (lastVersion.current !== null && version !== lastVersion.current) {
|
|
50
|
+
router.refresh();
|
|
51
|
+
}
|
|
52
|
+
lastVersion.current = version;
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const id = setInterval(poll, interval);
|
|
57
|
+
poll();
|
|
58
|
+
return () => {
|
|
59
|
+
controller.abort();
|
|
60
|
+
clearInterval(id);
|
|
61
|
+
};
|
|
62
|
+
}, [versionUrl, apiKey, interval, router]);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
66
|
+
0 && (module.exports = {
|
|
67
|
+
DevRefresh
|
|
68
|
+
});
|
package/dist/next.d.cts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface DevRefreshProps {
|
|
2
|
+
/** URL to poll for version changes (full URL to /v1/{site}/version) */
|
|
3
|
+
versionUrl: string;
|
|
4
|
+
/** API key header value */
|
|
5
|
+
apiKey: string;
|
|
6
|
+
/** Poll interval in milliseconds (default: 2000) */
|
|
7
|
+
interval?: number;
|
|
8
|
+
}
|
|
9
|
+
declare function DevRefresh({ versionUrl, apiKey, interval, }: DevRefreshProps): null;
|
|
10
|
+
|
|
11
|
+
export { DevRefresh, type DevRefreshProps };
|
package/dist/next.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface DevRefreshProps {
|
|
2
|
+
/** URL to poll for version changes (full URL to /v1/{site}/version) */
|
|
3
|
+
versionUrl: string;
|
|
4
|
+
/** API key header value */
|
|
5
|
+
apiKey: string;
|
|
6
|
+
/** Poll interval in milliseconds (default: 2000) */
|
|
7
|
+
interval?: number;
|
|
8
|
+
}
|
|
9
|
+
declare function DevRefresh({ versionUrl, apiKey, interval, }: DevRefreshProps): null;
|
|
10
|
+
|
|
11
|
+
export { DevRefresh, type DevRefreshProps };
|
package/dist/next.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/next/DevRefresh.tsx
|
|
4
|
+
import { useEffect, useRef } from "react";
|
|
5
|
+
import { useRouter } from "next/navigation";
|
|
6
|
+
function DevRefresh({
|
|
7
|
+
versionUrl,
|
|
8
|
+
apiKey,
|
|
9
|
+
interval = 2e3
|
|
10
|
+
}) {
|
|
11
|
+
const router = useRouter();
|
|
12
|
+
const lastVersion = useRef(null);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const controller = new AbortController();
|
|
15
|
+
const poll = async () => {
|
|
16
|
+
try {
|
|
17
|
+
const res = await fetch(versionUrl, {
|
|
18
|
+
headers: { "X-Headroom-Key": apiKey },
|
|
19
|
+
signal: controller.signal
|
|
20
|
+
});
|
|
21
|
+
if (!res.ok) return;
|
|
22
|
+
const data = await res.json();
|
|
23
|
+
const version = data.contentVersion;
|
|
24
|
+
if (lastVersion.current !== null && version !== lastVersion.current) {
|
|
25
|
+
router.refresh();
|
|
26
|
+
}
|
|
27
|
+
lastVersion.current = version;
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const id = setInterval(poll, interval);
|
|
32
|
+
poll();
|
|
33
|
+
return () => {
|
|
34
|
+
controller.abort();
|
|
35
|
+
clearInterval(id);
|
|
36
|
+
};
|
|
37
|
+
}, [versionUrl, apiKey, interval, router]);
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
DevRefresh
|
|
42
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@headroom-cms/api",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -19,6 +19,11 @@
|
|
|
19
19
|
"types": "./dist/astro.d.ts",
|
|
20
20
|
"import": "./dist/astro.js"
|
|
21
21
|
},
|
|
22
|
+
"./next": {
|
|
23
|
+
"types": "./dist/next.d.ts",
|
|
24
|
+
"import": "./dist/next.js",
|
|
25
|
+
"require": "./dist/next.cjs"
|
|
26
|
+
},
|
|
22
27
|
"./codegen": {
|
|
23
28
|
"types": "./dist/codegen.d.ts",
|
|
24
29
|
"import": "./dist/codegen.js",
|
|
@@ -41,9 +46,10 @@
|
|
|
41
46
|
"typecheck": "tsc --noEmit"
|
|
42
47
|
},
|
|
43
48
|
"peerDependencies": {
|
|
49
|
+
"astro": ">=5",
|
|
50
|
+
"next": ">=14",
|
|
44
51
|
"react": ">=18",
|
|
45
|
-
"react-dom": ">=18"
|
|
46
|
-
"astro": ">=5"
|
|
52
|
+
"react-dom": ">=18"
|
|
47
53
|
},
|
|
48
54
|
"peerDependenciesMeta": {
|
|
49
55
|
"react": {
|
|
@@ -54,19 +60,23 @@
|
|
|
54
60
|
},
|
|
55
61
|
"astro": {
|
|
56
62
|
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"next": {
|
|
65
|
+
"optional": true
|
|
57
66
|
}
|
|
58
67
|
},
|
|
59
68
|
"devDependencies": {
|
|
60
69
|
"@testing-library/jest-dom": "~6.6.3",
|
|
61
70
|
"@testing-library/react": "~16.3.0",
|
|
71
|
+
"@types/node": "~22.0.0",
|
|
62
72
|
"@types/react": "~19.1.0",
|
|
73
|
+
"astro": "^5.0.0",
|
|
63
74
|
"jsdom": "~26.1.0",
|
|
75
|
+
"next": "^16.2.1",
|
|
64
76
|
"react": "~19.1.0",
|
|
65
77
|
"react-dom": "~19.1.0",
|
|
66
78
|
"tsup": "~8.5.0",
|
|
67
79
|
"typescript": "~5.9.3",
|
|
68
|
-
"astro": "^5.0.0",
|
|
69
|
-
"@types/node": "~22.0.0",
|
|
70
80
|
"vitest": "~4.0.18"
|
|
71
81
|
}
|
|
72
82
|
}
|