File size: 2,445 Bytes
fa9c65f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
interface EventTargetLike {
  addEventListener: (type: string, listener: EventListenerOrEventListenerObject) => void;
}

interface StorageLike {
  getItem: (key: string) => string | null;
  setItem: (key: string, value: string) => void;
  removeItem: (key: string) => void;
}

interface ChunkReloadGuardOptions {
  eventTarget?: EventTargetLike;
  storage?: StorageLike;
  eventName?: string;
  reload?: () => void;
}

const memorySessionStorage = new Map<string, string>();

function getSafeSessionStorage(): StorageLike {
  let browserStorage: StorageLike | undefined;
  try {
    browserStorage = window.sessionStorage;
  } catch {
    // Storage access can throw in sandboxed frames or when cookies are blocked.
  }

  return {
    getItem(key) {
      try {
        const stored = browserStorage?.getItem(key);
        if (stored !== null && stored !== undefined) return stored;
      } catch {
        // Fall through to the in-memory one-shot guard.
      }
      return memorySessionStorage.get(key) ?? null;
    },
    setItem(key, value) {
      try {
        browserStorage?.setItem(key, value);
        if (browserStorage) {
          memorySessionStorage.delete(key);
          return;
        }
      } catch {
        // Preserve one-shot behavior for read-only or otherwise blocked storage.
      }
      memorySessionStorage.set(key, value);
    },
    removeItem(key) {
      try {
        browserStorage?.removeItem(key);
      } catch {
        // The in-memory guard still needs to be cleared below.
      }
      memorySessionStorage.delete(key);
    },
  };
}

export function buildChunkReloadStorageKey(version: string): string {
  return `wm-chunk-reload:${version}`;
}

export function installChunkReloadGuard(
  version: string,
  options: ChunkReloadGuardOptions = {}
): string {
  const storageKey = buildChunkReloadStorageKey(version);
  const eventName = options.eventName ?? 'vite:preloadError';
  const eventTarget = options.eventTarget ?? window;
  const storage = options.storage ?? getSafeSessionStorage();
  const reload = options.reload ?? (() => window.location.reload());

  eventTarget.addEventListener(eventName, () => {
    if (storage.getItem(storageKey)) return;
    storage.setItem(storageKey, '1');
    reload();
  });

  return storageKey;
}

export function clearChunkReloadGuard(storageKey: string, storage?: StorageLike): void {
  (storage ?? getSafeSessionStorage()).removeItem(storageKey);
}