@gojek/clickstream-web
Version:
A Modern, Fast, and Lightweight Event Ingestion library for Web
1 lines • 138 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","sources":["../src/constants/config.js","../src/constants/index.js","../src/error.js","../src/protos/raccoon.js","../src/logger.js","../src/blob.js","../src/transport.js","../src/processor.js","../src/scheduler.js","../src/event.js","../src/store.js","../src/id.js","../src/validator.js","../src/clickstream.js"],"sourcesContent":["/**\n * @typedef {object} EventConfig - Event configuration\n * @property {{instant: string[]}=} classification - event classification\n * @property {string=} group - product group name\n */\n\n/**\n * @typedef {object} BatchConfig - Batch configuration\n * @property {number=} maxTimeBetweenTwoBatches - Maximum wait time between two batches\n * @property {number=} maxBatchSize - Maximum size of a batch\n * @property {string=} dbName - name for indexedDB\n */\n\n/**\n * @typedef {object} NetworkConfig - Network configuration\n * @property {string | URL} url - base url\n * @property {Headers} headers - request headers\n * @property {number=} maxRetries - max retries\n * @property {number=} timeBetweenTwoRetries - time in seconds between two retries\n * @property {number=} timeToResumeRetries - time in seconds to resume retries\n */\n\n/**\n * @typedef {object} Config - Configuration\n * @property {EventConfig=} event - event configurations\n * @property {BatchConfig=} batch - batch configurations\n * @property {NetworkConfig} network - network configurations\n * @property {object=} crypto - crypto module instance\n * @property {Boolean=} debug - debug option\n */\n\n/** @type {Config} } */\nexport const defaultConfig = {\n event: {\n classification: {\n instant: [],\n },\n group: \"\",\n },\n batch: {\n // max interval time between two batches, in seconds\n maxTimeBetweenTwoBatches: 10,\n // max size of batch, in bytes\n maxBatchSize: 50000,\n // name for indexedDB, must be unique per domain\n dbName: \"clickstream_db\",\n },\n network: {\n url: \"\",\n headers: new Headers({}),\n // max number of retries before pausing\n maxRetries: 5,\n // gap between two retries (mSec)\n timeBetweenTwoRetries: 1000,\n // time after which retry will resume after hitting max retry count threshold (mSec)\n timeToResumeRetries: 20000,\n },\n crypto: null,\n}\n","export * from \"./config.js\"\n\nexport const EVENT_TYPE = {\n INSTANT: \"instant\",\n REALTIME: \"realTime\",\n}\n\nexport const CUSTOM_EVENT = {\n BATCH_CREATED: \"batchCreated\",\n BATCH_FAILED: \"batchFailed\",\n}\n\nexport const TICK_TIME = 1000\n","export const errorCodes = {\n CLICKSTREAM_ERROR: \"clickstreamError\",\n VALIDATION_ERROR: \"validationError\",\n DATABASE_ERROR: \"databaseError\",\n NETWORK_ERROR: \"networkError\",\n TRACKING_ERROR: \"trackingError\",\n CLEANUP_ERROR: \"cleanupError\",\n}\n\nexport const errorNames = {\n CLICKSTREAM_ERROR: \"Clickstream Error\",\n VALIDATION_ERROR: \"Validation Error\",\n DATABASE_ERROR: \"Database Error\",\n NETWORK_ERROR: \"Network Error\",\n TRACKING_ERROR: \"Tracking Error\",\n CLEANUP_ERROR: \"Cleanup Error\",\n}\n\nexport class ClickstreamError extends Error {\n constructor(message, options) {\n super(message, options)\n this.name = options?.name ?? errorNames.CLICKSTREAM_ERROR\n this.code = options?.code ?? errorCodes.CLICKSTREAM_ERROR\n }\n}\n\nexport class ValidationError extends ClickstreamError {\n constructor(message, options) {\n super(message, options)\n this.name = errorNames.VALIDATION_ERROR\n this.code = errorCodes.VALIDATION_ERROR\n }\n}\n\nexport class DatabaseError extends ClickstreamError {\n constructor(message, options) {\n super(message, options)\n this.name = errorNames.DATABASE_ERROR\n this.code = errorCodes.DATABASE_ERROR\n }\n}\n\nexport class NetworkError extends ClickstreamError {\n constructor(message, options) {\n super(message, options)\n this.name = errorNames.NETWORK_ERROR\n this.code = errorCodes.NETWORK_ERROR\n }\n}\n","// @ts-nocheck\n/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars*/\nimport * as $protobuf1 from \"protobufjs/minimal.js\"\n\nlet $protobuf\n\nif ($protobuf1.default) {\n $protobuf = $protobuf1.default\n}\n\n// Common aliases\nconst $Reader = $protobuf.Reader,\n $Writer = $protobuf.Writer,\n $util = $protobuf.util\n\n// Exported root namespace\nconst $root = $protobuf.roots[\"default\"] || ($protobuf.roots[\"default\"] = {})\n\n/**\n * @type {Class}\n */\nexport const EventService = ($root.EventService = (() => {\n /**\n * Constructs a new EventService service.\n * @exports EventService\n * @classdesc Represents an EventService\n * @extends $protobuf.rpc.Service\n * @constructor\n * @param {$protobuf.RPCImpl} rpcImpl RPC implementation\n * @param {boolean} [requestDelimited=false] Whether requests are length-delimited\n * @param {boolean} [responseDelimited=false] Whether responses are length-delimited\n */\n function EventService(rpcImpl, requestDelimited, responseDelimited) {\n $protobuf.rpc.Service.call(\n this,\n rpcImpl,\n requestDelimited,\n responseDelimited\n )\n }\n\n ;(EventService.prototype = Object.create(\n $protobuf.rpc.Service.prototype\n )).constructor = EventService\n\n /**\n * Creates new EventService service using the specified rpc implementation.\n * @function create\n * @memberof EventService\n * @static\n * @param {$protobuf.RPCImpl} rpcImpl RPC implementation\n * @param {boolean} [requestDelimited=false] Whether requests are length-delimited\n * @param {boolean} [responseDelimited=false] Whether responses are length-delimited\n * @returns {EventService} RPC service. Useful where requests and/or responses are streamed.\n */\n EventService.create = function create(\n rpcImpl,\n requestDelimited,\n responseDelimited\n ) {\n return new this(rpcImpl, requestDelimited, responseDelimited)\n }\n\n /**\n * Callback as used by {@link EventService#sendEvent}.\n * @memberof EventService\n * @typedef SendEventCallback\n * @type {function}\n * @param {Error|null} error Error, if any\n * @param {SendEventResponse} [response] SendEventResponse\n */\n\n /**\n * Calls SendEvent.\n * @function sendEvent\n * @memberof EventService\n * @instance\n * @param {ISendEventRequest} request SendEventRequest message or plain object\n * @param {EventService.SendEventCallback} callback Node-style callback called with the error, if any, and SendEventResponse\n * @returns {undefined}\n * @variation 1\n */\n Object.defineProperty(\n (EventService.prototype.sendEvent = function sendEvent(request, callback) {\n return this.rpcCall(\n sendEvent,\n $root.SendEventRequest,\n $root.SendEventResponse,\n request,\n callback\n )\n }),\n \"name\",\n { value: \"SendEvent\" }\n )\n\n /**\n * Calls SendEvent.\n * @function sendEvent\n * @memberof EventService\n * @instance\n * @param {ISendEventRequest} request SendEventRequest message or plain object\n * @returns {Promise<SendEventResponse>} Promise\n * @variation 2\n */\n\n return EventService\n})())\n\n/**\n * @type {Class}\n */\nexport const SendEventRequest = ($root.SendEventRequest = (() => {\n /**\n * Properties of a SendEventRequest.\n * @exports ISendEventRequest\n * @interface ISendEventRequest\n * @property {string|null} [reqGuid] SendEventRequest reqGuid\n * @property {ITimestamp|null} [sentTime] SendEventRequest sentTime\n * @property {Array.<IEvent>|null} [events] SendEventRequest events\n */\n\n /**\n * Constructs a new SendEventRequest.\n * @exports SendEventRequest\n * @classdesc Represents a SendEventRequest.\n * @implements ISendEventRequest\n * @constructor\n * @param {ISendEventRequest=} [properties] Properties to set\n */\n function SendEventRequest(properties) {\n this.events = []\n if (properties)\n for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i)\n if (properties[keys[i]] != null) this[keys[i]] = properties[keys[i]]\n }\n\n /**\n * SendEventRequest reqGuid.\n * @member {string} reqGuid\n * @memberof SendEventRequest\n * @instance\n */\n SendEventRequest.prototype.reqGuid = \"\"\n\n /**\n * SendEventRequest sentTime.\n * @member {ITimestamp|null|undefined} sentTime\n * @memberof SendEventRequest\n * @instance\n */\n SendEventRequest.prototype.sentTime = null\n\n /**\n * SendEventRequest events.\n * @member {Array.<IEvent>} events\n * @memberof SendEventRequest\n * @instance\n */\n SendEventRequest.prototype.events = $util.emptyArray\n\n /**\n * Creates a new SendEventRequest instance using the specified properties.\n * @function create\n * @memberof SendEventRequest\n * @static\n * @param {ISendEventRequest=} [properties] Properties to set\n * @returns {SendEventRequest} SendEventRequest instance\n */\n SendEventRequest.create = function create(properties) {\n return new SendEventRequest(properties)\n }\n\n /**\n * Encodes the specified SendEventRequest message. Does not implicitly {@link SendEventRequest.verify|verify} messages.\n * @function encode\n * @memberof SendEventRequest\n * @static\n * @param {ISendEventRequest} message SendEventRequest message or plain object to encode\n * @param {$protobuf.Writer} [writer] Writer to encode to\n * @returns {$protobuf.Writer} Writer\n */\n SendEventRequest.encode = function encode(message, writer) {\n if (!writer) writer = $Writer.create()\n if (\n message.reqGuid != null &&\n Object.hasOwnProperty.call(message, \"reqGuid\")\n )\n writer.uint32(/* id 1, wireType 2 =*/ 10).string(message.reqGuid)\n if (\n message.sentTime != null &&\n Object.hasOwnProperty.call(message, \"sentTime\")\n )\n $root.Timestamp.encode(\n message.sentTime,\n writer.uint32(/* id 2, wireType 2 =*/ 18).fork()\n ).ldelim()\n if (message.events != null && message.events.length)\n for (let i = 0; i < message.events.length; ++i)\n $root.Event.encode(\n message.events[i],\n writer.uint32(/* id 3, wireType 2 =*/ 26).fork()\n ).ldelim()\n return writer\n }\n\n /**\n * Decodes a SendEventRequest message from the specified reader or buffer.\n * @function decode\n * @memberof SendEventRequest\n * @static\n * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from\n * @param {number} [length] Message length if known beforehand\n * @returns {SendEventRequest} SendEventRequest\n * @throws {Error} If the payload is not a reader or valid buffer\n * @throws {$protobuf.util.ProtocolError} If required fields are missing\n */\n SendEventRequest.decode = function decode(reader, length) {\n if (!(reader instanceof $Reader)) reader = $Reader.create(reader)\n let end = length === undefined ? reader.len : reader.pos + length,\n message = new $root.SendEventRequest()\n while (reader.pos < end) {\n let tag = reader.uint32()\n switch (tag >>> 3) {\n case 1: {\n message.reqGuid = reader.string()\n break\n }\n case 2: {\n message.sentTime = $root.Timestamp.decode(reader, reader.uint32())\n break\n }\n case 3: {\n if (!(message.events && message.events.length)) message.events = []\n message.events.push($root.Event.decode(reader, reader.uint32()))\n break\n }\n default:\n reader.skipType(tag & 7)\n break\n }\n }\n return message\n }\n\n /**\n * Verifies a SendEventRequest message.\n * @function verify\n * @memberof SendEventRequest\n * @static\n * @param {Object.<string,*>} message Plain object to verify\n * @returns {string|null} `null` if valid, otherwise the reason why it is not\n */\n SendEventRequest.verify = function verify(message) {\n if (typeof message !== \"object\" || message === null)\n return \"object expected\"\n if (message.reqGuid != null && message.hasOwnProperty(\"reqGuid\"))\n if (!$util.isString(message.reqGuid)) return \"reqGuid: string expected\"\n if (message.sentTime != null && message.hasOwnProperty(\"sentTime\")) {\n let error = $root.Timestamp.verify(message.sentTime)\n if (error) return \"sentTime.\" + error\n }\n if (message.events != null && message.hasOwnProperty(\"events\")) {\n if (!Array.isArray(message.events)) return \"events: array expected\"\n for (let i = 0; i < message.events.length; ++i) {\n let error = $root.Event.verify(message.events[i])\n if (error) return \"events.\" + error\n }\n }\n return null\n }\n\n /**\n * Gets the default type url for SendEventRequest\n * @function getTypeUrl\n * @memberof SendEventRequest\n * @static\n * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default \"type.googleapis.com\")\n * @returns {string} The default type url\n */\n SendEventRequest.getTypeUrl = function getTypeUrl(typeUrlPrefix) {\n if (typeUrlPrefix === undefined) {\n typeUrlPrefix = \"type.googleapis.com\"\n }\n return typeUrlPrefix + \"/SendEventRequest\"\n }\n\n return SendEventRequest\n})())\n\n/**\n * @type {Class}\n */\nexport const Timestamp = ($root.Timestamp = (() => {\n /**\n * Properties of a Timestamp.\n * @exports ITimestamp\n * @interface ITimestamp\n * @property {number|Long|null} [seconds] Timestamp seconds\n * @property {number|null} [nanos] Timestamp nanos\n */\n\n /**\n * Constructs a new Timestamp.\n * @exports Timestamp\n * @classdesc Represents a Timestamp.\n * @implements ITimestamp\n * @constructor\n * @param {ITimestamp=} [properties] Properties to set\n */\n function Timestamp(properties) {\n if (properties)\n for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i)\n if (properties[keys[i]] != null) this[keys[i]] = properties[keys[i]]\n }\n\n /**\n * Timestamp seconds.\n * @member {number|Long} seconds\n * @memberof Timestamp\n * @instance\n */\n Timestamp.prototype.seconds = $util.Long\n ? $util.Long.fromBits(0, 0, false)\n : 0\n\n /**\n * Timestamp nanos.\n * @member {number} nanos\n * @memberof Timestamp\n * @instance\n */\n Timestamp.prototype.nanos = 0\n\n /**\n * Creates a new Timestamp instance using the specified properties.\n * @function create\n * @memberof Timestamp\n * @static\n * @param {ITimestamp=} [properties] Properties to set\n * @returns {Timestamp} Timestamp instance\n */\n Timestamp.create = function create(properties) {\n return new Timestamp(properties)\n }\n\n /**\n * Encodes the specified Timestamp message. Does not implicitly {@link Timestamp.verify|verify} messages.\n * @function encode\n * @memberof Timestamp\n * @static\n * @param {ITimestamp} message Timestamp message or plain object to encode\n * @param {$protobuf.Writer} [writer] Writer to encode to\n * @returns {$protobuf.Writer} Writer\n */\n Timestamp.encode = function encode(message, writer) {\n if (!writer) writer = $Writer.create()\n if (\n message.seconds != null &&\n Object.hasOwnProperty.call(message, \"seconds\")\n )\n writer.uint32(/* id 1, wireType 0 =*/ 8).int64(message.seconds)\n if (message.nanos != null && Object.hasOwnProperty.call(message, \"nanos\"))\n writer.uint32(/* id 2, wireType 0 =*/ 16).int32(message.nanos)\n return writer\n }\n\n /**\n * Decodes a Timestamp message from the specified reader or buffer.\n * @function decode\n * @memberof Timestamp\n * @static\n * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from\n * @param {number} [length] Message length if known beforehand\n * @returns {Timestamp} Timestamp\n * @throws {Error} If the payload is not a reader or valid buffer\n * @throws {$protobuf.util.ProtocolError} If required fields are missing\n */\n Timestamp.decode = function decode(reader, length) {\n if (!(reader instanceof $Reader)) reader = $Reader.create(reader)\n let end = length === undefined ? reader.len : reader.pos + length,\n message = new $root.Timestamp()\n while (reader.pos < end) {\n let tag = reader.uint32()\n switch (tag >>> 3) {\n case 1: {\n message.seconds = reader.int64()\n break\n }\n case 2: {\n message.nanos = reader.int32()\n break\n }\n default:\n reader.skipType(tag & 7)\n break\n }\n }\n return message\n }\n\n /**\n * Verifies a Timestamp message.\n * @function verify\n * @memberof Timestamp\n * @static\n * @param {Object.<string,*>} message Plain object to verify\n * @returns {string|null} `null` if valid, otherwise the reason why it is not\n */\n Timestamp.verify = function verify(message) {\n if (typeof message !== \"object\" || message === null)\n return \"object expected\"\n if (message.seconds != null && message.hasOwnProperty(\"seconds\"))\n if (\n !$util.isInteger(message.seconds) &&\n !(\n message.seconds &&\n $util.isInteger(message.seconds.low) &&\n $util.isInteger(message.seconds.high)\n )\n )\n return \"seconds: integer|Long expected\"\n if (message.nanos != null && message.hasOwnProperty(\"nanos\"))\n if (!$util.isInteger(message.nanos)) return \"nanos: integer expected\"\n return null\n }\n\n /**\n * Gets the default type url for Timestamp\n * @function getTypeUrl\n * @memberof Timestamp\n * @static\n * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default \"type.googleapis.com\")\n * @returns {string} The default type url\n */\n Timestamp.getTypeUrl = function getTypeUrl(typeUrlPrefix) {\n if (typeUrlPrefix === undefined) {\n typeUrlPrefix = \"type.googleapis.com\"\n }\n return typeUrlPrefix + \"/Timestamp\"\n }\n\n return Timestamp\n})())\n\n/**\n * @type {Class}\n */\nexport const Event = ($root.Event = (() => {\n /**\n * Properties of an Event.\n * @exports IEvent\n * @interface IEvent\n * @property {Uint8Array|null} [eventBytes] Event eventBytes\n * @property {string|null} [type] Event type\n */\n\n /**\n * Constructs a new Event.\n * @exports Event\n * @classdesc Represents an Event.\n * @implements IEvent\n * @constructor\n * @param {IEvent=} [properties] Properties to set\n */\n function Event(properties) {\n if (properties)\n for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i)\n if (properties[keys[i]] != null) this[keys[i]] = properties[keys[i]]\n }\n\n /**\n * Event eventBytes.\n * @member {Uint8Array} eventBytes\n * @memberof Event\n * @instance\n */\n Event.prototype.eventBytes = $util.newBuffer([])\n\n /**\n * Event type.\n * @member {string} type\n * @memberof Event\n * @instance\n */\n Event.prototype.type = \"\"\n\n /**\n * Creates a new Event instance using the specified properties.\n * @function create\n * @memberof Event\n * @static\n * @param {IEvent=} [properties] Properties to set\n * @returns {Event} Event instance\n */\n Event.create = function create(properties) {\n return new Event(properties)\n }\n\n /**\n * Encodes the specified Event message. Does not implicitly {@link Event.verify|verify} messages.\n * @function encode\n * @memberof Event\n * @static\n * @param {IEvent} message Event message or plain object to encode\n * @param {$protobuf.Writer} [writer] Writer to encode to\n * @returns {$protobuf.Writer} Writer\n */\n Event.encode = function encode(message, writer) {\n if (!writer) writer = $Writer.create()\n if (\n message.eventBytes != null &&\n Object.hasOwnProperty.call(message, \"eventBytes\")\n )\n writer.uint32(/* id 1, wireType 2 =*/ 10).bytes(message.eventBytes)\n if (message.type != null && Object.hasOwnProperty.call(message, \"type\"))\n writer.uint32(/* id 2, wireType 2 =*/ 18).string(message.type)\n return writer\n }\n\n /**\n * Decodes an Event message from the specified reader or buffer.\n * @function decode\n * @memberof Event\n * @static\n * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from\n * @param {number} [length] Message length if known beforehand\n * @returns {Event} Event\n * @throws {Error} If the payload is not a reader or valid buffer\n * @throws {$protobuf.util.ProtocolError} If required fields are missing\n */\n Event.decode = function decode(reader, length) {\n if (!(reader instanceof $Reader)) reader = $Reader.create(reader)\n let end = length === undefined ? reader.len : reader.pos + length,\n message = new $root.Event()\n while (reader.pos < end) {\n let tag = reader.uint32()\n switch (tag >>> 3) {\n case 1: {\n message.eventBytes = reader.bytes()\n break\n }\n case 2: {\n message.type = reader.string()\n break\n }\n default:\n reader.skipType(tag & 7)\n break\n }\n }\n return message\n }\n\n /**\n * Verifies an Event message.\n * @function verify\n * @memberof Event\n * @static\n * @param {Object.<string,*>} message Plain object to verify\n * @returns {string|null} `null` if valid, otherwise the reason why it is not\n */\n Event.verify = function verify(message) {\n if (typeof message !== \"object\" || message === null)\n return \"object expected\"\n if (message.eventBytes != null && message.hasOwnProperty(\"eventBytes\"))\n if (\n !(\n (message.eventBytes &&\n typeof message.eventBytes.length === \"number\") ||\n $util.isString(message.eventBytes)\n )\n )\n return \"eventBytes: buffer expected\"\n if (message.type != null && message.hasOwnProperty(\"type\"))\n if (!$util.isString(message.type)) return \"type: string expected\"\n return null\n }\n\n /**\n * Gets the default type url for Event\n * @function getTypeUrl\n * @memberof Event\n * @static\n * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default \"type.googleapis.com\")\n * @returns {string} The default type url\n */\n Event.getTypeUrl = function getTypeUrl(typeUrlPrefix) {\n if (typeUrlPrefix === undefined) {\n typeUrlPrefix = \"type.googleapis.com\"\n }\n return typeUrlPrefix + \"/Event\"\n }\n\n return Event\n})())\n\n/**\n * @type Class\n */\nexport const SendEventResponse = ($root.SendEventResponse = (() => {\n /**\n * Properties of a SendEventResponse.\n * @exports ISendEventResponse\n * @interface ISendEventResponse\n * @property {Status|null} [status] SendEventResponse status\n * @property {Code|null} [code] SendEventResponse code\n * @property {number|Long|null} [sentTime] SendEventResponse sentTime\n * @property {string|null} [reason] SendEventResponse reason\n * @property {Object.<string,string>|null} [data] SendEventResponse data\n */\n\n /**\n * Constructs a new SendEventResponse.\n * @exports SendEventResponse\n * @classdesc Represents a SendEventResponse.\n * @implements ISendEventResponse\n * @constructor\n * @param {ISendEventResponse=} [properties] Properties to set\n */\n function SendEventResponse(properties) {\n this.data = {}\n if (properties)\n for (let keys = Object.keys(properties), i = 0; i < keys.length; ++i)\n if (properties[keys[i]] != null) this[keys[i]] = properties[keys[i]]\n }\n\n /**\n * SendEventResponse status.\n * @member {Status} status\n * @memberof SendEventResponse\n * @instance\n */\n SendEventResponse.prototype.status = 0\n\n /**\n * SendEventResponse code.\n * @member {Code} code\n * @memberof SendEventResponse\n * @instance\n */\n SendEventResponse.prototype.code = 0\n\n /**\n * SendEventResponse sentTime.\n * @member {number|Long} sentTime\n * @memberof SendEventResponse\n * @instance\n */\n SendEventResponse.prototype.sentTime = $util.Long\n ? $util.Long.fromBits(0, 0, false)\n : 0\n\n /**\n * SendEventResponse reason.\n * @member {string} reason\n * @memberof SendEventResponse\n * @instance\n */\n SendEventResponse.prototype.reason = \"\"\n\n /**\n * SendEventResponse data.\n * @member {Object.<string,string>} data\n * @memberof SendEventResponse\n * @instance\n */\n SendEventResponse.prototype.data = $util.emptyObject\n\n /**\n * Creates a new SendEventResponse instance using the specified properties.\n * @function create\n * @memberof SendEventResponse\n * @static\n * @param {ISendEventResponse=} [properties] Properties to set\n * @returns {SendEventResponse} SendEventResponse instance\n */\n SendEventResponse.create = function create(properties) {\n return new SendEventResponse(properties)\n }\n\n /**\n * Encodes the specified SendEventResponse message. Does not implicitly {@link SendEventResponse.verify|verify} messages.\n * @function encode\n * @memberof SendEventResponse\n * @static\n * @param {ISendEventResponse} message SendEventResponse message or plain object to encode\n * @param {$protobuf.Writer} [writer] Writer to encode to\n * @returns {$protobuf.Writer} Writer\n */\n SendEventResponse.encode = function encode(message, writer) {\n if (!writer) writer = $Writer.create()\n if (message.status != null && Object.hasOwnProperty.call(message, \"status\"))\n writer.uint32(/* id 1, wireType 0 =*/ 8).int32(message.status)\n if (message.code != null && Object.hasOwnProperty.call(message, \"code\"))\n writer.uint32(/* id 2, wireType 0 =*/ 16).int32(message.code)\n if (\n message.sentTime != null &&\n Object.hasOwnProperty.call(message, \"sentTime\")\n )\n writer.uint32(/* id 3, wireType 0 =*/ 24).int64(message.sentTime)\n if (message.reason != null && Object.hasOwnProperty.call(message, \"reason\"))\n writer.uint32(/* id 4, wireType 2 =*/ 34).string(message.reason)\n if (message.data != null && Object.hasOwnProperty.call(message, \"data\"))\n for (let keys = Object.keys(message.data), i = 0; i < keys.length; ++i)\n writer\n .uint32(/* id 5, wireType 2 =*/ 42)\n .fork()\n .uint32(/* id 1, wireType 2 =*/ 10)\n .string(keys[i])\n .uint32(/* id 2, wireType 2 =*/ 18)\n .string(message.data[keys[i]])\n .ldelim()\n return writer\n }\n\n /**\n * Decodes a SendEventResponse message from the specified reader or buffer.\n * @function decode\n * @memberof SendEventResponse\n * @static\n * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from\n * @param {number} [length] Message length if known beforehand\n * @returns {SendEventResponse} SendEventResponse\n * @throws {Error} If the payload is not a reader or valid buffer\n * @throws {$protobuf.util.ProtocolError} If required fields are missing\n */\n SendEventResponse.decode = function decode(reader, length) {\n if (!(reader instanceof $Reader)) reader = $Reader.create(reader)\n let end = length === undefined ? reader.len : reader.pos + length,\n message = new $root.SendEventResponse(),\n key,\n value\n while (reader.pos < end) {\n let tag = reader.uint32()\n switch (tag >>> 3) {\n case 1: {\n message.status = reader.int32()\n break\n }\n case 2: {\n message.code = reader.int32()\n break\n }\n case 3: {\n message.sentTime = reader.int64()\n break\n }\n case 4: {\n message.reason = reader.string()\n break\n }\n case 5: {\n if (message.data === $util.emptyObject) message.data = {}\n let end2 = reader.uint32() + reader.pos\n key = \"\"\n value = \"\"\n while (reader.pos < end2) {\n let tag2 = reader.uint32()\n switch (tag2 >>> 3) {\n case 1:\n key = reader.string()\n break\n case 2:\n value = reader.string()\n break\n default:\n reader.skipType(tag2 & 7)\n break\n }\n }\n message.data[key] = value\n break\n }\n default:\n reader.skipType(tag & 7)\n break\n }\n }\n return message\n }\n\n /**\n * Verifies a SendEventResponse message.\n * @function verify\n * @memberof SendEventResponse\n * @static\n * @param {Object.<string,*>} message Plain object to verify\n * @returns {string|null} `null` if valid, otherwise the reason why it is not\n */\n SendEventResponse.verify = function verify(message) {\n if (typeof message !== \"object\" || message === null)\n return \"object expected\"\n if (message.status != null && message.hasOwnProperty(\"status\"))\n switch (message.status) {\n default:\n return \"status: enum value expected\"\n case 0:\n case 1:\n case 2:\n break\n }\n if (message.code != null && message.hasOwnProperty(\"code\"))\n switch (message.code) {\n default:\n return \"code: enum value expected\"\n case 0:\n case 1:\n case 2:\n case 3:\n case 4:\n case 5:\n break\n }\n if (message.sentTime != null && message.hasOwnProperty(\"sentTime\"))\n if (\n !$util.isInteger(message.sentTime) &&\n !(\n message.sentTime &&\n $util.isInteger(message.sentTime.low) &&\n $util.isInteger(message.sentTime.high)\n )\n )\n return \"sentTime: integer|Long expected\"\n if (message.reason != null && message.hasOwnProperty(\"reason\"))\n if (!$util.isString(message.reason)) return \"reason: string expected\"\n if (message.data != null && message.hasOwnProperty(\"data\")) {\n if (!$util.isObject(message.data)) return \"data: object expected\"\n let key = Object.keys(message.data)\n for (let i = 0; i < key.length; ++i)\n if (!$util.isString(message.data[key[i]]))\n return \"data: string{k:string} expected\"\n }\n return null\n }\n\n /**\n * Gets the default type url for SendEventResponse\n * @function getTypeUrl\n * @memberof SendEventResponse\n * @static\n * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default \"type.googleapis.com\")\n * @returns {string} The default type url\n */\n SendEventResponse.getTypeUrl = function getTypeUrl(typeUrlPrefix) {\n if (typeUrlPrefix === undefined) {\n typeUrlPrefix = \"type.googleapis.com\"\n }\n return typeUrlPrefix + \"/SendEventResponse\"\n }\n\n return SendEventResponse\n})())\n\n/**\n * Status enum.\n * @exports Status\n * @enum {number}\n * @property {number} STATUS_UNSPECIFIED=0 STATUS_UNSPECIFIED value\n * @property {number} STATUS_SUCCESS=1 STATUS_SUCCESS value\n * @property {number} STATUS_ERROR=2 STATUS_ERROR value\n */\nexport const Status = ($root.Status = (() => {\n const valuesById = {},\n values = Object.create(valuesById)\n values[(valuesById[0] = \"STATUS_UNSPECIFIED\")] = 0\n values[(valuesById[1] = \"STATUS_SUCCESS\")] = 1\n values[(valuesById[2] = \"STATUS_ERROR\")] = 2\n return values\n})())\n\n/**\n * Code enum.\n * @exports Code\n * @enum {number}\n * @property {number} CODE_UNSPECIFIED=0 CODE_UNSPECIFIED value\n * @property {number} CODE_OK=1 CODE_OK value\n * @property {number} CODE_BAD_REQUEST=2 CODE_BAD_REQUEST value\n * @property {number} CODE_INTERNAL_ERROR=3 CODE_INTERNAL_ERROR value\n * @property {number} CODE_MAX_CONNECTION_LIMIT_REACHED=4 CODE_MAX_CONNECTION_LIMIT_REACHED value\n * @property {number} CODE_MAX_USER_LIMIT_REACHED=5 CODE_MAX_USER_LIMIT_REACHED value\n */\nexport const Code = ($root.Code = (() => {\n const valuesById = {},\n values = Object.create(valuesById)\n values[(valuesById[0] = \"CODE_UNSPECIFIED\")] = 0\n values[(valuesById[1] = \"CODE_OK\")] = 1\n values[(valuesById[2] = \"CODE_BAD_REQUEST\")] = 2\n values[(valuesById[3] = \"CODE_INTERNAL_ERROR\")] = 3\n values[(valuesById[4] = \"CODE_MAX_CONNECTION_LIMIT_REACHED\")] = 4\n values[(valuesById[5] = \"CODE_MAX_USER_LIMIT_REACHED\")] = 5\n return values\n})())\n\nexport { $root as default }\n","let logging = false\n\nfunction format(prefix, args) {\n if (!prefix) return args\n return [prefix, ...args]\n}\n\nconst info = function (prefix = \"\", ...args) {\n if (!logging) return\n console.log(...format(prefix, args))\n}\n\nconst debug = function (prefix = \"\", ...args) {\n if (!logging) return\n console.debug(...format(prefix, args))\n}\n\nconst warn = function (prefix = \"\", ...args) {\n if (!logging) return\n console.warn(...format(prefix, args))\n}\n\nconst error = function (prefix = \"\", ...args) {\n console.error(...format(prefix, args))\n}\n\nexport const logger = {\n info,\n debug,\n warn,\n error,\n get logging() {\n return logging\n },\n set logging(value) {\n logging = Boolean(value)\n },\n}\n","/**\n * Returns a promise that resolves with the content of a blob as an ArrayBuffer\n *\n * @param {Blob} blob blob content\n * @returns {Promise}\n */\nexport const readAsBuffer = async (blob) => {\n if (blob.arrayBuffer) {\n return blob.arrayBuffer()\n }\n\n return new Promise((resolve, reject) => {\n const reader = new FileReader()\n\n reader.onloadend = () => {\n // https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readyState\n if (reader.readyState === 2) {\n resolve(reader.result)\n }\n }\n reader.onerror = (err) => reject(err)\n\n reader.readAsArrayBuffer(blob)\n })\n}\n","import { CUSTOM_EVENT, EVENT_TYPE } from \"./constants/index.js\"\nimport { NetworkError } from \"./error.js\"\nimport { SendEventRequest, SendEventResponse, Event } from \"./protos/raccoon.js\"\nimport { logger } from \"./logger.js\"\nimport { readAsBuffer } from \"./blob.js\"\n\nconst logPrefix = \"Network:\"\n\n/**\n * Gives timestamp object as google timestamp format\n * @returns timestamp object containing seconds\n */\nconst getTimestamp = () => {\n const date = new Date()\n const seconds = Math.floor(date.getTime() / 1000)\n\n return { seconds }\n}\n\nexport default class Transport {\n #config\n #store\n #eventBus\n #id\n #retryCount = 0\n #resetRetryTimeout\n constructor({ config, eventBus, store, id }) {\n this.#config = config\n this.#eventBus = eventBus\n this.#store = store\n this.#retryCount = 0\n this.#resetRetryTimeout = undefined\n this.#id = id\n }\n\n #createRequest(batch) {\n const reqGuid = this.#id.uuidv4()\n const { seconds } = getTimestamp()\n\n logger.info(logPrefix, \"generated reqGuid\", reqGuid)\n logger.info(logPrefix, \"generated timestamp(seconds)\", seconds)\n\n // update QoS1 events in store\n const realTimeBatch = batch.filter((event) => {\n return event.eventType === EVENT_TYPE.REALTIME\n })\n\n if (realTimeBatch.length && this.#store.isOpen) {\n this.#store.update(realTimeBatch, \"reqGuid\", reqGuid)\n logger.info(logPrefix, \"updated reqGuid for all events in store\")\n }\n\n const encodedBatch = batch.map((payload) => {\n const { data, type } = payload\n return Event.create({\n eventBytes: data,\n type,\n })\n })\n\n const request = SendEventRequest.create({\n reqGuid,\n sentTime: {\n seconds,\n },\n events: [...encodedBatch],\n })\n\n logger.debug(logPrefix, \"network request\", request)\n\n return {\n reqGuid,\n body: SendEventRequest.encode(request).finish(),\n }\n }\n\n #retry(request) {\n const { maxRetries, timeBetweenTwoRetries, timeToResumeRetries } =\n this.#config\n\n if (this.#retryCount < maxRetries) {\n if (this.#resetRetryTimeout) {\n window.clearTimeout(this.#resetRetryTimeout)\n this.#resetRetryTimeout = undefined\n }\n\n this.#retryCount += 1\n\n logger.debug(logPrefix, \"retry\", this.#retryCount)\n window.setTimeout(() => {\n this.#eventBus.emit(CUSTOM_EVENT.BATCH_FAILED, {\n reqGuid: request.reqGuid,\n })\n }, timeBetweenTwoRetries)\n } else if (this.#retryCount === maxRetries) {\n if (this.#resetRetryTimeout === undefined) {\n logger.debug(logPrefix, \"waiting for\", timeToResumeRetries)\n this.#resetRetryTimeout = window.setTimeout(() => {\n this.#retryCount = 0\n this.#retry(request)\n }, timeToResumeRetries)\n }\n }\n }\n\n async #makeRequest(request, { retry }) {\n const headers = new Headers(this.#config.headers)\n headers.append(\"Content-Type\", \"application/proto\")\n\n try {\n const response = await fetch(this.#config.url, {\n method: \"POST\",\n headers,\n body: request.body,\n })\n\n if (!response.ok) {\n logger.error(\n logPrefix,\n new NetworkError(\n `Network request to raccoon failed with status code ${response.status}`\n )\n )\n if (retry) this.#retry(request)\n return\n }\n\n logger.info(logPrefix, \"received response from raccoon \")\n if (this.#store.isOpen()) {\n const blob = await response.blob()\n\n const buffer = await readAsBuffer(blob)\n\n const uInt = new Uint8Array(buffer)\n const res = SendEventResponse.decode(uInt)\n\n logger.debug(\n logPrefix,\n \"response data from Raccoon\",\n res,\n JSON.stringify(res, undefined, 2)\n )\n\n const events = await this.#store.readByReqGuid(res.data[\"req_guid\"])\n this.#store.remove(events)\n logger.debug(\n \"remove events from store with reqGuid\",\n res.data[\"req_guid\"]\n )\n }\n } catch (err) {\n logger.error(logPrefix, new NetworkError(err.message, { cause: err }))\n if (retry) this.#retry(request)\n }\n }\n\n /**\n * Send data over network to clickstream BE\n *\n * @param batch batch to send\n */\n async send(\n /** @type {import(\"./store.js\").Event[]} */ batch,\n { retry = false } = {}\n ) {\n const request = this.#createRequest(batch)\n this.#makeRequest(request, { retry })\n }\n}\n","import { EVENT_TYPE } from \"./constants/index.js\"\nimport { logger } from \"./logger.js\"\n\nconst logPrefix = \"Processor:\"\n\nexport default class Processor {\n #config\n #store\n #id\n #isRealTimeEventsSupported\n constructor({ config, store, id, isRealTimeEventsSupported }) {\n this.#config = config\n this.#store = store\n this.#id = id\n this.#isRealTimeEventsSupported = isRealTimeEventsSupported\n }\n\n #type(proto) {\n if (!this.#isRealTimeEventsSupported) {\n logger.info(\n logPrefix,\n `treating \"${proto.eventName}\" event as QoS0 as QoS1 events are not supported`\n )\n return EVENT_TYPE.INSTANT\n }\n\n // if the storage is not available, event is treated as instant event\n if (!this.#store.isOpen()) {\n logger.info(\n logPrefix,\n `treating \"${proto.eventName}\" event as QoS0 as indexedDB is not supported`\n )\n return EVENT_TYPE.INSTANT\n }\n\n if (this.#config?.classification?.instant?.includes(proto.eventName)) {\n logger.info(\n logPrefix,\n `\"${proto.eventName}\" event is classified as QoS0 as per configuration`\n )\n return EVENT_TYPE.INSTANT\n }\n\n logger.info(\n logPrefix,\n `\"${proto.eventName}\" event is considered as QoS1 by default configuration`\n )\n return EVENT_TYPE.REALTIME\n }\n\n #createEvent(payload, eventType) {\n const PayloadConstructor = payload.constructor\n const encodedEvent = PayloadConstructor.encode(payload).finish()\n\n try {\n if (PayloadConstructor.decode) {\n const decodedEvent = PayloadConstructor.decode(encodedEvent)\n logger.debug(logPrefix, \"decoded event payload\", decodedEvent)\n }\n } catch (err) {\n logger.debug(logPrefix, \"event decoding failed\", err)\n }\n\n const typeUrlSplit = PayloadConstructor.getTypeUrl(\"\").split(\".\")\n const typeUrl = typeUrlSplit[typeUrlSplit.length - 1].toLowerCase()\n const type = this.#config.group\n ? `${this.#config.group}-${typeUrl}`\n : typeUrl\n\n logger.info(logPrefix, \"topic name is set to\", type)\n\n /** @type {import(\"./store.js\").Event} */\n const event = {\n data: encodedEvent,\n eventType,\n type,\n }\n\n if (eventType === EVENT_TYPE.REALTIME) {\n event.eventGuid = this.#id.uuidv4()\n event.reqGuid = \"\"\n }\n\n logger.info(logPrefix, \"created a new event\")\n logger.debug(logPrefix, \"new event data\", event)\n return event\n }\n\n /**\n * Processes an event\n *\n * @param proto - event proto\n * @returns type and event\n */\n process(/** @type {object} */ proto) {\n const type = this.#type(proto)\n const event = this.#createEvent(proto, type)\n return {\n type,\n event,\n }\n }\n}\n","import { CUSTOM_EVENT, TICK_TIME } from \"./constants/index.js\"\nimport { logger } from \"./logger.js\"\n\nconst logPrefix = \"Scheduler:\"\nexport default class Scheduler {\n /** @type { number | NodeJS.Timer | undefined } */\n #intervalId\n #waitTime\n #batching\n #config\n #eventBus\n #store\n #batch\n #lastBatch\n constructor({ config, eventBus, store }) {\n this.#config = config\n this.#eventBus = eventBus\n this.#store = store\n this.#intervalId = undefined\n this.#waitTime = 0\n this.#batching = false\n this.#batch = []\n this.#lastBatch = []\n }\n\n /**\n * Return if the scheduler is running or not\n */\n isRunning() {\n return this.#batching\n }\n\n /**\n * Start the scheduler\n */\n start() {\n this.#batching = true\n this.#run()\n this.#listeners()\n }\n\n /**\n * Stop the scheduler\n */\n stop() {\n this.#clearInterval()\n this.#waitTime = 0\n this.#batching = false\n }\n\n /**\n * Pause the scheduler\n */\n pause() {\n this.#batching = false\n }\n\n /**\n * Resume the scheduler\n */\n resume() {\n this.#batching = true\n }\n\n async free() {\n try {\n this.stop()\n logger.info(logPrefix, \"scheduler is stopped\")\n logger.info(logPrefix, \"flushing all events\")\n if (this.#store.isOpen()) {\n await this.#flush()\n }\n this.#removeListeners()\n } catch (err) {\n return Promise.reject(err)\n }\n }\n\n /**\n * Flushes all the events in store\n */\n async #flush() {\n let events = await this.#store.read()\n\n // filter out existing events in batch and last batch\n events = events.filter((event) => {\n return ![...this.#batch, ...this.#lastBatch].some((data) => {\n return data.eventGuid === event.eventGuid\n })\n })\n\n logger.debug(logPrefix, \"flushed events\", events)\n this.#batch.push(...events)\n\n this.#emit()\n }\n\n #clearInterval() {\n if (this.#intervalId !== undefined) {\n clearInterval(this.#intervalId)\n this.#intervalId = undefined\n }\n }\n\n #emit() {\n if (this.#batch.length) {\n this.#eventBus.emit(CUSTOM_EVENT.BATCH_CREATED, { batch: this.#batch })\n }\n\n this.#waitTime = 0\n this.#lastBatch = this.#batch\n this.#batch = []\n }\n\n #listeners() {\n this.#eventBus?.on(CUSTOM_EVENT.BATCH_FAILED, async (e) => {\n logger.debug(logPrefix, \"batch failed with reqGuid\", e.detail.reqGuid)\n const events = await this.#store.readByReqGuid(e.detail.reqGuid)\n this.#eventBus.emit(CUSTOM_EVENT.BATCH_CREATED, { batch: events })\n })\n logger.info(logPrefix, 'added \"BATCH_FAILED\" listener')\n }\n\n #removeListeners() {\n this.#eventBus?.remove(CUSTOM_EVENT.BATCH_FAILED)\n logger.info(logPrefix, 'removed \"BATCH_FAILED\" listener')\n }\n\n #batchSize(batch) {\n return batch.reduce((prev, curr) => {\n return prev + new Blob(curr?.data).size\n }, 0)\n }\n\n #splitBySize(events) {\n const unitSize = this.#batchSize([events[0]])\n const batchSize = this.#batchSize(this.#batch)\n const remSize = this.#config.maxBatchSize - batchSize\n\n logger.debug(logPrefix, \"current batch size\", batchSize)\n logger.debug(logPrefix, \"max batch size\", this.#config.maxBatchSize)\n logger.debug(logPrefix, \"remaining batch size\", remSize)\n\n return events.splice(0, Math.ceil(remSize / unitSize) + 1)\n }\n\n async #getRealTimeEvents() {\n if (!this.#store.isOpen()) {\n logger.debug(logPrefix, \"store is not open\")\n return []\n }\n try {\n let events = await this.#store.read()\n\n // filter out existing events in batch and last batch\n events = events.filter((event) => {\n return ![...this.#batch, ...this.#lastBatch].some((data) => {\n return data.eventGuid === event.eventGuid\n })\n })\n\n if (!events.length) {\n logger.debug(\"no new QoS1 events are found\")\n return []\n }\n\n const eventsBySize = this.#splitBySize(events)\n\n logger.debug(\n logPrefix,\n \"events before splitting by size\",\n events,\n events.length\n )\n\n logger.debug(\n logPrefix,\n \"events after splitting by size\",\n eventsBySize,\n eventsBySize.length\n )\n\n return eventsBySize\n } catch (error) {\n logger.error(logPrefix, error)\n return []\n }\n }\n\n async #fill() {\n const realTimeEvents = await this.#getRealTimeEvents()\n logger.debug(logPrefix, \"QoS1 events\", realTimeEvents)\n if (realTimeEvents.length) {\n this.#batch.push(...realTimeEvents)\n logger.debug(logPrefix, \"QoS1 events pushed in batch\", this.#batch)\n }\n }\n\n #run() {\n this.#clearInterval()\n this.#intervalId = setInterval(() => {\n if (!this.#batching) {\n logger.debug(logPrefix, \"batching is not running\")\n return\n }\n\n this.#waitTime += 1\n this.#fill()\n\n const batchSize = this.#batchSize(this.#batch)\n\n if (batchSize >= this.#config.maxBatchSize) {\n this.#emit()\n logger.info(\n logPrefix,\n \"this batch of size\",\n batchSize,\n \"batch has reached max size threshold of\",\n this.#config.maxBatchSize\n )\n } else if (this.#waitTime >= this.#config.maxTimeBetweenTwoBatches) {\n this.#emit()\n logger.info(\n logPrefix,\n \"batch has waited max time of\",\n this.#config.maxTimeBetweenTwoBatches\n )\n }\n }, TICK_TIME)\n }\n}\n","export default class EventBus {\n constructor() {\n this.eventTarget = new EventTarget()\n }\n /**\n * Event emitter\n *\n * emit the event on EventTarget\n *\n * @param type name of the event\n * @param payload data to attach in event\n */\n emit(/** @type {string} */ type, /** @type {object} */ payload) {\n const event = new CustomEvent(type, { detail: payload })\n this.eventTarget.dispatchEvent(event)\n }\n\n /**\n * Event listener\n *\n * Subscribes to the given event on the same EventTarget\n *\n * @param type name of the event\n * @param callback callback function\n */\n on(/** @type {string} */ type, callback) {\n this.eventTarget.addEventListener(type, callback)\n }\n\n /**\n * Event remover\n *\n * Removes to the given event on the same EventTarget\n *\n * @param type name of the event\n * @param callback callback function\n */\n remove(/** @type {string} */ type, callback) {\n this.eventTarget.removeEventListener(type, callback)\n }\n}\n","import { DatabaseError } from \"./error.js\"\nimport { logger } from \"./logger.js\"\n\nconst STORE = \"events\"\nconst logPrefix = \"Store:\"\n\n/**\n * @typedef {object} Event - Event type used in database\n * @property {Uint8Array} data - encoded event data\n * @property {string=} eventGuid - eventGuid\n * @property {string=} reqGuid - reqGuid\n * @property {string} eventType - event type\n * @property {string} type - type\n */\nexport default class Store {\n #name\n #version\n #db\n #isOpen\n constructor({ name = \"clickstream_db\", version = 1 }) {\n this.#name = name\n this.#version = version\n this.#isOpen = false\n }\n\n isOpen() {\n return this.#isOpen\n }\n\n /**\n * Open a new database connection\n * @returns Returns status\n */\n open() {\n return new Promise((resolve, reject) => {\n const request = window.indexedDB.open(this.#name, this.#version)\n\n request.onblocked = (event) => {\n // If some other tab is loaded with the database, then it needs to be closed\n // before we can proceed.\n logger.info(\n logPrefix,\n \"please close all other tabs with this site open\"\n )\n // @ts-ignore\n reject(event.target.error)\n }\n\n request.onerror = (event) => {\n // @ts-ignore\n reject(event.target.error)\n }\n\n request.onsuccess = (event) => {\n // @ts-ignore\n this.#db = event.target.result\n this.#isOpen = true\n resolve(\"success\")\n logger.info(logPrefix, \"store is open with name\", this.#name)\n\n t