From d79083040d0cbd21fd694ff60636abc970624bac Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Thu, 6 Apr 2023 17:22:56 +1000 Subject: [PATCH] Add local storage-based implementation #1419 This can be used like: ``` import { LocalStorageSendBuffer } from "@microsoft/applicationinsights-channel-js"; const appInsights = new ApplicationInsights(...); appInsights.getSender().setBuffer(LocalStorageSendBuffer); ``` --- .../src/SendBuffer.ts | 75 ++++++++++++------- .../src/Sender.ts | 12 +++ .../src/applicationinsights-channel-js.ts | 3 +- 3 files changed, 63 insertions(+), 27 deletions(-) diff --git a/channels/applicationinsights-channel-js/src/SendBuffer.ts b/channels/applicationinsights-channel-js/src/SendBuffer.ts index 24caa324e..e76403b3d 100644 --- a/channels/applicationinsights-channel-js/src/SendBuffer.ts +++ b/channels/applicationinsights-channel-js/src/SendBuffer.ts @@ -1,5 +1,5 @@ import dynamicProto from "@microsoft/dynamicproto-js"; -import { utlGetSessionStorage, utlSetSessionStorage } from "@microsoft/applicationinsights-common"; +import { utlGetSessionStorage, utlSetSessionStorage, utlGetLocalStorage, utlSetLocalStorage } from "@microsoft/applicationinsights-common"; import { IDiagnosticLogger, _eInternalMessageId, _throwInternal, arrForEach, arrIndexOf, dumpObj, eLoggingSeverity, getExceptionName, getJSON, isArray, isFunction, isString @@ -49,7 +49,7 @@ export interface ISendBuffer { clearSent: (payload: string[]) => void; } -abstract class BaseSendBuffer { +export abstract class BaseSendBuffer { protected _get: () => string[]; protected _set: (buffer: string[]) => string[]; @@ -185,36 +185,39 @@ export class ArraySendBuffer extends BaseSendBuffer implements ISendBuffer { } } +type StorageGetter = typeof utlGetSessionStorage; +type StorageSetter = typeof utlSetSessionStorage; + /* - * Session storage buffer holds a copy of all unsent items in the browser session storage. + * Base storage buffer for simple sync-based string storage */ -export class SessionStorageSendBuffer extends BaseSendBuffer implements ISendBuffer { +export class StringStorageSendBuffer extends BaseSendBuffer implements ISendBuffer { static BUFFER_KEY = "AI_buffer"; static SENT_BUFFER_KEY = "AI_sentBuffer"; // Maximum number of payloads stored in the buffer. If the buffer is full, new elements will be dropped. static MAX_BUFFER_SIZE = 2000; - constructor(logger: IDiagnosticLogger, config: ISenderConfig) { + constructor(logger: IDiagnosticLogger, config: ISenderConfig, setStorage: StorageSetter, getStorage: StorageGetter) { super(logger, config); let _bufferFullMessageSent = false; - dynamicProto(SessionStorageSendBuffer, this, (_self, _base) => { - const bufferItems = _getBuffer(SessionStorageSendBuffer.BUFFER_KEY); - const notDeliveredItems = _getBuffer(SessionStorageSendBuffer.SENT_BUFFER_KEY); + dynamicProto(StringStorageSendBuffer, this, (_self, _base) => { + const bufferItems = _getBuffer(StringStorageSendBuffer.BUFFER_KEY); + const notDeliveredItems = _getBuffer(StringStorageSendBuffer.SENT_BUFFER_KEY); let buffer = _self._set(bufferItems.concat(notDeliveredItems)); // If the buffer has too many items, drop items from the end. - if (buffer.length > SessionStorageSendBuffer.MAX_BUFFER_SIZE) { - buffer.length = SessionStorageSendBuffer.MAX_BUFFER_SIZE; + if (buffer.length > StringStorageSendBuffer.MAX_BUFFER_SIZE) { + buffer.length = StringStorageSendBuffer.MAX_BUFFER_SIZE; } - _setBuffer(SessionStorageSendBuffer.SENT_BUFFER_KEY, []); - _setBuffer(SessionStorageSendBuffer.BUFFER_KEY, buffer); + _setBuffer(StringStorageSendBuffer.SENT_BUFFER_KEY, []); + _setBuffer(StringStorageSendBuffer.BUFFER_KEY, buffer); _self.enqueue = (payload: string) => { - if (_self.count() >= SessionStorageSendBuffer.MAX_BUFFER_SIZE) { + if (_self.count() >= StringStorageSendBuffer.MAX_BUFFER_SIZE) { // sent internal log only once per page view if (!_bufferFullMessageSent) { _throwInternal(logger, @@ -229,26 +232,26 @@ export class SessionStorageSendBuffer extends BaseSendBuffer implements ISendBuf } _base.enqueue(payload); - _setBuffer(SessionStorageSendBuffer.BUFFER_KEY, _self._get()); + _setBuffer(StringStorageSendBuffer.BUFFER_KEY, _self._get()); }; _self.clear = () => { _base.clear(); - _setBuffer(SessionStorageSendBuffer.BUFFER_KEY, _self._get()); - _setBuffer(SessionStorageSendBuffer.SENT_BUFFER_KEY, []); + _setBuffer(StringStorageSendBuffer.BUFFER_KEY, _self._get()); + _setBuffer(StringStorageSendBuffer.SENT_BUFFER_KEY, []); _bufferFullMessageSent = false; }; _self.markAsSent = (payload: string[]) => { - _setBuffer(SessionStorageSendBuffer.BUFFER_KEY, + _setBuffer(StringStorageSendBuffer.BUFFER_KEY, _self._set(_removePayloadsFromBuffer(payload, _self._get()))); - let sentElements = _getBuffer(SessionStorageSendBuffer.SENT_BUFFER_KEY); + let sentElements = _getBuffer(StringStorageSendBuffer.SENT_BUFFER_KEY); if (sentElements instanceof Array && payload instanceof Array) { sentElements = sentElements.concat(payload); - if (sentElements.length > SessionStorageSendBuffer.MAX_BUFFER_SIZE) { + if (sentElements.length > StringStorageSendBuffer.MAX_BUFFER_SIZE) { // We send telemetry normally. If the SENT_BUFFER is too big we don't add new elements // until we receive a response from the backend and the buffer has free space again (see clearSent method) _throwInternal(logger, @@ -257,18 +260,18 @@ export class SessionStorageSendBuffer extends BaseSendBuffer implements ISendBuf "Sent buffer reached its maximum size: " + sentElements.length, true); - sentElements.length = SessionStorageSendBuffer.MAX_BUFFER_SIZE; + sentElements.length = StringStorageSendBuffer.MAX_BUFFER_SIZE; } - _setBuffer(SessionStorageSendBuffer.SENT_BUFFER_KEY, sentElements); + _setBuffer(StringStorageSendBuffer.SENT_BUFFER_KEY, sentElements); } }; _self.clearSent = (payload: string[]) => { - let sentElements = _getBuffer(SessionStorageSendBuffer.SENT_BUFFER_KEY); + let sentElements = _getBuffer(StringStorageSendBuffer.SENT_BUFFER_KEY); sentElements = _removePayloadsFromBuffer(payload, sentElements); - _setBuffer(SessionStorageSendBuffer.SENT_BUFFER_KEY, sentElements); + _setBuffer(StringStorageSendBuffer.SENT_BUFFER_KEY, sentElements); }; function _removePayloadsFromBuffer(payloads: string[], buffer: string[]): string[] { @@ -287,7 +290,7 @@ export class SessionStorageSendBuffer extends BaseSendBuffer implements ISendBuf let prefixedKey = key; try { prefixedKey = config.namePrefix && config.namePrefix() ? config.namePrefix() + "_" + prefixedKey : prefixedKey; - const bufferJson = utlGetSessionStorage(logger, prefixedKey); + const bufferJson = getStorage(logger, prefixedKey); if (bufferJson) { let buffer: string[] = getJSON().parse(bufferJson); if (isString(buffer)) { @@ -314,11 +317,11 @@ export class SessionStorageSendBuffer extends BaseSendBuffer implements ISendBuf try { prefixedKey = config.namePrefix && config.namePrefix() ? config.namePrefix() + "_" + prefixedKey : prefixedKey; const bufferJson = JSON.stringify(buffer); - utlSetSessionStorage(logger, prefixedKey, bufferJson); + setStorage(logger, prefixedKey, bufferJson); } catch (e) { // if there was an error, clear the buffer // telemetry is stored in the _buffer array so we won't loose any items - utlSetSessionStorage(logger, prefixedKey, JSON.stringify([])); + setStorage(logger, prefixedKey, JSON.stringify([])); _throwInternal(logger, eLoggingSeverity.WARNING, _eInternalMessageId.FailedToSetStorageBuffer, @@ -345,3 +348,23 @@ export class SessionStorageSendBuffer extends BaseSendBuffer implements ISendBuf // @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging } } + + + +/* + * Session storage buffer holds a copy of all unsent items in the browser session storage. + */ +export class SessionStorageSendBuffer extends StringStorageSendBuffer implements ISendBuffer { + constructor(logger: IDiagnosticLogger, config: ISenderConfig) { + super(logger, config, utlSetSessionStorage, utlGetSessionStorage); + } +} + +/* + * Local storage buffer holds a copy of all unsent items in the browser local storage. + */ +export class LocalStorageSendBuffer extends StringStorageSendBuffer implements ISendBuffer { + constructor(logger: IDiagnosticLogger, config: ISenderConfig) { + super(logger, config, utlSetLocalStorage, utlGetLocalStorage); + } +} diff --git a/channels/applicationinsights-channel-js/src/Sender.ts b/channels/applicationinsights-channel-js/src/Sender.ts index ef84783af..d89090dba 100644 --- a/channels/applicationinsights-channel-js/src/Sender.ts +++ b/channels/applicationinsights-channel-js/src/Sender.ts @@ -283,6 +283,10 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControlsAI { _syncUnloadSender = _fallbackSender; } }; + + _self.setBuffer = (buffer: new (logger: IDiagnosticLogger, config: ISenderConfig) => ISendBuffer) => { + _self._buffer = new buffer(_self.diagLog(), _self._senderConfig); + } _self.processTelemetry = (telemetryItem: ITelemetryItem, itemCtx?: IProcessTelemetryContext) => { itemCtx = _self._getTelCtx(itemCtx); @@ -1101,6 +1105,14 @@ export class Sender extends BaseTelemetryPlugin implements IChannelControlsAI { // @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging } + /** + * Buffer to store telemetry items in before sending and after sending failures. + * Must be called prior to triggering any message. + */ + public setBuffer(buffer: new (logger: IDiagnosticLogger, config: ISenderConfig) => ISendBuffer): void { + // @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging + } + public processTelemetry(telemetryItem: ITelemetryItem, itemCtx?: IProcessTelemetryContext) { // @DynamicProtoStub -- DO NOT add any code as this will be removed during packaging } diff --git a/channels/applicationinsights-channel-js/src/applicationinsights-channel-js.ts b/channels/applicationinsights-channel-js/src/applicationinsights-channel-js.ts index 01f58b6c9..2bcbcd8f5 100644 --- a/channels/applicationinsights-channel-js/src/applicationinsights-channel-js.ts +++ b/channels/applicationinsights-channel-js/src/applicationinsights-channel-js.ts @@ -1 +1,2 @@ -export { Sender } from "./Sender"; \ No newline at end of file +export { Sender } from "./Sender"; +export { SessionStorageSendBuffer, LocalStorageSendBuffer, StringStorageSendBuffer, BaseSendBuffer, ISendBuffer } from "./SendBuffer"; \ No newline at end of file