@datadog/browser-core
Advanced tools
Comparing version 4.17.2 to 4.18.1
@@ -9,3 +9,3 @@ "use strict"; | ||
var publicApi = (0, utils_1.assign)({ | ||
version: "4.17.2", | ||
version: "4.18.1", | ||
// This API method is intentionally not monitored, since the only thing executed is the | ||
@@ -12,0 +12,0 @@ // user-provided 'callback'. All SDK usages executed in the callback should be monitored, and |
@@ -28,5 +28,5 @@ "use strict"; | ||
var parameters = 'ddsource=browser' + | ||
"&ddtags=".concat(encodeURIComponent(["sdk_version:".concat("4.17.2")].concat(tags).join(','))) + | ||
"&ddtags=".concat(encodeURIComponent(["sdk_version:".concat("4.18.1")].concat(tags).join(','))) + | ||
"&dd-api-key=".concat(clientToken) + | ||
"&dd-evp-origin-version=".concat(encodeURIComponent("4.17.2")) + | ||
"&dd-evp-origin-version=".concat(encodeURIComponent("4.18.1")) + | ||
'&dd-evp-origin=browser' + | ||
@@ -33,0 +33,0 @@ "&dd-request-id=".concat((0, utils_1.generateUUID)()); |
@@ -1,7 +0,1 @@ | ||
/** | ||
* LIMITATION: | ||
* For NPM setup, this feature flag singleton is shared between RUM and Logs product. | ||
* This means that an experimental flag set on the RUM product will be set on the Logs product. | ||
* So keep in mind that in certain configurations, your experimental feature flag may affect other products. | ||
*/ | ||
export declare function updateExperimentalFeatures(enabledFeatures: string[] | undefined): void; | ||
@@ -8,0 +2,0 @@ export declare function isExperimentalFeatureEnabled(featureName: string): boolean; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getExperimentalFeatures = exports.resetExperimentalFeatures = exports.isExperimentalFeatureEnabled = exports.updateExperimentalFeatures = void 0; | ||
/** | ||
@@ -8,4 +10,4 @@ * LIMITATION: | ||
*/ | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getExperimentalFeatures = exports.resetExperimentalFeatures = exports.isExperimentalFeatureEnabled = exports.updateExperimentalFeatures = void 0; | ||
var utils_1 = require("../../tools/utils"); | ||
var display_1 = require("../../tools/display"); | ||
var enabledExperimentalFeatures; | ||
@@ -23,2 +25,5 @@ function updateExperimentalFeatures(enabledFeatures) { | ||
.forEach(function (flag) { | ||
if ((0, utils_1.includes)(flag, '-')) { | ||
display_1.display.warn("please use snake case for '".concat(flag, "'")); | ||
} | ||
enabledExperimentalFeatures.add(flag); | ||
@@ -25,0 +30,0 @@ }); |
@@ -40,3 +40,3 @@ "use strict"; | ||
service: 'browser-sdk', | ||
version: "4.17.2", | ||
version: "4.18.1", | ||
source: 'browser', | ||
@@ -43,0 +43,0 @@ _dd: { |
@@ -11,3 +11,3 @@ export { Configuration, InitConfiguration, buildCookieOptions, validateAndBuildConfiguration, DefaultPrivacyLevel, EndpointBuilder, isExperimentalFeatureEnabled, updateExperimentalFeatures, resetExperimentalFeatures, } from './domain/configuration'; | ||
export { SESSION_TIME_OUT_DELAY, } from './domain/session/sessionConstants'; | ||
export { HttpRequest, Batch, canUseEventBridge, getEventBridge, startBatchWithReplica } from './transport'; | ||
export { HttpRequest, createHttpRequest, Batch, canUseEventBridge, getEventBridge, startBatchWithReplica, } from './transport'; | ||
export * from './tools/display'; | ||
@@ -14,0 +14,0 @@ export * from './tools/urlPolyfill'; |
@@ -17,3 +17,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.createContextManager = exports.catchUserErrors = exports.BoundedBuffer = exports.initConsoleObservable = exports.initFetchObservable = exports.initXhrObservable = exports.COOKIE_ACCESS_DELAY = exports.deleteCookie = exports.setCookie = exports.getCookie = exports.areCookiesAuthorized = exports.getFileFromStackTraceString = exports.toStackTraceString = exports.createHandlingStack = exports.formatUnknownError = exports.ErrorSource = exports.instrumentSetter = exports.instrumentMethodAndCallOriginal = exports.instrumentMethod = exports.startBatchWithReplica = exports.getEventBridge = exports.canUseEventBridge = exports.Batch = exports.HttpRequest = exports.SESSION_TIME_OUT_DELAY = exports.stopSessionManager = exports.startSessionManager = exports.Observable = exports.setDebugMode = exports.callMonitored = exports.monitor = exports.monitored = exports.isTelemetryReplicationAllowed = exports.resetTelemetry = exports.startFakeTelemetry = exports.addTelemetryError = exports.addTelemetryDebug = exports.startTelemetry = exports.RawReportType = exports.initReportObservable = exports.makePublicApi = exports.defineGlobal = exports.computeStackTrace = exports.trackRuntimeError = exports.resetExperimentalFeatures = exports.updateExperimentalFeatures = exports.isExperimentalFeatureEnabled = exports.DefaultPrivacyLevel = exports.validateAndBuildConfiguration = exports.buildCookieOptions = void 0; | ||
exports.createContextManager = exports.catchUserErrors = exports.BoundedBuffer = exports.initConsoleObservable = exports.initFetchObservable = exports.initXhrObservable = exports.COOKIE_ACCESS_DELAY = exports.deleteCookie = exports.setCookie = exports.getCookie = exports.areCookiesAuthorized = exports.getFileFromStackTraceString = exports.toStackTraceString = exports.createHandlingStack = exports.formatUnknownError = exports.ErrorSource = exports.instrumentSetter = exports.instrumentMethodAndCallOriginal = exports.instrumentMethod = exports.startBatchWithReplica = exports.getEventBridge = exports.canUseEventBridge = exports.Batch = exports.createHttpRequest = exports.SESSION_TIME_OUT_DELAY = exports.stopSessionManager = exports.startSessionManager = exports.Observable = exports.setDebugMode = exports.callMonitored = exports.monitor = exports.monitored = exports.isTelemetryReplicationAllowed = exports.resetTelemetry = exports.startFakeTelemetry = exports.addTelemetryError = exports.addTelemetryDebug = exports.startTelemetry = exports.RawReportType = exports.initReportObservable = exports.makePublicApi = exports.defineGlobal = exports.computeStackTrace = exports.trackRuntimeError = exports.resetExperimentalFeatures = exports.updateExperimentalFeatures = exports.isExperimentalFeatureEnabled = exports.DefaultPrivacyLevel = exports.validateAndBuildConfiguration = exports.buildCookieOptions = void 0; | ||
exports.getSyntheticsResultId = exports.getSyntheticsTestId = exports.willSyntheticsInjectRum = exports.SESSION_COOKIE_NAME = exports.CLEAR_OLD_CONTEXTS_INTERVAL = exports.ContextHistory = exports.limitModification = void 0; | ||
@@ -58,3 +58,3 @@ var configuration_1 = require("./domain/configuration"); | ||
var transport_1 = require("./transport"); | ||
Object.defineProperty(exports, "HttpRequest", { enumerable: true, get: function () { return transport_1.HttpRequest; } }); | ||
Object.defineProperty(exports, "createHttpRequest", { enumerable: true, get: function () { return transport_1.createHttpRequest; } }); | ||
Object.defineProperty(exports, "Batch", { enumerable: true, get: function () { return transport_1.Batch; } }); | ||
@@ -61,0 +61,0 @@ Object.defineProperty(exports, "canUseEventBridge", { enumerable: true, get: function () { return transport_1.canUseEventBridge; } }); |
@@ -10,2 +10,5 @@ "use strict"; | ||
var instrumentationWrapper = function () { | ||
if (typeof instrumentation !== 'function') { | ||
return undefined; | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return | ||
@@ -12,0 +15,0 @@ return instrumentation.apply(this, arguments); |
@@ -17,3 +17,4 @@ import type { Context } from '../tools/context'; | ||
upsert(message: Context, key: string): void; | ||
flush(): void; | ||
flush(sendFn?: (data: string | FormData, bytesCount: number) => void): void; | ||
flushOnExit(): void; | ||
computeBytesCount(candidate: string): number; | ||
@@ -28,3 +29,3 @@ private addOrUpdate; | ||
private flushPeriodically; | ||
private flushOnVisibilityHidden; | ||
private setupFlushOnExit; | ||
} |
@@ -23,3 +23,3 @@ "use strict"; | ||
this.bufferMessagesCount = 0; | ||
this.flushOnVisibilityHidden(); | ||
this.setupFlushOnExit(); | ||
this.flushPeriodically(); | ||
@@ -33,3 +33,4 @@ } | ||
}; | ||
Batch.prototype.flush = function () { | ||
Batch.prototype.flush = function (sendFn) { | ||
if (sendFn === void 0) { sendFn = this.request.send; } | ||
if (this.bufferMessagesCount !== 0) { | ||
@@ -42,5 +43,8 @@ var messages = this.pushOnlyBuffer.concat((0, utils_1.objectValues)(this.upsertBuffer)); | ||
this.bufferMessagesCount = 0; | ||
this.request.send(messages.join('\n'), bytesCount); | ||
sendFn(messages.join('\n'), bytesCount); | ||
} | ||
}; | ||
Batch.prototype.flushOnExit = function () { | ||
this.flush(this.request.sendOnExit); | ||
}; | ||
Batch.prototype.computeBytesCount = function (candidate) { | ||
@@ -119,3 +123,3 @@ // Accurate bytes count computations can degrade performances when there is a lot of events to process | ||
}; | ||
Batch.prototype.flushOnVisibilityHidden = function () { | ||
Batch.prototype.setupFlushOnExit = function () { | ||
var _this = this; | ||
@@ -139,3 +143,3 @@ /** | ||
if (document.visibilityState === 'hidden') { | ||
_this.flush(); | ||
_this.flushOnExit(); | ||
} | ||
@@ -148,3 +152,3 @@ }); | ||
*/ | ||
(0, utils_1.addEventListener)(window, "beforeunload" /* BEFORE_UNLOAD */, function () { return _this.flush(); }); | ||
(0, utils_1.addEventListener)(window, "beforeunload" /* BEFORE_UNLOAD */, function () { return _this.flushOnExit(); }); | ||
} | ||
@@ -151,0 +155,0 @@ }; |
@@ -10,7 +10,10 @@ import type { EndpointBuilder } from '../domain/configuration'; | ||
*/ | ||
export declare class HttpRequest { | ||
private endpointBuilder; | ||
private bytesLimit; | ||
constructor(endpointBuilder: EndpointBuilder, bytesLimit: number); | ||
send(data: string | FormData, bytesCount: number): void; | ||
} | ||
export declare type HttpRequest = ReturnType<typeof createHttpRequest>; | ||
export declare function createHttpRequest(endpointBuilder: EndpointBuilder, bytesLimit: number): { | ||
send: (data: string | FormData, bytesCount: number) => void; | ||
/** | ||
* Since fetch keepalive behaves like regular fetch on Firefox, | ||
* keep using sendBeaconStrategy on exit | ||
*/ | ||
sendOnExit: (data: string | FormData, bytesCount: number) => void; | ||
}; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.HttpRequest = void 0; | ||
exports.createHttpRequest = void 0; | ||
var telemetry_1 = require("../domain/telemetry"); | ||
/** | ||
* Use POST request without content type to: | ||
* - avoid CORS preflight requests | ||
* - allow usage of sendBeacon | ||
* | ||
* multiple elements are sent separated by \n in order | ||
* to be parsed correctly without content type header | ||
*/ | ||
var HttpRequest = /** @class */ (function () { | ||
function HttpRequest(endpointBuilder, bytesLimit) { | ||
this.endpointBuilder = endpointBuilder; | ||
this.bytesLimit = bytesLimit; | ||
} | ||
HttpRequest.prototype.send = function (data, bytesCount) { | ||
var url = this.endpointBuilder.build(); | ||
var canUseBeacon = !!navigator.sendBeacon && bytesCount < this.bytesLimit; | ||
var monitor_1 = require("../tools/monitor"); | ||
function createHttpRequest(endpointBuilder, bytesLimit) { | ||
function sendBeaconStrategy(data, bytesCount) { | ||
var url = endpointBuilder.build(); | ||
var canUseBeacon = !!navigator.sendBeacon && bytesCount < bytesLimit; | ||
if (canUseBeacon) { | ||
@@ -32,9 +21,43 @@ try { | ||
} | ||
sendXHR(url, data); | ||
} | ||
function fetchKeepAliveStrategy(data, bytesCount) { | ||
var url = endpointBuilder.build(); | ||
var canUseKeepAlive = isKeepAliveSupported() && bytesCount < bytesLimit; | ||
if (canUseKeepAlive) { | ||
fetch(url, { method: 'POST', body: data, keepalive: true }).catch((0, monitor_1.monitor)(function () { | ||
// failed to queue the request | ||
sendXHR(url, data); | ||
})); | ||
} | ||
else { | ||
sendXHR(url, data); | ||
} | ||
} | ||
function isKeepAliveSupported() { | ||
// Request can throw, cf https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#errors | ||
try { | ||
return window.Request && 'keepalive' in new Request('http://a'); | ||
} | ||
catch (_a) { | ||
return false; | ||
} | ||
} | ||
function sendXHR(url, data) { | ||
var request = new XMLHttpRequest(); | ||
request.open('POST', url, true); | ||
request.send(data); | ||
} | ||
return { | ||
send: function (data, bytesCount) { | ||
fetchKeepAliveStrategy(data, bytesCount); | ||
}, | ||
/** | ||
* Since fetch keepalive behaves like regular fetch on Firefox, | ||
* keep using sendBeaconStrategy on exit | ||
*/ | ||
sendOnExit: sendBeaconStrategy, | ||
}; | ||
return HttpRequest; | ||
}()); | ||
exports.HttpRequest = HttpRequest; | ||
} | ||
exports.createHttpRequest = createHttpRequest; | ||
var hasReportedBeaconError = false; | ||
@@ -41,0 +64,0 @@ function reportBeaconError(e) { |
@@ -1,4 +0,4 @@ | ||
export { HttpRequest } from './httpRequest'; | ||
export { HttpRequest, createHttpRequest } from './httpRequest'; | ||
export { Batch } from './batch'; | ||
export { canUseEventBridge, getEventBridge, BrowserWindowWithEventBridge } from './eventBridge'; | ||
export { startBatchWithReplica } from './startBatchWithReplica'; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.startBatchWithReplica = exports.getEventBridge = exports.canUseEventBridge = exports.Batch = exports.HttpRequest = void 0; | ||
exports.startBatchWithReplica = exports.getEventBridge = exports.canUseEventBridge = exports.Batch = exports.createHttpRequest = void 0; | ||
var httpRequest_1 = require("./httpRequest"); | ||
Object.defineProperty(exports, "HttpRequest", { enumerable: true, get: function () { return httpRequest_1.HttpRequest; } }); | ||
Object.defineProperty(exports, "createHttpRequest", { enumerable: true, get: function () { return httpRequest_1.createHttpRequest; } }); | ||
var batch_1 = require("./batch"); | ||
@@ -7,0 +7,0 @@ Object.defineProperty(exports, "Batch", { enumerable: true, get: function () { return batch_1.Batch; } }); |
@@ -13,3 +13,3 @@ "use strict"; | ||
function createBatch(endpointBuilder) { | ||
return new batch_1.Batch(new httpRequest_1.HttpRequest(endpointBuilder, configuration.batchBytesLimit), configuration.batchMessagesLimit, configuration.batchBytesLimit, configuration.messageBytesLimit, configuration.flushTimeout); | ||
return new batch_1.Batch((0, httpRequest_1.createHttpRequest)(endpointBuilder, configuration.batchBytesLimit), configuration.batchMessagesLimit, configuration.batchBytesLimit, configuration.messageBytesLimit, configuration.flushTimeout); | ||
} | ||
@@ -16,0 +16,0 @@ return { |
@@ -6,3 +6,3 @@ import { catchUserErrors } from '../tools/catchUserErrors'; | ||
var publicApi = assign({ | ||
version: "4.17.2", | ||
version: "4.18.1", | ||
// This API method is intentionally not monitored, since the only thing executed is the | ||
@@ -9,0 +9,0 @@ // user-provided 'callback'. All SDK usages executed in the callback should be monitored, and |
@@ -25,5 +25,5 @@ import { timeStampNow } from '../../tools/timeUtils'; | ||
var parameters = 'ddsource=browser' + | ||
"&ddtags=".concat(encodeURIComponent(["sdk_version:".concat("4.17.2")].concat(tags).join(','))) + | ||
"&ddtags=".concat(encodeURIComponent(["sdk_version:".concat("4.18.1")].concat(tags).join(','))) + | ||
"&dd-api-key=".concat(clientToken) + | ||
"&dd-evp-origin-version=".concat(encodeURIComponent("4.17.2")) + | ||
"&dd-evp-origin-version=".concat(encodeURIComponent("4.18.1")) + | ||
'&dd-evp-origin=browser' + | ||
@@ -30,0 +30,0 @@ "&dd-request-id=".concat(generateUUID()); |
@@ -1,7 +0,1 @@ | ||
/** | ||
* LIMITATION: | ||
* For NPM setup, this feature flag singleton is shared between RUM and Logs product. | ||
* This means that an experimental flag set on the RUM product will be set on the Logs product. | ||
* So keep in mind that in certain configurations, your experimental feature flag may affect other products. | ||
*/ | ||
export declare function updateExperimentalFeatures(enabledFeatures: string[] | undefined): void; | ||
@@ -8,0 +2,0 @@ export declare function isExperimentalFeatureEnabled(featureName: string): boolean; |
@@ -7,2 +7,4 @@ /** | ||
*/ | ||
import { includes } from '../../tools/utils'; | ||
import { display } from '../../tools/display'; | ||
var enabledExperimentalFeatures; | ||
@@ -20,2 +22,5 @@ export function updateExperimentalFeatures(enabledFeatures) { | ||
.forEach(function (flag) { | ||
if (includes(flag, '-')) { | ||
display.warn("please use snake case for '".concat(flag, "'")); | ||
} | ||
enabledExperimentalFeatures.add(flag); | ||
@@ -22,0 +27,0 @@ }); |
@@ -37,3 +37,3 @@ import { ConsoleApiName } from '../../tools/display'; | ||
service: 'browser-sdk', | ||
version: "4.17.2", | ||
version: "4.18.1", | ||
source: 'browser', | ||
@@ -40,0 +40,0 @@ _dd: { |
@@ -11,3 +11,3 @@ export { Configuration, InitConfiguration, buildCookieOptions, validateAndBuildConfiguration, DefaultPrivacyLevel, EndpointBuilder, isExperimentalFeatureEnabled, updateExperimentalFeatures, resetExperimentalFeatures, } from './domain/configuration'; | ||
export { SESSION_TIME_OUT_DELAY, } from './domain/session/sessionConstants'; | ||
export { HttpRequest, Batch, canUseEventBridge, getEventBridge, startBatchWithReplica } from './transport'; | ||
export { HttpRequest, createHttpRequest, Batch, canUseEventBridge, getEventBridge, startBatchWithReplica, } from './transport'; | ||
export * from './tools/display'; | ||
@@ -14,0 +14,0 @@ export * from './tools/urlPolyfill'; |
@@ -14,3 +14,3 @@ export { buildCookieOptions, validateAndBuildConfiguration, DefaultPrivacyLevel, isExperimentalFeatureEnabled, updateExperimentalFeatures, resetExperimentalFeatures, } from './domain/configuration'; | ||
} from './domain/session/sessionConstants'; | ||
export { HttpRequest, Batch, canUseEventBridge, getEventBridge, startBatchWithReplica } from './transport'; | ||
export { createHttpRequest, Batch, canUseEventBridge, getEventBridge, startBatchWithReplica, } from './transport'; | ||
export * from './tools/display'; | ||
@@ -17,0 +17,0 @@ export * from './tools/urlPolyfill'; |
@@ -7,2 +7,5 @@ import { callMonitored, monitor } from './monitor'; | ||
var instrumentationWrapper = function () { | ||
if (typeof instrumentation !== 'function') { | ||
return undefined; | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return | ||
@@ -9,0 +12,0 @@ return instrumentation.apply(this, arguments); |
@@ -17,3 +17,4 @@ import type { Context } from '../tools/context'; | ||
upsert(message: Context, key: string): void; | ||
flush(): void; | ||
flush(sendFn?: (data: string | FormData, bytesCount: number) => void): void; | ||
flushOnExit(): void; | ||
computeBytesCount(candidate: string): number; | ||
@@ -28,3 +29,3 @@ private addOrUpdate; | ||
private flushPeriodically; | ||
private flushOnVisibilityHidden; | ||
private setupFlushOnExit; | ||
} |
@@ -20,3 +20,3 @@ import { display } from '../tools/display'; | ||
this.bufferMessagesCount = 0; | ||
this.flushOnVisibilityHidden(); | ||
this.setupFlushOnExit(); | ||
this.flushPeriodically(); | ||
@@ -30,3 +30,4 @@ } | ||
}; | ||
Batch.prototype.flush = function () { | ||
Batch.prototype.flush = function (sendFn) { | ||
if (sendFn === void 0) { sendFn = this.request.send; } | ||
if (this.bufferMessagesCount !== 0) { | ||
@@ -39,5 +40,8 @@ var messages = this.pushOnlyBuffer.concat(objectValues(this.upsertBuffer)); | ||
this.bufferMessagesCount = 0; | ||
this.request.send(messages.join('\n'), bytesCount); | ||
sendFn(messages.join('\n'), bytesCount); | ||
} | ||
}; | ||
Batch.prototype.flushOnExit = function () { | ||
this.flush(this.request.sendOnExit); | ||
}; | ||
Batch.prototype.computeBytesCount = function (candidate) { | ||
@@ -116,3 +120,3 @@ // Accurate bytes count computations can degrade performances when there is a lot of events to process | ||
}; | ||
Batch.prototype.flushOnVisibilityHidden = function () { | ||
Batch.prototype.setupFlushOnExit = function () { | ||
var _this = this; | ||
@@ -136,3 +140,3 @@ /** | ||
if (document.visibilityState === 'hidden') { | ||
_this.flush(); | ||
_this.flushOnExit(); | ||
} | ||
@@ -145,3 +149,3 @@ }); | ||
*/ | ||
addEventListener(window, "beforeunload" /* BEFORE_UNLOAD */, function () { return _this.flush(); }); | ||
addEventListener(window, "beforeunload" /* BEFORE_UNLOAD */, function () { return _this.flushOnExit(); }); | ||
} | ||
@@ -148,0 +152,0 @@ }; |
@@ -10,7 +10,10 @@ import type { EndpointBuilder } from '../domain/configuration'; | ||
*/ | ||
export declare class HttpRequest { | ||
private endpointBuilder; | ||
private bytesLimit; | ||
constructor(endpointBuilder: EndpointBuilder, bytesLimit: number); | ||
send(data: string | FormData, bytesCount: number): void; | ||
} | ||
export declare type HttpRequest = ReturnType<typeof createHttpRequest>; | ||
export declare function createHttpRequest(endpointBuilder: EndpointBuilder, bytesLimit: number): { | ||
send: (data: string | FormData, bytesCount: number) => void; | ||
/** | ||
* Since fetch keepalive behaves like regular fetch on Firefox, | ||
* keep using sendBeaconStrategy on exit | ||
*/ | ||
sendOnExit: (data: string | FormData, bytesCount: number) => void; | ||
}; |
import { addTelemetryError } from '../domain/telemetry'; | ||
/** | ||
* Use POST request without content type to: | ||
* - avoid CORS preflight requests | ||
* - allow usage of sendBeacon | ||
* | ||
* multiple elements are sent separated by \n in order | ||
* to be parsed correctly without content type header | ||
*/ | ||
var HttpRequest = /** @class */ (function () { | ||
function HttpRequest(endpointBuilder, bytesLimit) { | ||
this.endpointBuilder = endpointBuilder; | ||
this.bytesLimit = bytesLimit; | ||
} | ||
HttpRequest.prototype.send = function (data, bytesCount) { | ||
var url = this.endpointBuilder.build(); | ||
var canUseBeacon = !!navigator.sendBeacon && bytesCount < this.bytesLimit; | ||
import { monitor } from '../tools/monitor'; | ||
export function createHttpRequest(endpointBuilder, bytesLimit) { | ||
function sendBeaconStrategy(data, bytesCount) { | ||
var url = endpointBuilder.build(); | ||
var canUseBeacon = !!navigator.sendBeacon && bytesCount < bytesLimit; | ||
if (canUseBeacon) { | ||
@@ -29,9 +18,42 @@ try { | ||
} | ||
sendXHR(url, data); | ||
} | ||
function fetchKeepAliveStrategy(data, bytesCount) { | ||
var url = endpointBuilder.build(); | ||
var canUseKeepAlive = isKeepAliveSupported() && bytesCount < bytesLimit; | ||
if (canUseKeepAlive) { | ||
fetch(url, { method: 'POST', body: data, keepalive: true }).catch(monitor(function () { | ||
// failed to queue the request | ||
sendXHR(url, data); | ||
})); | ||
} | ||
else { | ||
sendXHR(url, data); | ||
} | ||
} | ||
function isKeepAliveSupported() { | ||
// Request can throw, cf https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#errors | ||
try { | ||
return window.Request && 'keepalive' in new Request('http://a'); | ||
} | ||
catch (_a) { | ||
return false; | ||
} | ||
} | ||
function sendXHR(url, data) { | ||
var request = new XMLHttpRequest(); | ||
request.open('POST', url, true); | ||
request.send(data); | ||
} | ||
return { | ||
send: function (data, bytesCount) { | ||
fetchKeepAliveStrategy(data, bytesCount); | ||
}, | ||
/** | ||
* Since fetch keepalive behaves like regular fetch on Firefox, | ||
* keep using sendBeaconStrategy on exit | ||
*/ | ||
sendOnExit: sendBeaconStrategy, | ||
}; | ||
return HttpRequest; | ||
}()); | ||
export { HttpRequest }; | ||
} | ||
var hasReportedBeaconError = false; | ||
@@ -38,0 +60,0 @@ function reportBeaconError(e) { |
@@ -1,4 +0,4 @@ | ||
export { HttpRequest } from './httpRequest'; | ||
export { HttpRequest, createHttpRequest } from './httpRequest'; | ||
export { Batch } from './batch'; | ||
export { canUseEventBridge, getEventBridge, BrowserWindowWithEventBridge } from './eventBridge'; | ||
export { startBatchWithReplica } from './startBatchWithReplica'; |
@@ -1,2 +0,2 @@ | ||
export { HttpRequest } from './httpRequest'; | ||
export { createHttpRequest } from './httpRequest'; | ||
export { Batch } from './batch'; | ||
@@ -3,0 +3,0 @@ export { canUseEventBridge, getEventBridge } from './eventBridge'; |
import { Batch } from './batch'; | ||
import { HttpRequest } from './httpRequest'; | ||
import { createHttpRequest } from './httpRequest'; | ||
export function startBatchWithReplica(configuration, endpoint, replicaEndpoint) { | ||
@@ -10,3 +10,3 @@ var primaryBatch = createBatch(endpoint); | ||
function createBatch(endpointBuilder) { | ||
return new Batch(new HttpRequest(endpointBuilder, configuration.batchBytesLimit), configuration.batchMessagesLimit, configuration.batchBytesLimit, configuration.messageBytesLimit, configuration.flushTimeout); | ||
return new Batch(createHttpRequest(endpointBuilder, configuration.batchBytesLimit), configuration.batchMessagesLimit, configuration.batchBytesLimit, configuration.messageBytesLimit, configuration.flushTimeout); | ||
} | ||
@@ -13,0 +13,0 @@ return { |
{ | ||
"name": "@datadog/browser-core", | ||
"version": "4.17.2", | ||
"version": "4.18.1", | ||
"license": "Apache-2.0", | ||
@@ -24,3 +24,3 @@ "main": "cjs/index.js", | ||
}, | ||
"gitHead": "b4a39bcc003ad5f38044dfedbe3bd3b118281183" | ||
"gitHead": "bd833602495172229db1e133b89a9f2b0b8eea9e" | ||
} |
@@ -7,2 +7,4 @@ /** | ||
*/ | ||
import { includes } from '../../tools/utils' | ||
import { display } from '../../tools/display' | ||
@@ -24,2 +26,5 @@ let enabledExperimentalFeatures: Set<string> | undefined | ||
.forEach((flag: string) => { | ||
if (includes(flag, '-')) { | ||
display.warn(`please use snake case for '${flag}'`) | ||
} | ||
enabledExperimentalFeatures!.add(flag) | ||
@@ -26,0 +31,0 @@ }) |
@@ -40,3 +40,10 @@ export { | ||
} from './domain/session/sessionConstants' | ||
export { HttpRequest, Batch, canUseEventBridge, getEventBridge, startBatchWithReplica } from './transport' | ||
export { | ||
HttpRequest, | ||
createHttpRequest, | ||
Batch, | ||
canUseEventBridge, | ||
getEventBridge, | ||
startBatchWithReplica, | ||
} from './transport' | ||
export * from './tools/display' | ||
@@ -43,0 +50,0 @@ export * from './tools/urlPolyfill' |
@@ -102,8 +102,22 @@ import type { Clock } from '../../test/specHelper' | ||
}) | ||
it('should not throw errors if original method was undefined', () => { | ||
const object: { method?: () => number } = {} | ||
const instrumentationStub = () => 2 | ||
const { stop } = instrumentMethod(object, 'method', () => instrumentationStub) | ||
thirdPartyInstrumentation(object) | ||
stop() | ||
expect(object.method).not.toThrow() | ||
}) | ||
}) | ||
}) | ||
function thirdPartyInstrumentation(object: { method: () => number }) { | ||
function thirdPartyInstrumentation(object: { method?: () => number }) { | ||
const originalMethod = object.method | ||
object.method = () => originalMethod() + 2 | ||
if (typeof originalMethod === 'function') { | ||
object.method = () => originalMethod() + 2 | ||
} | ||
} | ||
@@ -110,0 +124,0 @@ }) |
@@ -15,3 +15,6 @@ import { callMonitored, monitor } from './monitor' | ||
const instrumentationWrapper = function (this: OBJECT): ReturnType<OBJECT[METHOD]> { | ||
const instrumentationWrapper = function (this: OBJECT): ReturnType<OBJECT[METHOD]> | undefined { | ||
if (typeof instrumentation !== 'function') { | ||
return undefined | ||
} | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return | ||
@@ -18,0 +21,0 @@ return instrumentation.apply(this, arguments as unknown as Parameters<OBJECT[METHOD]>) |
@@ -25,3 +25,3 @@ import { display } from '../tools/display' | ||
) { | ||
this.flushOnVisibilityHidden() | ||
this.setupFlushOnExit() | ||
this.flushPeriodically() | ||
@@ -38,3 +38,3 @@ } | ||
flush() { | ||
flush(sendFn = this.request.send) { | ||
if (this.bufferMessagesCount !== 0) { | ||
@@ -49,6 +49,10 @@ const messages = this.pushOnlyBuffer.concat(objectValues(this.upsertBuffer)) | ||
this.request.send(messages.join('\n'), bytesCount) | ||
sendFn(messages.join('\n'), bytesCount) | ||
} | ||
} | ||
flushOnExit() { | ||
this.flush(this.request.sendOnExit) | ||
} | ||
computeBytesCount(candidate: string) { | ||
@@ -142,3 +146,3 @@ // Accurate bytes count computations can degrade performances when there is a lot of events to process | ||
private flushOnVisibilityHidden() { | ||
private setupFlushOnExit() { | ||
/** | ||
@@ -162,3 +166,3 @@ * With sendBeacon, requests are guaranteed to be successfully sent during document unload | ||
if (document.visibilityState === 'hidden') { | ||
this.flush() | ||
this.flushOnExit() | ||
} | ||
@@ -171,5 +175,5 @@ }) | ||
*/ | ||
addEventListener(window, DOM_EVENT.BEFORE_UNLOAD, () => this.flush()) | ||
addEventListener(window, DOM_EVENT.BEFORE_UNLOAD, () => this.flushOnExit()) | ||
} | ||
} | ||
} |
/* eslint-disable @typescript-eslint/unbound-method */ | ||
import sinon from 'sinon' | ||
import { stubEndpointBuilder } from '../../test/specHelper' | ||
import { stubEndpointBuilder, interceptRequests } from '../../test/specHelper' | ||
import type { Request } from '../../test/specHelper' | ||
import type { EndpointBuilder } from '../domain/configuration' | ||
import { createEndpointBuilder } from '../domain/configuration' | ||
import { HttpRequest } from './httpRequest' | ||
import { createHttpRequest } from './httpRequest' | ||
import type { HttpRequest } from './httpRequest' | ||
describe('httpRequest', () => { | ||
const BATCH_BYTES_LIMIT = 100 | ||
let server: sinon.SinonFakeServer | ||
let interceptor: ReturnType<typeof interceptRequests> | ||
let requests: Request[] | ||
let endpointBuilder: EndpointBuilder | ||
@@ -16,55 +18,120 @@ let request: HttpRequest | ||
beforeEach(() => { | ||
server = sinon.fakeServer.create() | ||
interceptor = interceptRequests() | ||
requests = interceptor.requests | ||
endpointBuilder = stubEndpointBuilder(ENDPOINT_URL) | ||
request = new HttpRequest(endpointBuilder, BATCH_BYTES_LIMIT) | ||
request = createHttpRequest(endpointBuilder, BATCH_BYTES_LIMIT) | ||
}) | ||
afterEach(() => { | ||
server.restore() | ||
interceptor.restore() | ||
}) | ||
it('should use xhr when sendBeacon is not defined', () => { | ||
request.send('{"foo":"bar1"}\n{"foo":"bar2"}', 10) | ||
describe('send', () => { | ||
it('should use xhr when fetch keepalive is not available', () => { | ||
interceptor.withRequest(false) | ||
expect(server.requests.length).toEqual(1) | ||
expect(server.requests[0].url).toContain(ENDPOINT_URL) | ||
expect(server.requests[0].requestBody).toEqual('{"foo":"bar1"}\n{"foo":"bar2"}') | ||
}) | ||
request.send('{"foo":"bar1"}\n{"foo":"bar2"}', 10) | ||
it('should use sendBeacon when the bytes count is correct', () => { | ||
spyOn(navigator, 'sendBeacon').and.callFake(() => true) | ||
expect(requests.length).toEqual(1) | ||
expect(requests[0].type).toBe('xhr') | ||
expect(requests[0].url).toContain(ENDPOINT_URL) | ||
expect(requests[0].body).toEqual('{"foo":"bar1"}\n{"foo":"bar2"}') | ||
}) | ||
request.send('{"foo":"bar1"}\n{"foo":"bar2"}', 10) | ||
it('should use fetch keepalive when the bytes count is correct', () => { | ||
if (!interceptor.isFetchKeepAliveSupported()) { | ||
pending('no fetch keepalive support') | ||
} | ||
expect(navigator.sendBeacon).toHaveBeenCalled() | ||
}) | ||
request.send('{"foo":"bar1"}\n{"foo":"bar2"}', 10) | ||
it('should use xhr over sendBeacon when the bytes count is too high', () => { | ||
spyOn(navigator, 'sendBeacon').and.callFake(() => true) | ||
expect(requests.length).toEqual(1) | ||
expect(requests[0].type).toBe('fetch') | ||
}) | ||
request.send('{"foo":"bar1"}\n{"foo":"bar2"}', BATCH_BYTES_LIMIT) | ||
it('should use xhr over fetch keepalive when the bytes count is too high', () => { | ||
request.send('{"foo":"bar1"}\n{"foo":"bar2"}', BATCH_BYTES_LIMIT) | ||
expect(server.requests.length).toEqual(1) | ||
expect(server.requests[0].url).toContain(ENDPOINT_URL) | ||
expect(server.requests[0].requestBody).toEqual('{"foo":"bar1"}\n{"foo":"bar2"}') | ||
}) | ||
expect(requests.length).toEqual(1) | ||
expect(requests[0].type).toBe('xhr') | ||
}) | ||
it('should fallback to xhr when sendBeacon is not queued', () => { | ||
spyOn(navigator, 'sendBeacon').and.callFake(() => false) | ||
it('should fallback to xhr when fetch keepalive is not queued', (done) => { | ||
if (!interceptor.isFetchKeepAliveSupported()) { | ||
pending('no fetch keepalive support') | ||
} | ||
let notQueuedFetch: Promise<never> | ||
interceptor.withFetch(() => { | ||
notQueuedFetch = Promise.reject() | ||
return notQueuedFetch | ||
}) | ||
request.send('{"foo":"bar1"}\n{"foo":"bar2"}', 10) | ||
request.send('{"foo":"bar1"}\n{"foo":"bar2"}', 10) | ||
expect(navigator.sendBeacon).toHaveBeenCalled() | ||
expect(server.requests.length).toEqual(1) | ||
notQueuedFetch!.catch(() => { | ||
expect(requests.length).toEqual(1) | ||
expect(requests[0].type).toBe('xhr') | ||
done() | ||
}) | ||
}) | ||
}) | ||
it('should fallback to xhr when sendBeacon throws', () => { | ||
spyOn(navigator, 'sendBeacon').and.callFake(() => { | ||
throw new TypeError() | ||
describe('sendOnExit', () => { | ||
it('should use xhr when sendBeacon is not defined', () => { | ||
interceptor.withSendBeacon(false) | ||
request.sendOnExit('{"foo":"bar1"}\n{"foo":"bar2"}', 10) | ||
expect(requests.length).toEqual(1) | ||
expect(requests[0].type).toBe('xhr') | ||
expect(requests[0].url).toContain(ENDPOINT_URL) | ||
expect(requests[0].body).toEqual('{"foo":"bar1"}\n{"foo":"bar2"}') | ||
}) | ||
request.send('{"foo":"bar1"}\n{"foo":"bar2"}', 10) | ||
it('should use sendBeacon when the bytes count is correct', () => { | ||
if (!interceptor.isSendBeaconSupported()) { | ||
pending('no sendBeacon support') | ||
} | ||
expect(navigator.sendBeacon).toHaveBeenCalled() | ||
expect(server.requests.length).toEqual(1) | ||
request.sendOnExit('{"foo":"bar1"}\n{"foo":"bar2"}', 10) | ||
expect(requests.length).toEqual(1) | ||
expect(requests[0].type).toBe('sendBeacon') | ||
}) | ||
it('should use xhr over sendBeacon when the bytes count is too high', () => { | ||
request.sendOnExit('{"foo":"bar1"}\n{"foo":"bar2"}', BATCH_BYTES_LIMIT) | ||
expect(requests.length).toEqual(1) | ||
expect(requests[0].type).toBe('xhr') | ||
}) | ||
it('should fallback to xhr when sendBeacon is not queued', () => { | ||
if (!interceptor.isSendBeaconSupported()) { | ||
pending('no sendBeacon support') | ||
} | ||
interceptor.withSendBeacon(() => false) | ||
request.sendOnExit('{"foo":"bar1"}\n{"foo":"bar2"}', 10) | ||
expect(requests.length).toEqual(1) | ||
expect(requests[0].type).toBe('xhr') | ||
}) | ||
it('should fallback to xhr when sendBeacon throws', () => { | ||
if (!interceptor.isSendBeaconSupported()) { | ||
pending('no sendBeacon support') | ||
} | ||
let sendBeaconCalled = false | ||
interceptor.withSendBeacon(() => { | ||
sendBeaconCalled = true | ||
throw new TypeError() | ||
}) | ||
request.sendOnExit('{"foo":"bar1"}\n{"foo":"bar2"}', 10) | ||
expect(sendBeaconCalled).toBe(true) | ||
expect(requests.length).toEqual(1) | ||
expect(requests[0].type).toBe('xhr') | ||
}) | ||
}) | ||
@@ -76,3 +143,4 @@ }) | ||
const BATCH_BYTES_LIMIT = 100 | ||
let server: sinon.SinonFakeServer | ||
let interceptor: ReturnType<typeof interceptRequests> | ||
let requests: Request[] | ||
let endpointBuilder: EndpointBuilder | ||
@@ -82,9 +150,10 @@ let request: HttpRequest | ||
beforeEach(() => { | ||
server = sinon.fakeServer.create() | ||
interceptor = interceptRequests() | ||
requests = interceptor.requests | ||
endpointBuilder = createEndpointBuilder({ clientToken }, 'logs', []) | ||
request = new HttpRequest(endpointBuilder, BATCH_BYTES_LIMIT) | ||
request = createHttpRequest(endpointBuilder, BATCH_BYTES_LIMIT) | ||
}) | ||
afterEach(() => { | ||
server.restore() | ||
interceptor.restore() | ||
}) | ||
@@ -97,8 +166,8 @@ | ||
const search = /dd-request-id=([^&]*)/ | ||
const requestId1 = search.exec(server.requests[0].url)?.[1] | ||
const requestId2 = search.exec(server.requests[1].url)?.[1] | ||
const requestId1 = search.exec(requests[0].url)?.[1] | ||
const requestId2 = search.exec(requests[1].url)?.[1] | ||
expect(requestId1).not.toBe(requestId2) | ||
expect(server.requests.length).toEqual(2) | ||
expect(requests.length).toEqual(2) | ||
}) | ||
}) |
import type { EndpointBuilder } from '../domain/configuration' | ||
import { addTelemetryError } from '../domain/telemetry' | ||
import { monitor } from '../tools/monitor' | ||
@@ -12,8 +13,9 @@ /** | ||
*/ | ||
export class HttpRequest { | ||
constructor(private endpointBuilder: EndpointBuilder, private bytesLimit: number) {} | ||
send(data: string | FormData, bytesCount: number) { | ||
const url = this.endpointBuilder.build() | ||
const canUseBeacon = !!navigator.sendBeacon && bytesCount < this.bytesLimit | ||
export type HttpRequest = ReturnType<typeof createHttpRequest> | ||
export function createHttpRequest(endpointBuilder: EndpointBuilder, bytesLimit: number) { | ||
function sendBeaconStrategy(data: string | FormData, bytesCount: number) { | ||
const url = endpointBuilder.build() | ||
const canUseBeacon = !!navigator.sendBeacon && bytesCount < bytesLimit | ||
if (canUseBeacon) { | ||
@@ -31,2 +33,30 @@ try { | ||
sendXHR(url, data) | ||
} | ||
function fetchKeepAliveStrategy(data: string | FormData, bytesCount: number) { | ||
const url = endpointBuilder.build() | ||
const canUseKeepAlive = isKeepAliveSupported() && bytesCount < bytesLimit | ||
if (canUseKeepAlive) { | ||
fetch(url, { method: 'POST', body: data, keepalive: true }).catch( | ||
monitor(() => { | ||
// failed to queue the request | ||
sendXHR(url, data) | ||
}) | ||
) | ||
} else { | ||
sendXHR(url, data) | ||
} | ||
} | ||
function isKeepAliveSupported() { | ||
// Request can throw, cf https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#errors | ||
try { | ||
return window.Request && 'keepalive' in new Request('http://a') | ||
} catch { | ||
return false | ||
} | ||
} | ||
function sendXHR(url: string, data: string | FormData) { | ||
const request = new XMLHttpRequest() | ||
@@ -36,5 +66,17 @@ request.open('POST', url, true) | ||
} | ||
return { | ||
send: (data: string | FormData, bytesCount: number) => { | ||
fetchKeepAliveStrategy(data, bytesCount) | ||
}, | ||
/** | ||
* Since fetch keepalive behaves like regular fetch on Firefox, | ||
* keep using sendBeaconStrategy on exit | ||
*/ | ||
sendOnExit: sendBeaconStrategy, | ||
} | ||
} | ||
let hasReportedBeaconError = false | ||
function reportBeaconError(e: unknown) { | ||
@@ -41,0 +83,0 @@ if (!hasReportedBeaconError) { |
@@ -1,4 +0,4 @@ | ||
export { HttpRequest } from './httpRequest' | ||
export { HttpRequest, createHttpRequest } from './httpRequest' | ||
export { Batch } from './batch' | ||
export { canUseEventBridge, getEventBridge, BrowserWindowWithEventBridge } from './eventBridge' | ||
export { startBatchWithReplica } from './startBatchWithReplica' |
import type { Configuration, EndpointBuilder } from '../domain/configuration' | ||
import type { Context } from '../tools/context' | ||
import { Batch } from './batch' | ||
import { HttpRequest } from './httpRequest' | ||
import { createHttpRequest } from './httpRequest' | ||
@@ -19,3 +19,3 @@ export function startBatchWithReplica<T extends Context>( | ||
return new Batch( | ||
new HttpRequest(endpointBuilder, configuration.batchBytesLimit), | ||
createHttpRequest(endpointBuilder, configuration.batchBytesLimit), | ||
configuration.batchMessagesLimit, | ||
@@ -22,0 +22,0 @@ configuration.batchBytesLimit, |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
850150
16888
2