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