UNPKG

quodolores

Version:

Monorepo for the Firebase JavaScript SDK

193 lines (168 loc) 5.7 kB
/** * @license * Copyright 2019 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { SettingsService } from './settings_service'; import { ERROR_FACTORY, ErrorCode } from '../utils/errors'; import { consoleLogger } from '../utils/console_logger'; const DEFAULT_SEND_INTERVAL_MS = 10 * 1000; const INITIAL_SEND_TIME_DELAY_MS = 5.5 * 1000; // If end point does not work, the call will be tried for these many times. const DEFAULT_REMAINING_TRIES = 3; const MAX_EVENT_COUNT_PER_REQUEST = 1000; let remainingTries = DEFAULT_REMAINING_TRIES; interface LogResponseDetails { responseAction?: string; } interface BatchEvent { message: string; eventTime: number; } /* eslint-disable camelcase */ // CC/Fl accepted log format. interface TransportBatchLogFormat { request_time_ms: string; client_info: ClientInfo; log_source: number; log_event: Log[]; } interface ClientInfo { client_type: number; js_client_info: {}; } interface Log { source_extension_json_proto3: string; event_time_ms: string; } /* eslint-enable camelcase */ let queue: BatchEvent[] = []; let isTransportSetup: boolean = false; export function setupTransportService(): void { if (!isTransportSetup) { processQueue(INITIAL_SEND_TIME_DELAY_MS); isTransportSetup = true; } } /** * Utilized by testing to clean up message queue and un-initialize transport service. */ export function resetTransportService(): void { isTransportSetup = false; queue = []; } function processQueue(timeOffset: number): void { setTimeout(() => { // If there is no remainingTries left, stop retrying. if (remainingTries === 0) { return; } // If there are no events to process, wait for DEFAULT_SEND_INTERVAL_MS and try again. if (!queue.length) { return processQueue(DEFAULT_SEND_INTERVAL_MS); } dispatchQueueEvents(); }, timeOffset); } function dispatchQueueEvents(): void { // Extract events up to the maximum cap of single logRequest from top of "official queue". // The staged events will be used for current logRequest attempt, remaining events will be kept // for next attempt. const staged = queue.splice(0, MAX_EVENT_COUNT_PER_REQUEST); /* eslint-disable camelcase */ // We will pass the JSON serialized event to the backend. const log_event: Log[] = staged.map(evt => ({ source_extension_json_proto3: evt.message, event_time_ms: String(evt.eventTime) })); const data: TransportBatchLogFormat = { request_time_ms: String(Date.now()), client_info: { client_type: 1, // 1 is JS js_client_info: {} }, log_source: SettingsService.getInstance().logSource, log_event }; /* eslint-enable camelcase */ sendEventsToFl(data, staged).catch(() => { // If the request fails for some reason, add the events that were attempted // back to the primary queue to retry later. queue = [...staged, ...queue]; remainingTries--; consoleLogger.info(`Tries left: ${remainingTries}.`); processQueue(DEFAULT_SEND_INTERVAL_MS); }); } function sendEventsToFl( data: TransportBatchLogFormat, staged: BatchEvent[] ): Promise<void> { return postToFlEndpoint(data) .then(res => { if (!res.ok) { consoleLogger.info('Call to Firebase backend failed.'); } return res.json(); }) .then(res => { // Find the next call wait time from the response. const transportWait = Number(res.nextRequestWaitMillis); let requestOffset = DEFAULT_SEND_INTERVAL_MS; if (!isNaN(transportWait)) { requestOffset = Math.max(transportWait, requestOffset); } // Delete request if response include RESPONSE_ACTION_UNKNOWN or DELETE_REQUEST action. // Otherwise, retry request using normal scheduling if response include RETRY_REQUEST_LATER. const logResponseDetails: LogResponseDetails[] = res.logResponseDetails; if ( Array.isArray(logResponseDetails) && logResponseDetails.length > 0 && logResponseDetails[0].responseAction === 'RETRY_REQUEST_LATER' ) { queue = [...staged, ...queue]; consoleLogger.info(`Retry transport request later.`); } remainingTries = DEFAULT_REMAINING_TRIES; // Schedule the next process. processQueue(requestOffset); }); } function postToFlEndpoint(data: TransportBatchLogFormat): Promise<Response> { const flTransportFullUrl = SettingsService.getInstance().getFlTransportFullUrl(); return fetch(flTransportFullUrl, { method: 'POST', body: JSON.stringify(data) }); } function addToQueue(evt: BatchEvent): void { if (!evt.eventTime || !evt.message) { throw ERROR_FACTORY.create(ErrorCode.INVALID_CC_LOG); } // Add the new event to the queue. queue = [...queue, evt]; } /** Log handler for cc service to send the performance logs to the server. */ export function transportHandler( // eslint-disable-next-line @typescript-eslint/no-explicit-any serializer: (...args: any[]) => string ): (...args: unknown[]) => void { return (...args) => { const message = serializer(...args); addToQueue({ message, eventTime: Date.now() }); }; }