astro-integration-pocketbase
Version:
An Astro integration to support developers working with astro-loader-pocketbase.
141 lines (123 loc) • 3.98 kB
text/typescript
import type { BaseIntegrationHooks } from "astro";
import { EventSource } from "eventsource";
import type { PocketBaseIntegrationOptions } from "../types/pocketbase-integration-options.type";
import { getSuperuserToken } from "../utils/get-superuser-token";
import { mapCollectionsToWatch } from "../utils/map-collections-to-watch";
export function refreshCollectionsRealtime(
{
url,
superuserCredentials,
collectionsToWatch
}: PocketBaseIntegrationOptions,
{
logger,
refreshContent,
toolbar
}: Parameters<BaseIntegrationHooks["astro:server:setup"]>[0]
): EventSource | undefined {
// Check if collections should be watched
const collectionsMap = mapCollectionsToWatch(collectionsToWatch);
if (!collectionsMap) {
return undefined;
}
const remoteCollections = [...collectionsMap.keys()];
// Check if content loader is used
if (!refreshContent) {
logger.warn(
"No content loader available, skipping subscription to PocketBase realtime API."
);
return undefined;
}
// Check if EventSource is available
if (!EventSource) {
logger.warn(
"EventSource is not available, skipping subscription to PocketBase realtime API.\n" +
"Please install the 'eventsource' package."
);
return undefined;
}
let refreshEnabled = true;
// Enable or disable real-time updates via the toolbar
toolbar.on("astro-integration-pocketbase:real-time", (enabled: boolean) => {
refreshEnabled = enabled;
});
const eventSource = new EventSource(`${url}/api/realtime`);
let wasConnectedOnce = false;
let isConnected = false;
// Log potential errors
eventSource.onerror = (error): void => {
isConnected = false;
// Wait for 5 seconds in case of a connection error
setTimeout(() => {
if (isConnected) {
// Connection was automatically re-established, no need to log the error
return;
}
logger.error(
`Error while connecting to PocketBase realtime API: ${error.type}`
);
}, 5000);
};
// Add event listeners for all collections
for (const collection of remoteCollections) {
eventSource.addEventListener(`${collection}/*`, async (event) => {
// Do not refresh if the refresh is disabled
if (!refreshEnabled) {
return;
}
// Refresh the content
logger.info(`Received update for ${collection}. Refreshing content...`);
await refreshContent({
loaders: ["pocketbase-loader"],
context: {
source: "astro-integration-pocketbase",
collection: collectionsMap.get(collection),
data: JSON.parse(event.data)
}
});
});
}
// Add event listener for the connection event
eventSource.addEventListener("PB_CONNECT", async (event) => {
// Extract the clientId
const clientId = event.lastEventId;
// Get the superuser token if credentials are available
let superuserToken: string | undefined;
if (superuserCredentials) {
superuserToken = await getSuperuserToken(
url,
superuserCredentials,
logger
);
}
// Subscribe to the PocketBase realtime API
const result = await fetch(`${url}/api/realtime`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: superuserToken || ""
},
body: JSON.stringify({
clientId: clientId,
subscriptions: remoteCollections.map((collection) => `${collection}/*`)
})
});
// Log the connection status
if (!result.ok) {
logger.error(
`Error while subscribing to PocketBase realtime API: ${result.status}`
);
return;
}
if (!wasConnectedOnce) {
wasConnectedOnce = true;
logger.info(
`Subscribed to PocketBase realtime API. Waiting for updates on ${remoteCollections.join(
", "
)}.`
);
}
isConnected = true;
});
return eventSource;
}