Skip to content

Commit

Permalink
Add local storage-based implementation #1419
Browse files Browse the repository at this point in the history
This can be used like:

```
import { LocalStorageSendBuffer } from "@microsoft/applicationinsights-channel-js";

const appInsights = new ApplicationInsights(...);
appInsights.getSender().setBuffer(LocalStorageSendBuffer);
```
  • Loading branch information
peitschie committed Apr 6, 2023
1 parent 4361915 commit d790830
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 27 deletions.
75 changes: 49 additions & 26 deletions channels/applicationinsights-channel-js/src/SendBuffer.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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[];
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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[] {
Expand All @@ -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)) {
Expand All @@ -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,
Expand All @@ -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);
}
}
12 changes: 12 additions & 0 deletions channels/applicationinsights-channel-js/src/Sender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { Sender } from "./Sender";
export { Sender } from "./Sender";
export { SessionStorageSendBuffer, LocalStorageSendBuffer, StringStorageSendBuffer, BaseSendBuffer, ISendBuffer } from "./SendBuffer";

0 comments on commit d790830

Please sign in to comment.