fix: for more stable broadcast channels on CF workers (#2007)
This commit is contained in:
committed by
GitHub
parent
983b3025a5
commit
5f925566c4
@@ -20,43 +20,47 @@ const PREVIEW_CHANNEL = 'preview-updates';
|
||||
export class PreviewsStore {
|
||||
#availablePreviews = new Map<number, PreviewInfo>();
|
||||
#webcontainer: Promise<WebContainer>;
|
||||
#broadcastChannel: BroadcastChannel;
|
||||
#broadcastChannel?: BroadcastChannel;
|
||||
#lastUpdate = new Map<string, number>();
|
||||
#watchedFiles = new Set<string>();
|
||||
#refreshTimeouts = new Map<string, NodeJS.Timeout>();
|
||||
#REFRESH_DELAY = 300;
|
||||
#storageChannel: BroadcastChannel;
|
||||
#storageChannel?: BroadcastChannel;
|
||||
|
||||
previews = atom<PreviewInfo[]>([]);
|
||||
|
||||
constructor(webcontainerPromise: Promise<WebContainer>) {
|
||||
this.#webcontainer = webcontainerPromise;
|
||||
this.#broadcastChannel = new BroadcastChannel(PREVIEW_CHANNEL);
|
||||
this.#storageChannel = new BroadcastChannel('storage-sync-channel');
|
||||
this.#broadcastChannel = this.#maybeCreateChannel(PREVIEW_CHANNEL);
|
||||
this.#storageChannel = this.#maybeCreateChannel('storage-sync-channel');
|
||||
|
||||
// Listen for preview updates from other tabs
|
||||
this.#broadcastChannel.onmessage = (event) => {
|
||||
const { type, previewId } = event.data;
|
||||
if (this.#broadcastChannel) {
|
||||
// Listen for preview updates from other tabs
|
||||
this.#broadcastChannel.onmessage = (event) => {
|
||||
const { type, previewId } = event.data;
|
||||
|
||||
if (type === 'file-change') {
|
||||
const timestamp = event.data.timestamp;
|
||||
const lastUpdate = this.#lastUpdate.get(previewId) || 0;
|
||||
if (type === 'file-change') {
|
||||
const timestamp = event.data.timestamp;
|
||||
const lastUpdate = this.#lastUpdate.get(previewId) || 0;
|
||||
|
||||
if (timestamp > lastUpdate) {
|
||||
this.#lastUpdate.set(previewId, timestamp);
|
||||
this.refreshPreview(previewId);
|
||||
if (timestamp > lastUpdate) {
|
||||
this.#lastUpdate.set(previewId, timestamp);
|
||||
this.refreshPreview(previewId);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Listen for storage sync messages
|
||||
this.#storageChannel.onmessage = (event) => {
|
||||
const { storage, source } = event.data;
|
||||
if (this.#storageChannel) {
|
||||
// Listen for storage sync messages
|
||||
this.#storageChannel.onmessage = (event) => {
|
||||
const { storage, source } = event.data;
|
||||
|
||||
if (storage && source !== this._getTabId()) {
|
||||
this._syncStorage(storage);
|
||||
}
|
||||
};
|
||||
if (storage && source !== this._getTabId()) {
|
||||
this._syncStorage(storage);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Override localStorage setItem to catch all changes
|
||||
if (typeof window !== 'undefined') {
|
||||
@@ -71,6 +75,29 @@ export class PreviewsStore {
|
||||
this.#init();
|
||||
}
|
||||
|
||||
#maybeCreateChannel(name: string): BroadcastChannel | undefined {
|
||||
if (typeof globalThis === 'undefined') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const globalBroadcastChannel = (
|
||||
globalThis as typeof globalThis & {
|
||||
BroadcastChannel?: typeof BroadcastChannel;
|
||||
}
|
||||
).BroadcastChannel;
|
||||
|
||||
if (typeof globalBroadcastChannel !== 'function') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
return new globalBroadcastChannel(name);
|
||||
} catch (error) {
|
||||
console.warn('[Preview] BroadcastChannel unavailable:', error);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a unique ID for this tab
|
||||
private _getTabId(): string {
|
||||
if (typeof window !== 'undefined') {
|
||||
@@ -130,7 +157,7 @@ export class PreviewsStore {
|
||||
}
|
||||
}
|
||||
|
||||
this.#storageChannel.postMessage({
|
||||
this.#storageChannel?.postMessage({
|
||||
type: 'storage-sync',
|
||||
storage,
|
||||
source: this._getTabId(),
|
||||
@@ -192,7 +219,7 @@ export class PreviewsStore {
|
||||
const timestamp = Date.now();
|
||||
this.#lastUpdate.set(previewId, timestamp);
|
||||
|
||||
this.#broadcastChannel.postMessage({
|
||||
this.#broadcastChannel?.postMessage({
|
||||
type: 'state-change',
|
||||
previewId,
|
||||
timestamp,
|
||||
@@ -204,7 +231,7 @@ export class PreviewsStore {
|
||||
const timestamp = Date.now();
|
||||
this.#lastUpdate.set(previewId, timestamp);
|
||||
|
||||
this.#broadcastChannel.postMessage({
|
||||
this.#broadcastChannel?.postMessage({
|
||||
type: 'file-change',
|
||||
previewId,
|
||||
timestamp,
|
||||
@@ -219,7 +246,7 @@ export class PreviewsStore {
|
||||
const timestamp = Date.now();
|
||||
this.#lastUpdate.set(previewId, timestamp);
|
||||
|
||||
this.#broadcastChannel.postMessage({
|
||||
this.#broadcastChannel?.postMessage({
|
||||
type: 'file-change',
|
||||
previewId,
|
||||
timestamp,
|
||||
|
||||
@@ -46,17 +46,22 @@ export default function WebContainerPreview() {
|
||||
}, [previewId, previewUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
// Initialize broadcast channel
|
||||
broadcastChannelRef.current = new BroadcastChannel(PREVIEW_CHANNEL);
|
||||
const supportsBroadcastChannel = typeof window !== 'undefined' && typeof window.BroadcastChannel === 'function';
|
||||
|
||||
// Listen for preview updates
|
||||
broadcastChannelRef.current.onmessage = (event) => {
|
||||
if (event.data.previewId === previewId) {
|
||||
if (event.data.type === 'refresh-preview' || event.data.type === 'file-change') {
|
||||
handleRefresh();
|
||||
if (supportsBroadcastChannel) {
|
||||
broadcastChannelRef.current = new window.BroadcastChannel(PREVIEW_CHANNEL);
|
||||
|
||||
// Listen for preview updates
|
||||
broadcastChannelRef.current.onmessage = (event) => {
|
||||
if (event.data.previewId === previewId) {
|
||||
if (event.data.type === 'refresh-preview' || event.data.type === 'file-change') {
|
||||
handleRefresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
} else {
|
||||
broadcastChannelRef.current = undefined;
|
||||
}
|
||||
|
||||
// Construct the WebContainer preview URL
|
||||
const url = `https://${previewId}.local-credentialless.webcontainer-api.io`;
|
||||
|
||||
Reference in New Issue
Block a user