diff --git a/src/Rokt-Kit.ts b/src/Rokt-Kit.ts index 1ff847f..cb93564 100644 --- a/src/Rokt-Kit.ts +++ b/src/Rokt-Kit.ts @@ -884,7 +884,7 @@ class RoktKit implements KitInterface { const pageView = buildPageEvent(event); pageViews.push(pageView); - if (!writePageViews(pageViews)) { + if (writePageViews(pageViews) === 0) { const reason = isLocalStorageAvailable() ? 'quota' : 'ls_unavailable'; this.loggingService?.log({ message: `Rokt Kit: Failed to persist page view for ${pageUrl} [reason: ${reason}]`, diff --git a/src/pageViewStorage.ts b/src/pageViewStorage.ts index 3a041be..d8fffc4 100644 --- a/src/pageViewStorage.ts +++ b/src/pageViewStorage.ts @@ -53,8 +53,15 @@ export function loadPageViews(loggingService: LoggingService | null): PageEvent[ return Array.isArray(stored) ? (stored as PageEvent[]) : []; } -export function writePageViews(pageViews: PageEvent[]): boolean { - return writeNamespacedField(LS_NAMESPACE_KEY, LS_PAGE_VIEWS_FIELD, capPageViews(pageViews)); +export function writePageViews(pageViews: PageEvent[]): number { + const views = capPageViews(pageViews); + for (let i = 0; i < views.length; i++) { + const toWrite = views.slice(i); + if (writeNamespacedField(LS_NAMESPACE_KEY, LS_PAGE_VIEWS_FIELD, toWrite)) { + return toWrite.length; + } + } + return 0; } export function clearPageViews(): void { diff --git a/test/src/pageViewStorage.spec.ts b/test/src/pageViewStorage.spec.ts index 84af00c..0de3b30 100644 --- a/test/src/pageViewStorage.spec.ts +++ b/test/src/pageViewStorage.spec.ts @@ -112,19 +112,77 @@ describe('pageViewStorage', () => { }); describe('writePageViews', () => { - it('persists the page views and returns true', () => { - expect(writePageViews([pageView('home')])).toBe(true); + it('persists the page views and returns the stored count', () => { + expect(writePageViews([pageView('home')])).toBe(1); expect(loadPageViews(null)).toEqual([pageView('home')]); }); it('keeps only the 25 most-recent views when given more than 25', () => { const views = Array.from({ length: 40 }, (_, i) => pageView('page-' + i)); - expect(writePageViews(views)).toBe(true); + expect(writePageViews(views)).toBe(25); const stored = loadPageViews(null); expect(stored).toHaveLength(25); expect(stored[0].sourceMessageId).toBe('page-15'); expect(stored[24].sourceMessageId).toBe('page-39'); }); + + it('evicts oldest records and retries when the initial write fails due to quota', () => { + const views = Array.from({ length: 5 }, (_, i) => pageView('page-' + i)); + const original = Storage.prototype.setItem; + let calls = 0; + vi.spyOn(Storage.prototype, 'setItem').mockImplementation(function (this: Storage, key: string, value: string) { + if (++calls === 1) throw new DOMException('quota', 'QuotaExceededError'); + original.call(this, key, value); + }); + + expect(writePageViews(views)).toBe(4); + const stored = loadPageViews(null); + expect(stored).toHaveLength(4); + expect(stored[0].sourceMessageId).toBe('page-1'); + expect(stored[3].sourceMessageId).toBe('page-4'); + }); + + it('evicts oldest records across multiple failed writes until one succeeds', () => { + const views = Array.from({ length: 5 }, (_, i) => pageView('page-' + i)); + const original = Storage.prototype.setItem; + let calls = 0; + vi.spyOn(Storage.prototype, 'setItem').mockImplementation(function (this: Storage, key: string, value: string) { + if (++calls <= 3) throw new DOMException('quota', 'QuotaExceededError'); + original.call(this, key, value); + }); + + expect(writePageViews(views)).toBe(2); + const stored = loadPageViews(null); + expect(stored).toHaveLength(2); + expect(stored[0].sourceMessageId).toBe('page-3'); + expect(stored[1].sourceMessageId).toBe('page-4'); + }); + + it('evicts the oldest record from pre-existing storage when quota is tight on the next write', () => { + const existing = Array.from({ length: 5 }, (_, i) => pageView('existing-' + i)); + writePageViews(existing); // seed storage + + const updated = [...existing, pageView('new')]; + const original = Storage.prototype.setItem; + let calls = 0; + vi.spyOn(Storage.prototype, 'setItem').mockImplementation(function (this: Storage, key: string, value: string) { + if (++calls === 1) throw new DOMException('quota', 'QuotaExceededError'); + original.call(this, key, value); + }); + + expect(writePageViews(updated)).toBe(5); + const stored = loadPageViews(null); + expect(stored).toHaveLength(5); + expect(stored[0].sourceMessageId).toBe('existing-1'); + expect(stored[4].sourceMessageId).toBe('new'); + }); + + it('returns false when every write attempt fails', () => { + vi.spyOn(Storage.prototype, 'setItem').mockImplementation(() => { + throw new DOMException('quota', 'QuotaExceededError'); + }); + expect(writePageViews([pageView('home')])).toBe(0); + }); }); describe('buildPageEvents', () => {