astro-integration-pocketbase 3.1.0 → 3.1.2-next.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/{src/types/pocketbase-integration-options.type.ts → dist/index.d.mts} +26 -19
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +237 -0
- package/dist/index.mjs.map +1 -0
- package/dist/middleware.d.mts +5 -0
- package/dist/middleware.d.mts.map +1 -0
- package/dist/middleware.mjs +53 -0
- package/dist/middleware.mjs.map +1 -0
- package/dist/toolbar.d.mts +5 -0
- package/dist/toolbar.d.mts.map +1 -0
- package/dist/toolbar.mjs +254 -0
- package/dist/toolbar.mjs.map +1 -0
- package/package.json +24 -14
- package/index.ts +0 -5
- package/src/core/index.ts +0 -2
- package/src/core/refresh-collections-realtime.ts +0 -169
- package/src/core/refresh-collections.ts +0 -46
- package/src/middleware/index.ts +0 -50
- package/src/middleware/is-pocketbase-entry.ts +0 -33
- package/src/pocketbase-integration.ts +0 -75
- package/src/toolbar/dom/create-entity.ts +0 -69
- package/src/toolbar/dom/create-header.ts +0 -153
- package/src/toolbar/dom/create-placeholder.ts +0 -20
- package/src/toolbar/dom/index.ts +0 -3
- package/src/toolbar/index.ts +0 -6
- package/src/toolbar/init-toolbar.ts +0 -98
- package/src/toolbar/types/entity.ts +0 -8
- package/src/toolbar/types/options.ts +0 -17
- package/src/tsconfig.json +0 -7
- package/src/types/pocketbase-api-response.type.ts +0 -21
- package/src/utils/get-superuser-token.ts +0 -68
- package/src/utils/map-collections-to-watch.ts +0 -56
- package/src/utils/push-to-map-array.ts +0 -16
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { AstroIntegration } from "astro";
|
|
2
|
+
|
|
3
|
+
//#region src/types/pocketbase-integration-options.type.d.ts
|
|
1
4
|
/**
|
|
2
5
|
* Options for the PocketBase integration.
|
|
3
6
|
*/
|
|
4
|
-
|
|
7
|
+
interface PocketBaseIntegrationOptions {
|
|
5
8
|
/**
|
|
6
9
|
* URL of the PocketBase instance.
|
|
7
10
|
*/
|
|
@@ -10,24 +13,22 @@ export interface PocketBaseIntegrationOptions {
|
|
|
10
13
|
* Credentials of a superuser to get full access to the PocketBase instance.
|
|
11
14
|
* This is required to access all resources even if they are not public.
|
|
12
15
|
*/
|
|
13
|
-
superuserCredentials?:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
impersonateToken: string;
|
|
30
|
-
};
|
|
16
|
+
superuserCredentials?: {
|
|
17
|
+
/**
|
|
18
|
+
* Email of the superuser.
|
|
19
|
+
*/
|
|
20
|
+
email: string;
|
|
21
|
+
/**
|
|
22
|
+
* Password of the superuser.
|
|
23
|
+
*/
|
|
24
|
+
password: string;
|
|
25
|
+
} | {
|
|
26
|
+
/**
|
|
27
|
+
* Impersonate auth token of the superuser.
|
|
28
|
+
* This token will take precedence over the email and password.
|
|
29
|
+
*/
|
|
30
|
+
impersonateToken: string;
|
|
31
|
+
};
|
|
31
32
|
/**
|
|
32
33
|
* List of PocketBase collections to watch for changes.
|
|
33
34
|
* When an entry in one of these collections changes, the content will be reloaded.
|
|
@@ -56,3 +57,9 @@ export interface PocketBaseIntegrationOptions {
|
|
|
56
57
|
*/
|
|
57
58
|
collectionsToWatch?: Array<string> | Record<string, true | Array<string>>;
|
|
58
59
|
}
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/pocketbase-integration.d.ts
|
|
62
|
+
declare function pocketbaseIntegration(options: PocketBaseIntegrationOptions): AstroIntegration;
|
|
63
|
+
//#endregion
|
|
64
|
+
export { type PocketBaseIntegrationOptions, pocketbaseIntegration };
|
|
65
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/pocketbase-integration-options.type.ts","../src/pocketbase-integration.ts"],"mappings":";;;;;;UAGiB,4BAAA;EAA4B;;;EAI3C,GAAA;EAiDqC;;;;EA5CrC,oBAAA;IAKM;;;IAAA,KAAA;IAuCe;;;IAnCf,QAAA;EAAA;;;;ACdR;IDqBQ,gBAAA;EAAA;ECnBW;;;;;AAAA;;;;;;;;;;;;;;;;;;;;;ED+CjB,kBAAA,GAAqB,KAAA,WAAgB,MAAA,gBAAsB,KAAA;AAAA;;;iBCjD7C,qBAAA,CACd,OAAA,EAAS,4BAAA,GACR,gBAAgB"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { EventSource } from "eventsource";
|
|
3
|
+
import { z } from "astro/zod";
|
|
4
|
+
//#region src/core/refresh-collections.ts
|
|
5
|
+
/**
|
|
6
|
+
* Listen for the refresh event of the toolbar.
|
|
7
|
+
* When the event is triggered in the toolbar, refresh the content loaded by the PocketBase loader.
|
|
8
|
+
*/
|
|
9
|
+
function handleRefreshCollections({ toolbar, refreshContent, logger }) {
|
|
10
|
+
if (!refreshContent) return;
|
|
11
|
+
logger.info("Setting up refresh listener for PocketBase integration");
|
|
12
|
+
toolbar.on("astro-integration-pocketbase:refresh", async ({ force }) => {
|
|
13
|
+
toolbar.send("astro-integration-pocketbase:refresh", { loading: true });
|
|
14
|
+
logger.info(`Refreshing ${force ? "all " : ""}content loaded by PocketBase loader`);
|
|
15
|
+
await refreshContent({
|
|
16
|
+
loaders: ["pocketbase-loader"],
|
|
17
|
+
context: {
|
|
18
|
+
source: "astro-integration-pocketbase",
|
|
19
|
+
force
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
toolbar.send("astro-integration-pocketbase:refresh", { loading: false });
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/types/pocketbase-api-response.type.ts
|
|
27
|
+
/**
|
|
28
|
+
* The schema for a PocketBase error response.
|
|
29
|
+
*/
|
|
30
|
+
const pocketBaseErrorResponse = z.object({
|
|
31
|
+
/**
|
|
32
|
+
* The error message returned by PocketBase.
|
|
33
|
+
*/
|
|
34
|
+
message: z.string() });
|
|
35
|
+
/**
|
|
36
|
+
* The schema for a PocketBase login response.
|
|
37
|
+
*/
|
|
38
|
+
const pocketBaseLoginResponse = z.object({
|
|
39
|
+
/**
|
|
40
|
+
* The authentication token returned by PocketBase.
|
|
41
|
+
*/
|
|
42
|
+
token: z.string() });
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/utils/get-superuser-token.ts
|
|
45
|
+
/**
|
|
46
|
+
* This function will get a superuser token from the given PocketBase instance.
|
|
47
|
+
*
|
|
48
|
+
* @param url URL of the PocketBase instance
|
|
49
|
+
* @param superuserCredentials Credentials of the superuser
|
|
50
|
+
*
|
|
51
|
+
* @returns A superuser token to access all resources of the PocketBase instance.
|
|
52
|
+
*/
|
|
53
|
+
async function getSuperuserToken(url, superuserCredentials, logger) {
|
|
54
|
+
const loginUrl = new URL(`api/collections/_superusers/auth-with-password`, url).href;
|
|
55
|
+
const loginData = new FormData();
|
|
56
|
+
loginData.set("identity", superuserCredentials.email);
|
|
57
|
+
loginData.set("password", superuserCredentials.password);
|
|
58
|
+
const loginRequest = await fetch(loginUrl, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
body: loginData
|
|
61
|
+
});
|
|
62
|
+
if (!loginRequest.ok) {
|
|
63
|
+
if (loginRequest.status === 429) {
|
|
64
|
+
logger.info("A rate limit was hit while trying to authenticate with PocketBase. Consider using an `impersonateToken` as credentials to avoid this issue.");
|
|
65
|
+
const retryAfter = Math.random() * 5 + 3;
|
|
66
|
+
await new Promise((resolve) => {
|
|
67
|
+
setTimeout(resolve, retryAfter * 1e3);
|
|
68
|
+
});
|
|
69
|
+
return getSuperuserToken(url, superuserCredentials, logger);
|
|
70
|
+
}
|
|
71
|
+
const errorMessage = `The given email / password for ${url} was not correct. Astro can't generate type definitions automatically and may not have access to all resources.\nReason: ${pocketBaseErrorResponse.parse(await loginRequest.json()).message}`;
|
|
72
|
+
logger.error(errorMessage);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
return pocketBaseLoginResponse.parse(await loginRequest.json()).token;
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/utils/push-to-map-array.ts
|
|
79
|
+
/**
|
|
80
|
+
* Adds a value to an array in a map. If the key does not exist, it will be created.
|
|
81
|
+
*/
|
|
82
|
+
function pushToMapArray(map, key, value) {
|
|
83
|
+
const array = map.get(key);
|
|
84
|
+
if (array) {
|
|
85
|
+
array.push(value);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
map.set(key, [value]);
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/utils/map-collections-to-watch.ts
|
|
92
|
+
/**
|
|
93
|
+
* Create a map of remote collections to watch.
|
|
94
|
+
* Each key in the map represents a remote collection to subscribe to.
|
|
95
|
+
* The value is an array of local collections that should be refreshed when an entry in the remote collection changes.
|
|
96
|
+
*/
|
|
97
|
+
function mapCollectionsToWatch(collectionsToWatch) {
|
|
98
|
+
if (!collectionsToWatch) return;
|
|
99
|
+
if (Array.isArray(collectionsToWatch)) {
|
|
100
|
+
if (collectionsToWatch.length === 0) return;
|
|
101
|
+
return new Map(collectionsToWatch.map((collection) => [collection, [collection]]));
|
|
102
|
+
}
|
|
103
|
+
if (Object.keys(collectionsToWatch).length === 0) return;
|
|
104
|
+
const collectionsMap = /* @__PURE__ */ new Map();
|
|
105
|
+
for (const localCollection in collectionsToWatch) {
|
|
106
|
+
const watch = collectionsToWatch[localCollection];
|
|
107
|
+
if (!watch) continue;
|
|
108
|
+
if (watch === true) {
|
|
109
|
+
pushToMapArray(collectionsMap, localCollection, localCollection);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
for (const remoteCollection of watch) pushToMapArray(collectionsMap, remoteCollection, localCollection);
|
|
113
|
+
}
|
|
114
|
+
return collectionsMap;
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/core/refresh-collections-realtime.ts
|
|
118
|
+
function refreshCollectionsRealtime(options, { logger, refreshContent, toolbar }) {
|
|
119
|
+
const collectionsMap = mapCollectionsToWatch(options.collectionsToWatch);
|
|
120
|
+
if (!collectionsMap) return;
|
|
121
|
+
const remoteCollections = [...collectionsMap.keys()];
|
|
122
|
+
if (!refreshContent) {
|
|
123
|
+
logger.warn("No content loader available, skipping subscription to PocketBase realtime API.");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!EventSource) {
|
|
127
|
+
logger.warn("EventSource is not available, skipping subscription to PocketBase realtime API.\nPlease install the 'eventsource' package.");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
let refreshEnabled = true;
|
|
131
|
+
toolbar.on("astro-integration-pocketbase:real-time", (enabled) => {
|
|
132
|
+
refreshEnabled = enabled;
|
|
133
|
+
});
|
|
134
|
+
const eventSourceUrl = new URL("api/realtime", options.url).href;
|
|
135
|
+
const eventSource = new EventSource(eventSourceUrl);
|
|
136
|
+
let wasConnectedOnce = false;
|
|
137
|
+
let isConnected = false;
|
|
138
|
+
eventSource.addEventListener("error", (error) => {
|
|
139
|
+
isConnected = false;
|
|
140
|
+
setTimeout(() => {
|
|
141
|
+
if (isConnected) return;
|
|
142
|
+
logger.error(`Error while connecting to PocketBase realtime API: ${error.type}`);
|
|
143
|
+
}, 5e3);
|
|
144
|
+
});
|
|
145
|
+
for (const collection of remoteCollections) eventSource.addEventListener(`${collection}/*`, async (event) => {
|
|
146
|
+
if (!refreshEnabled) return;
|
|
147
|
+
logger.info(`Received update for ${collection}. Refreshing content...`);
|
|
148
|
+
await refreshContent({
|
|
149
|
+
loaders: ["pocketbase-loader"],
|
|
150
|
+
context: {
|
|
151
|
+
source: "astro-integration-pocketbase",
|
|
152
|
+
collection: collectionsMap.get(collection),
|
|
153
|
+
data: JSON.parse(event.data)
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
eventSource.addEventListener("PB_CONNECT", async (event) => {
|
|
158
|
+
isConnected = await handleConnectEvent(event, remoteCollections, wasConnectedOnce, options, logger);
|
|
159
|
+
if (isConnected) wasConnectedOnce = true;
|
|
160
|
+
});
|
|
161
|
+
return eventSource;
|
|
162
|
+
}
|
|
163
|
+
async function handleConnectEvent(event, remoteCollections, wasConnectedOnce, options, logger) {
|
|
164
|
+
const clientId = event.lastEventId;
|
|
165
|
+
let superuserToken;
|
|
166
|
+
if (options.superuserCredentials) if ("impersonateToken" in options.superuserCredentials) superuserToken = options.superuserCredentials.impersonateToken;
|
|
167
|
+
else superuserToken = await getSuperuserToken(options.url, options.superuserCredentials, logger);
|
|
168
|
+
const subscriptionUrl = new URL("api/realtime", options.url).href;
|
|
169
|
+
const result = await fetch(subscriptionUrl, {
|
|
170
|
+
method: "POST",
|
|
171
|
+
headers: {
|
|
172
|
+
"Content-Type": "application/json",
|
|
173
|
+
Authorization: superuserToken || ""
|
|
174
|
+
},
|
|
175
|
+
body: JSON.stringify({
|
|
176
|
+
clientId,
|
|
177
|
+
subscriptions: remoteCollections.map((collection) => `${collection}/*`)
|
|
178
|
+
})
|
|
179
|
+
});
|
|
180
|
+
if (!result.ok) {
|
|
181
|
+
logger.error(`Error while subscribing to PocketBase realtime API: ${result.status}`);
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
if (!wasConnectedOnce) logger.info(`Subscribed to PocketBase realtime API. Waiting for updates on ${remoteCollections.join(", ")}.`);
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region src/pocketbase-integration.ts
|
|
189
|
+
function pocketbaseIntegration(options) {
|
|
190
|
+
let eventSource = void 0;
|
|
191
|
+
let initialSetupDone = false;
|
|
192
|
+
return {
|
|
193
|
+
name: "pocketbase-integration",
|
|
194
|
+
hooks: {
|
|
195
|
+
"astro:config:setup": ({ addDevToolbarApp, addMiddleware, command }) => {
|
|
196
|
+
if (command !== "dev") return;
|
|
197
|
+
addDevToolbarApp({
|
|
198
|
+
name: "PocketBase",
|
|
199
|
+
id: `pocketbase-entry`,
|
|
200
|
+
icon: `<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>PocketBase</title><path fill="currentColor" d="M5.684 12a.632.632 0 0 1-.631-.632V4.421c0-.349.282-.632.631-.632h2.37c.46 0 .889.047 1.287.139.407.084.758.23 1.053.44.303.202.541.475.715.82.173.335.26.75.26 1.246 0 .479-.092.894-.273 1.247a2.373 2.373 0 0 1-.715.869 3.11 3.11 0 0 1-1.053.503c-.398.11-.823.164-1.273.164h-.46a.632.632 0 0 0-.632.632v1.52a.632.632 0 0 1-.632.631Zm1.279-4.888c0 .349.283.632.632.632h.343c1.04 0 1.56-.437 1.56-1.31 0-.428-.135-.73-.404-.907-.26-.176-.645-.264-1.156-.264h-.343a.632.632 0 0 0-.632.631Zm6.3 13.098a.632.632 0 0 1-.631-.631v-6.947a.63.63 0 0 1 .631-.632h2.203c.44 0 .845.034 1.216.1.38.06.708.169.984.328.276.16.492.37.647.63.164.26.246.587.246.982 0 .185-.03.37-.09.554a1.537 1.537 0 0 1-.26.516 1.857 1.857 0 0 1-1.076.7.031.031 0 0 0-.023.03c0 .015.01.028.025.03.591.111 1.04.32 1.346.626.311.31.466.743.466 1.297 0 .42-.082.78-.246 1.083a2.153 2.153 0 0 1-.685.755 3.4 3.4 0 0 1-1.036.441 5.477 5.477 0 0 1-1.268.139zm1.271-5.542c0 .349.283.631.632.631h.21c.465 0 .802-.088 1.009-.264.207-.176.31-.424.31-.743 0-.302-.107-.516-.323-.642-.207-.135-.535-.202-.984-.202h-.222a.632.632 0 0 0-.632.632Zm0 3.463c0 .349.283.631.632.631h.39c1.019 0 1.528-.369 1.528-1.108 0-.36-.125-.621-.376-.78-.241-.16-.625-.24-1.152-.24h-.39a.632.632 0 0 0-.632.632zM1.389 0C.629 0 0 .629 0 1.389V15.03a1.4 1.4 0 0 0 1.389 1.39H8.21a.632.632 0 0 0 .63-.632.632.632 0 0 0-.63-.63H1.389c-.078 0-.125-.05-.125-.128V1.39c0-.078.047-.125.125-.125H15.03c.078 0 .127.047.127.125v6.82a.632.632 0 0 0 .631.63.632.632 0 0 0 .633-.63V1.389A1.4 1.4 0 0 0 15.032 0ZM15.79 7.578a.632.632 0 0 0-.632.633.632.632 0 0 0 .631.63h6.822c.078 0 .125.05.125.128V22.61c0 .078-.047.125-.125.125H8.97c-.077 0-.127-.047-.127-.125v-6.82a.632.632 0 0 0-.631-.63.632.632 0 0 0-.633.63v6.822A1.4 1.4 0 0 0 8.968 24h13.643c.76 0 1.389-.629 1.389-1.389V8.97a1.4 1.4 0 0 0-1.389-1.39Z"/></svg>`,
|
|
201
|
+
entrypoint: fileURLToPath(new URL("./toolbar", import.meta.url))
|
|
202
|
+
});
|
|
203
|
+
addMiddleware({
|
|
204
|
+
order: "post",
|
|
205
|
+
entrypoint: fileURLToPath(new URL("./middleware", import.meta.url))
|
|
206
|
+
});
|
|
207
|
+
},
|
|
208
|
+
"astro:server:setup": (setupOptions) => {
|
|
209
|
+
if (!initialSetupDone) handleRefreshCollections(setupOptions);
|
|
210
|
+
if (eventSource) {
|
|
211
|
+
eventSource.close();
|
|
212
|
+
eventSource = void 0;
|
|
213
|
+
}
|
|
214
|
+
eventSource = refreshCollectionsRealtime(options, setupOptions);
|
|
215
|
+
setupOptions.toolbar.onAppInitialized("pocketbase-entry", () => {
|
|
216
|
+
setupOptions.toolbar.send("astro-integration-pocketbase:settings", {
|
|
217
|
+
hasContentLoader: !!setupOptions.refreshContent,
|
|
218
|
+
realtime: !!eventSource,
|
|
219
|
+
baseUrl: options.url
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
initialSetupDone = true;
|
|
223
|
+
},
|
|
224
|
+
"astro:server:done": ({ logger }) => {
|
|
225
|
+
if (eventSource) {
|
|
226
|
+
logger.info("Closing EventSource connection");
|
|
227
|
+
eventSource.close();
|
|
228
|
+
eventSource = void 0;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
//#endregion
|
|
235
|
+
export { pocketbaseIntegration };
|
|
236
|
+
|
|
237
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/core/refresh-collections.ts","../src/types/pocketbase-api-response.type.ts","../src/utils/get-superuser-token.ts","../src/utils/push-to-map-array.ts","../src/utils/map-collections-to-watch.ts","../src/core/refresh-collections-realtime.ts","../src/pocketbase-integration.ts"],"sourcesContent":["import type { BaseIntegrationHooks } from \"astro\";\n\n/**\n * Listen for the refresh event of the toolbar.\n * When the event is triggered in the toolbar, refresh the content loaded by the PocketBase loader.\n */\nexport function handleRefreshCollections({\n toolbar,\n refreshContent,\n logger\n}: Parameters<BaseIntegrationHooks[\"astro:server:setup\"]>[0]): void {\n if (!refreshContent) {\n return;\n }\n\n logger.info(\"Setting up refresh listener for PocketBase integration\");\n\n // Listen for the refresh event of the toolbar\n toolbar.on(\n \"astro-integration-pocketbase:refresh\",\n // oxlint-disable-next-line strict-void-return\n async ({ force }: { force: boolean }) => {\n // Send a loading state to the toolbar\n toolbar.send(\"astro-integration-pocketbase:refresh\", {\n loading: true\n });\n\n // Refresh content loaded by the PocketBase loader\n logger.info(\n `Refreshing ${force ? \"all \" : \"\"}content loaded by PocketBase loader`\n );\n await refreshContent({\n loaders: [\"pocketbase-loader\"],\n context: {\n source: \"astro-integration-pocketbase\",\n force\n }\n });\n\n // Reset the loading state in the toolbar\n toolbar.send(\"astro-integration-pocketbase:refresh\", {\n loading: false\n });\n }\n );\n}\n","import { z } from \"astro/zod\";\n\n/**\n * The schema for a PocketBase error response.\n */\nexport const pocketBaseErrorResponse = z.object({\n /**\n * The error message returned by PocketBase.\n */\n message: z.string()\n});\n\n/**\n * The schema for a PocketBase login response.\n */\nexport const pocketBaseLoginResponse = z.object({\n /**\n * The authentication token returned by PocketBase.\n */\n token: z.string()\n});\n","import type { AstroIntegrationLogger } from \"astro\";\nimport {\n pocketBaseErrorResponse,\n pocketBaseLoginResponse\n} from \"../types/pocketbase-api-response.type\";\n\n/**\n * This function will get a superuser token from the given PocketBase instance.\n *\n * @param url URL of the PocketBase instance\n * @param superuserCredentials Credentials of the superuser\n *\n * @returns A superuser token to access all resources of the PocketBase instance.\n */\nexport async function getSuperuserToken(\n url: string,\n superuserCredentials: {\n email: string;\n password: string;\n },\n logger: AstroIntegrationLogger\n): Promise<string | undefined> {\n // Build the URL for the login endpoint\n const loginUrl = new URL(\n `api/collections/_superusers/auth-with-password`,\n url\n ).href;\n\n // Create a new FormData object to send the login data\n const loginData = new FormData();\n loginData.set(\"identity\", superuserCredentials.email);\n loginData.set(\"password\", superuserCredentials.password);\n\n // Send the login request to get a token\n const loginRequest = await fetch(loginUrl, {\n method: \"POST\",\n body: loginData\n });\n\n // If the login request was not successful, print the error message and return undefined\n if (!loginRequest.ok) {\n if (loginRequest.status === 429) {\n const info =\n \"A rate limit was hit while trying to authenticate with PocketBase. Consider using an `impersonateToken` as credentials to avoid this issue.\";\n logger.info(info);\n\n // Random wait between 3 (default rate limit interval) and 8 seconds\n const retryAfter = Math.random() * 5 + 3;\n // oxlint-disable-next-line promise/avoid-new\n await new Promise((resolve) => {\n setTimeout(resolve, retryAfter * 1000);\n });\n\n return getSuperuserToken(url, superuserCredentials, logger);\n }\n\n const errorResponse = pocketBaseErrorResponse.parse(\n await loginRequest.json()\n );\n const errorMessage = `The given email / password for ${url} was not correct. Astro can't generate type definitions automatically and may not have access to all resources.\\nReason: ${errorResponse.message}`;\n logger.error(errorMessage);\n return undefined;\n }\n\n // Return the token\n const response = pocketBaseLoginResponse.parse(await loginRequest.json());\n return response.token;\n}\n","/**\n * Adds a value to an array in a map. If the key does not exist, it will be created.\n */\nexport function pushToMapArray<TKey, TArray>(\n map: Map<TKey, Array<TArray>>,\n key: TKey,\n value: TArray\n): void {\n const array = map.get(key);\n if (array) {\n array.push(value);\n return;\n }\n\n map.set(key, [value]);\n}\n","import type { PocketBaseIntegrationOptions } from \"../types/pocketbase-integration-options.type\";\nimport { pushToMapArray } from \"./push-to-map-array\";\n\n/**\n * Create a map of remote collections to watch.\n * Each key in the map represents a remote collection to subscribe to.\n * The value is an array of local collections that should be refreshed when an entry in the remote collection changes.\n */\nexport function mapCollectionsToWatch(\n collectionsToWatch: PocketBaseIntegrationOptions[\"collectionsToWatch\"]\n): Map<string, Array<string>> | undefined {\n // Check if collections should be watched\n if (!collectionsToWatch) {\n return undefined;\n }\n\n // Check if collectionsToWatch is an array\n if (Array.isArray(collectionsToWatch)) {\n // Check if the array is empty\n if (collectionsToWatch.length === 0) {\n return undefined;\n }\n\n // Create a map where each collection is watched by itself\n return new Map(\n collectionsToWatch.map((collection) => [collection, [collection]])\n );\n }\n\n // Check if collectionsToWatch is an empty object\n if (Object.keys(collectionsToWatch).length === 0) {\n return undefined;\n }\n\n // Map collections to watch\n const collectionsMap = new Map<string, Array<string>>();\n for (const localCollection in collectionsToWatch) {\n const watch = collectionsToWatch[localCollection];\n if (!watch) {\n continue;\n }\n\n // Check if collection should be watched by itself\n if (watch === true) {\n pushToMapArray(collectionsMap, localCollection, localCollection);\n continue;\n }\n\n // Collection should be watched by multiple collections\n for (const remoteCollection of watch) {\n pushToMapArray(collectionsMap, remoteCollection, localCollection);\n }\n }\n\n return collectionsMap;\n}\n","import type { AstroIntegrationLogger, BaseIntegrationHooks } from \"astro\";\nimport { EventSource } from \"eventsource\";\nimport type { PocketBaseIntegrationOptions } from \"../types/pocketbase-integration-options.type\";\nimport { getSuperuserToken } from \"../utils/get-superuser-token\";\nimport { mapCollectionsToWatch } from \"../utils/map-collections-to-watch\";\n\nexport function refreshCollectionsRealtime(\n options: PocketBaseIntegrationOptions,\n {\n logger,\n refreshContent,\n toolbar\n }: Parameters<BaseIntegrationHooks[\"astro:server:setup\"]>[0]\n): EventSource | undefined {\n // Check if collections should be watched\n const collectionsMap = mapCollectionsToWatch(options.collectionsToWatch);\n if (!collectionsMap) {\n return undefined;\n }\n const remoteCollections = [...collectionsMap.keys()];\n\n // Check if content loader is used\n if (!refreshContent) {\n logger.warn(\n \"No content loader available, skipping subscription to PocketBase realtime API.\"\n );\n return undefined;\n }\n\n // Check if EventSource is available\n // oxlint-disable-next-line no-unnecessary-condition\n if (!EventSource) {\n logger.warn(\n \"EventSource is not available, skipping subscription to PocketBase realtime API.\\n\" +\n \"Please install the 'eventsource' package.\"\n );\n return undefined;\n }\n\n let refreshEnabled = true;\n // Enable or disable real-time updates via the toolbar\n toolbar.on(\"astro-integration-pocketbase:real-time\", (enabled: boolean) => {\n refreshEnabled = enabled;\n });\n\n const eventSourceUrl = new URL(\"api/realtime\", options.url).href;\n const eventSource = new EventSource(eventSourceUrl);\n let wasConnectedOnce = false;\n let isConnected = false;\n\n // Log potential errors\n // oxlint-disable-next-line prefer-await-to-callbacks\n eventSource.addEventListener(\"error\", (error) => {\n isConnected = false;\n\n // Wait for 5 seconds in case of a connection error\n setTimeout(() => {\n if (isConnected) {\n // Connection was automatically re-established, no need to log the error\n return;\n }\n\n logger.error(\n `Error while connecting to PocketBase realtime API: ${error.type}`\n );\n }, 5000);\n });\n\n // Add event listeners for all collections\n for (const collection of remoteCollections) {\n eventSource.addEventListener(\n `${collection}/*`,\n async (event: MessageEvent<string>) => {\n // Do not refresh if the refresh is disabled\n if (!refreshEnabled) {\n return;\n }\n\n // Refresh the content\n logger.info(`Received update for ${collection}. Refreshing content...`);\n await refreshContent({\n loaders: [\"pocketbase-loader\"],\n context: {\n source: \"astro-integration-pocketbase\",\n collection: collectionsMap.get(collection),\n // oxlint-disable-next-line @typescript-eslint/no-unsafe-assignment\n data: JSON.parse(event.data)\n }\n });\n }\n );\n }\n\n // Add event listener for the connection event\n eventSource.addEventListener(\n \"PB_CONNECT\",\n async (event: MessageEvent<void>) => {\n isConnected = await handleConnectEvent(\n event,\n remoteCollections,\n wasConnectedOnce,\n options,\n logger\n );\n if (isConnected) {\n wasConnectedOnce = true;\n }\n }\n );\n\n return eventSource;\n}\n\nasync function handleConnectEvent(\n event: MessageEvent<void>,\n remoteCollections: Array<string>,\n wasConnectedOnce: boolean,\n options: PocketBaseIntegrationOptions,\n logger: AstroIntegrationLogger\n): Promise<boolean> {\n // Extract the clientId\n const clientId = event.lastEventId;\n\n // Get the superuser token if credentials are available\n let superuserToken: string | undefined;\n if (options.superuserCredentials) {\n if (\"impersonateToken\" in options.superuserCredentials) {\n superuserToken = options.superuserCredentials.impersonateToken;\n } else {\n superuserToken = await getSuperuserToken(\n options.url,\n options.superuserCredentials,\n logger\n );\n }\n }\n\n // Subscribe to the PocketBase realtime API\n const subscriptionUrl = new URL(\"api/realtime\", options.url).href;\n const result = await fetch(subscriptionUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: superuserToken || \"\"\n },\n body: JSON.stringify({\n clientId,\n subscriptions: remoteCollections.map((collection) => `${collection}/*`)\n })\n });\n\n // Log the connection status\n if (!result.ok) {\n logger.error(\n `Error while subscribing to PocketBase realtime API: ${result.status}`\n );\n return false;\n }\n\n if (!wasConnectedOnce) {\n logger.info(\n `Subscribed to PocketBase realtime API. Waiting for updates on ${remoteCollections.join(\n \", \"\n )}.`\n );\n }\n\n return true;\n}\n","import { fileURLToPath } from \"node:url\";\nimport type { AstroIntegration } from \"astro\";\nimport type { EventSource } from \"eventsource\";\nimport { handleRefreshCollections, refreshCollectionsRealtime } from \"./core\";\nimport type { ToolbarOptions } from \"./toolbar/types/options\";\nimport type { PocketBaseIntegrationOptions } from \"./types/pocketbase-integration-options.type\";\n\nexport function pocketbaseIntegration(\n options: PocketBaseIntegrationOptions\n): AstroIntegration {\n let eventSource: EventSource | undefined = undefined;\n let initialSetupDone = false;\n\n return {\n name: \"pocketbase-integration\",\n hooks: {\n \"astro:config:setup\": ({\n addDevToolbarApp,\n addMiddleware,\n command\n }): void => {\n // This integration is only available in dev mode\n if (command !== \"dev\") {\n return;\n }\n\n // Setup Toolbar\n addDevToolbarApp({\n name: \"PocketBase\",\n id: `pocketbase-entry`,\n icon: `<svg role=\"img\" viewBox=\"0 0 24 24\" xmlns=\"http://www.w3.org/2000/svg\"><title>PocketBase</title><path fill=\"currentColor\" d=\"M5.684 12a.632.632 0 0 1-.631-.632V4.421c0-.349.282-.632.631-.632h2.37c.46 0 .889.047 1.287.139.407.084.758.23 1.053.44.303.202.541.475.715.82.173.335.26.75.26 1.246 0 .479-.092.894-.273 1.247a2.373 2.373 0 0 1-.715.869 3.11 3.11 0 0 1-1.053.503c-.398.11-.823.164-1.273.164h-.46a.632.632 0 0 0-.632.632v1.52a.632.632 0 0 1-.632.631Zm1.279-4.888c0 .349.283.632.632.632h.343c1.04 0 1.56-.437 1.56-1.31 0-.428-.135-.73-.404-.907-.26-.176-.645-.264-1.156-.264h-.343a.632.632 0 0 0-.632.631Zm6.3 13.098a.632.632 0 0 1-.631-.631v-6.947a.63.63 0 0 1 .631-.632h2.203c.44 0 .845.034 1.216.1.38.06.708.169.984.328.276.16.492.37.647.63.164.26.246.587.246.982 0 .185-.03.37-.09.554a1.537 1.537 0 0 1-.26.516 1.857 1.857 0 0 1-1.076.7.031.031 0 0 0-.023.03c0 .015.01.028.025.03.591.111 1.04.32 1.346.626.311.31.466.743.466 1.297 0 .42-.082.78-.246 1.083a2.153 2.153 0 0 1-.685.755 3.4 3.4 0 0 1-1.036.441 5.477 5.477 0 0 1-1.268.139zm1.271-5.542c0 .349.283.631.632.631h.21c.465 0 .802-.088 1.009-.264.207-.176.31-.424.31-.743 0-.302-.107-.516-.323-.642-.207-.135-.535-.202-.984-.202h-.222a.632.632 0 0 0-.632.632Zm0 3.463c0 .349.283.631.632.631h.39c1.019 0 1.528-.369 1.528-1.108 0-.36-.125-.621-.376-.78-.241-.16-.625-.24-1.152-.24h-.39a.632.632 0 0 0-.632.632zM1.389 0C.629 0 0 .629 0 1.389V15.03a1.4 1.4 0 0 0 1.389 1.39H8.21a.632.632 0 0 0 .63-.632.632.632 0 0 0-.63-.63H1.389c-.078 0-.125-.05-.125-.128V1.39c0-.078.047-.125.125-.125H15.03c.078 0 .127.047.127.125v6.82a.632.632 0 0 0 .631.63.632.632 0 0 0 .633-.63V1.389A1.4 1.4 0 0 0 15.032 0ZM15.79 7.578a.632.632 0 0 0-.632.633.632.632 0 0 0 .631.63h6.822c.078 0 .125.05.125.128V22.61c0 .078-.047.125-.125.125H8.97c-.077 0-.127-.047-.127-.125v-6.82a.632.632 0 0 0-.631-.63.632.632 0 0 0-.633.63v6.822A1.4 1.4 0 0 0 8.968 24h13.643c.76 0 1.389-.629 1.389-1.389V8.97a1.4 1.4 0 0 0-1.389-1.39Z\"/></svg>`,\n entrypoint: fileURLToPath(new URL(\"./toolbar\", import.meta.url))\n });\n\n // Setup middleware\n addMiddleware({\n order: \"post\",\n entrypoint: fileURLToPath(new URL(\"./middleware\", import.meta.url))\n });\n },\n \"astro:server:setup\": (setupOptions): void => {\n if (!initialSetupDone) {\n // Listen for the refresh event of the toolbar\n handleRefreshCollections(setupOptions);\n }\n\n // Subscribe to PocketBase realtime API\n if (eventSource) {\n eventSource.close();\n eventSource = undefined;\n }\n eventSource = refreshCollectionsRealtime(options, setupOptions);\n\n // Send settings to the toolbar on initialization\n setupOptions.toolbar.onAppInitialized(\"pocketbase-entry\", () => {\n setupOptions.toolbar.send(\"astro-integration-pocketbase:settings\", {\n hasContentLoader: !!setupOptions.refreshContent,\n realtime: !!eventSource,\n baseUrl: options.url\n } satisfies ToolbarOptions);\n });\n\n initialSetupDone = true;\n },\n \"astro:server:done\": ({ logger }): void => {\n // Close the EventSource connection when the server is done\n if (eventSource) {\n logger.info(\"Closing EventSource connection\");\n eventSource.close();\n eventSource = undefined;\n }\n }\n }\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.mts","names":[],"sources":["../src/middleware/index.ts"],"mappings":";cAIa,SAAA,kBAAS,iBAsBpB"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from "astro/zod";
|
|
2
|
+
import { defineMiddleware } from "astro/middleware";
|
|
3
|
+
//#region src/middleware/is-pocketbase-entry.ts
|
|
4
|
+
/**
|
|
5
|
+
* Schema for a PocketBase entry created with [astro-loader-pocketbase](https://github.com/pawcoding/astro-loader-pocketbase)
|
|
6
|
+
*/
|
|
7
|
+
const pocketbaseEntrySchema = z.object({
|
|
8
|
+
id: z.string(),
|
|
9
|
+
data: z.object({
|
|
10
|
+
id: z.string(),
|
|
11
|
+
collectionId: z.string(),
|
|
12
|
+
collectionName: z.string()
|
|
13
|
+
}),
|
|
14
|
+
digest: z.string().length(16),
|
|
15
|
+
collection: z.string()
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Checks if the given data is a PocketBase entry.
|
|
19
|
+
*/
|
|
20
|
+
function isPocketbaseEntry(data) {
|
|
21
|
+
try {
|
|
22
|
+
pocketbaseEntrySchema.parse(data);
|
|
23
|
+
return true;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/middleware/index.ts
|
|
30
|
+
const onRequest = defineMiddleware(async (context, next) => {
|
|
31
|
+
const entities = findEntitiesRecursive(Object.values(context.props)).map((entity) => entity.data);
|
|
32
|
+
const response = await next();
|
|
33
|
+
if (!response.headers.get("content-type")?.includes("text/html")) return response;
|
|
34
|
+
const body = await response.text();
|
|
35
|
+
const entitiesJson = JSON.stringify(entities);
|
|
36
|
+
const newBody = body.replace("</head>", `<script>window.__astro_entities__ = ${entitiesJson}<\/script></head>`);
|
|
37
|
+
return new Response(newBody, response);
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* Find PocketBase entities in the given data.
|
|
41
|
+
*/
|
|
42
|
+
function findEntitiesRecursive(data) {
|
|
43
|
+
if (Array.isArray(data)) return data.flatMap((item) => findEntitiesRecursive(item));
|
|
44
|
+
if (typeof data === "object" && data !== null) {
|
|
45
|
+
if (isPocketbaseEntry(data)) return [data];
|
|
46
|
+
return findEntitiesRecursive(Object.values(data));
|
|
47
|
+
}
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { onRequest };
|
|
52
|
+
|
|
53
|
+
//# sourceMappingURL=middleware.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.mjs","names":[],"sources":["../src/middleware/is-pocketbase-entry.ts","../src/middleware/index.ts"],"sourcesContent":["import { z } from \"astro/zod\";\n\n/**\n * Schema for a PocketBase entry created with [astro-loader-pocketbase](https://github.com/pawcoding/astro-loader-pocketbase)\n */\nconst pocketbaseEntrySchema = z.object({\n id: z.string(),\n data: z.object({\n id: z.string(),\n collectionId: z.string(),\n collectionName: z.string()\n }),\n digest: z.string().length(16),\n collection: z.string()\n});\n\n/**\n * Type for a PocketBase entry created with [astro-loader-pocketbase](https://github.com/pawcoding/astro-loader-pocketbase)\n */\nexport type PocketBaseEntry = z.infer<typeof pocketbaseEntrySchema>;\n\n/**\n * Checks if the given data is a PocketBase entry.\n */\nexport function isPocketbaseEntry(data: unknown): data is PocketBaseEntry {\n try {\n // Try to parse the data with the PocketBase entry schema\n pocketbaseEntrySchema.parse(data);\n return true;\n } catch {\n return false;\n }\n}\n","import { defineMiddleware } from \"astro/middleware\";\nimport type { PocketBaseEntry } from \"./is-pocketbase-entry\";\nimport { isPocketbaseEntry } from \"./is-pocketbase-entry\";\n\nexport const onRequest = defineMiddleware(async (context, next) => {\n // Look for entities given as props to the page\n const props = Object.values(context.props);\n const entities = findEntitiesRecursive(props).map((entity) => entity.data);\n\n const response = await next();\n const contentType = response.headers.get(\"content-type\");\n if (!contentType?.includes(\"text/html\")) {\n // Pass through non-HTML responses unchanged\n return response;\n }\n\n const body = await response.text();\n\n // Append the entities to the <head>\n const entitiesJson = JSON.stringify(entities);\n const newBody = body.replace(\n \"</head>\",\n `<script>window.__astro_entities__ = ${entitiesJson}</script></head>`\n );\n\n return new Response(newBody, response);\n});\n\n/**\n * Find PocketBase entities in the given data.\n */\nfunction findEntitiesRecursive(data: unknown): Array<PocketBaseEntry> {\n // Check if the data is an array and search for entities in each element\n if (Array.isArray(data)) {\n return data.flatMap((item) => findEntitiesRecursive(item));\n }\n\n if (typeof data === \"object\" && data !== null) {\n // Check if the data is an object and a PocketBase entry\n if (isPocketbaseEntry(data)) {\n return [data];\n }\n\n // Search for entities in all values\n return findEntitiesRecursive(Object.values(data));\n }\n\n // No entities found\n return [];\n}\n"],"mappings":";;;;;;AAKA,MAAM,wBAAwB,EAAE,OAAO;CACrC,IAAI,EAAE,OAAO;CACb,MAAM,EAAE,OAAO;EACb,IAAI,EAAE,OAAO;EACb,cAAc,EAAE,OAAO;EACvB,gBAAgB,EAAE,OAAO;CAC3B,CAAC;CACD,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE;CAC5B,YAAY,EAAE,OAAO;AACvB,CAAC;;;;AAUD,SAAgB,kBAAkB,MAAwC;CACxE,IAAI;EAEF,sBAAsB,MAAM,IAAI;EAChC,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;AC5BA,MAAa,YAAY,iBAAiB,OAAO,SAAS,SAAS;CAGjE,MAAM,WAAW,sBADH,OAAO,OAAO,QAAQ,KACO,CAAC,EAAE,KAAK,WAAW,OAAO,IAAI;CAEzE,MAAM,WAAW,MAAM,KAAK;CAE5B,IAAI,CADgB,SAAS,QAAQ,IAAI,cAC1B,GAAG,SAAS,WAAW,GAEpC,OAAO;CAGT,MAAM,OAAO,MAAM,SAAS,KAAK;CAGjC,MAAM,eAAe,KAAK,UAAU,QAAQ;CAC5C,MAAM,UAAU,KAAK,QACnB,WACA,uCAAuC,aAAa,kBACtD;CAEA,OAAO,IAAI,SAAS,SAAS,QAAQ;AACvC,CAAC;;;;AAKD,SAAS,sBAAsB,MAAuC;CAEpE,IAAI,MAAM,QAAQ,IAAI,GACpB,OAAO,KAAK,SAAS,SAAS,sBAAsB,IAAI,CAAC;CAG3D,IAAI,OAAO,SAAS,YAAY,SAAS,MAAM;EAE7C,IAAI,kBAAkB,IAAI,GACxB,OAAO,CAAC,IAAI;EAId,OAAO,sBAAsB,OAAO,OAAO,IAAI,CAAC;CAClD;CAGA,OAAO,CAAC;AACV"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolbar.d.mts","names":[],"sources":["../src/toolbar/index.ts"],"mappings":""}
|