@newrelic/browser-agent 1.276.0 → 1.278.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/dist/cjs/common/aggregate/event-aggregator.js +1 -1
- package/dist/cjs/common/config/init.js +1 -10
- package/dist/cjs/common/config/runtime.js +2 -1
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/harvest/harvester.js +255 -0
- package/dist/cjs/common/harvest/types.js +5 -21
- package/dist/cjs/features/ajax/aggregate/index.js +2 -11
- package/dist/cjs/features/generic_events/aggregate/index.js +17 -13
- package/dist/cjs/features/generic_events/constants.js +2 -1
- package/dist/cjs/features/jserrors/aggregate/index.js +3 -14
- package/dist/cjs/features/logging/aggregate/index.js +4 -12
- package/dist/cjs/features/metrics/aggregate/index.js +7 -15
- package/dist/cjs/features/page_view_event/aggregate/index.js +46 -48
- package/dist/cjs/features/page_view_timing/aggregate/index.js +0 -9
- package/dist/cjs/features/session_replay/aggregate/index.js +21 -43
- package/dist/cjs/features/session_replay/instrument/index.js +2 -1
- package/dist/cjs/features/session_replay/shared/recorder.js +6 -6
- package/dist/cjs/features/session_trace/aggregate/index.js +9 -24
- package/dist/cjs/features/session_trace/aggregate/trace/storage.js +8 -2
- package/dist/cjs/features/soft_navigations/aggregate/index.js +31 -21
- package/dist/cjs/features/soft_navigations/aggregate/initial-page-load-interaction.js +2 -1
- package/dist/cjs/features/soft_navigations/aggregate/interaction.js +12 -12
- package/dist/cjs/features/soft_navigations/constants.js +5 -2
- package/dist/cjs/features/spa/aggregate/index.js +7 -10
- package/dist/cjs/features/utils/aggregate-base.js +66 -27
- package/dist/cjs/features/utils/event-buffer.js +0 -1
- package/dist/cjs/features/utils/event-store-manager.js +109 -0
- package/dist/cjs/features/utils/instrument-base.js +1 -10
- package/dist/cjs/loaders/api/api-methods.js +1 -1
- package/dist/cjs/loaders/api/api.js +2 -1
- package/dist/cjs/loaders/features/features.js +16 -10
- package/dist/cjs/loaders/micro-agent-base.js +10 -0
- package/dist/cjs/loaders/micro-agent.js +1 -0
- package/dist/esm/common/aggregate/event-aggregator.js +1 -1
- package/dist/esm/common/config/init.js +1 -10
- package/dist/esm/common/config/runtime.js +2 -1
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/harvest/harvester.js +249 -0
- package/dist/esm/common/harvest/types.js +5 -21
- package/dist/esm/features/ajax/aggregate/index.js +3 -12
- package/dist/esm/features/generic_events/aggregate/index.js +18 -14
- package/dist/esm/features/generic_events/constants.js +1 -0
- package/dist/esm/features/jserrors/aggregate/index.js +4 -15
- package/dist/esm/features/logging/aggregate/index.js +4 -12
- package/dist/esm/features/metrics/aggregate/index.js +7 -15
- package/dist/esm/features/page_view_event/aggregate/index.js +46 -48
- package/dist/esm/features/page_view_timing/aggregate/index.js +1 -10
- package/dist/esm/features/session_replay/aggregate/index.js +22 -44
- package/dist/esm/features/session_replay/instrument/index.js +2 -1
- package/dist/esm/features/session_replay/shared/recorder.js +6 -6
- package/dist/esm/features/session_trace/aggregate/index.js +9 -24
- package/dist/esm/features/session_trace/aggregate/trace/storage.js +8 -2
- package/dist/esm/features/soft_navigations/aggregate/index.js +33 -23
- package/dist/esm/features/soft_navigations/aggregate/initial-page-load-interaction.js +2 -1
- package/dist/esm/features/soft_navigations/aggregate/interaction.js +13 -13
- package/dist/esm/features/soft_navigations/constants.js +4 -1
- package/dist/esm/features/spa/aggregate/index.js +8 -11
- package/dist/esm/features/utils/aggregate-base.js +66 -27
- package/dist/esm/features/utils/event-buffer.js +0 -1
- package/dist/esm/features/utils/event-store-manager.js +103 -0
- package/dist/esm/features/utils/instrument-base.js +1 -10
- package/dist/esm/loaders/api/api-methods.js +1 -1
- package/dist/esm/loaders/api/api.js +2 -1
- package/dist/esm/loaders/features/features.js +15 -9
- package/dist/esm/loaders/micro-agent-base.js +10 -0
- package/dist/esm/loaders/micro-agent.js +1 -0
- package/dist/types/common/aggregate/event-aggregator.d.ts +1 -1
- package/dist/types/common/aggregate/event-aggregator.d.ts.map +1 -1
- package/dist/types/common/config/init.d.ts.map +1 -1
- package/dist/types/common/config/runtime.d.ts.map +1 -1
- package/dist/types/common/harvest/harvester.d.ts +16 -0
- package/dist/types/common/harvest/harvester.d.ts.map +1 -0
- package/dist/types/common/harvest/types.d.ts +8 -45
- package/dist/types/common/harvest/types.d.ts.map +1 -1
- package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/aggregate/index.d.ts +1 -3
- package/dist/types/features/generic_events/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/generic_events/constants.d.ts +1 -0
- package/dist/types/features/generic_events/constants.d.ts.map +1 -1
- package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/logging/aggregate/index.d.ts +0 -3
- package/dist/types/features/logging/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/index.d.ts +1 -1
- package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/aggregate/index.d.ts +6 -2
- package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_timing/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +12 -15
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/index.d.ts +0 -5
- package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts +8 -5
- package/dist/types/features/session_trace/aggregate/trace/storage.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/index.d.ts +1 -0
- package/dist/types/features/soft_navigations/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/initial-page-load-interaction.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/aggregate/interaction.d.ts +3 -3
- package/dist/types/features/soft_navigations/aggregate/interaction.d.ts.map +1 -1
- package/dist/types/features/soft_navigations/constants.d.ts +1 -0
- package/dist/types/features/soft_navigations/constants.d.ts.map +1 -1
- package/dist/types/features/spa/aggregate/index.d.ts +0 -1
- package/dist/types/features/spa/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/utils/aggregate-base.d.ts +12 -7
- package/dist/types/features/utils/aggregate-base.d.ts.map +1 -1
- package/dist/types/features/utils/event-buffer.d.ts +1 -2
- package/dist/types/features/utils/event-buffer.d.ts.map +1 -1
- package/dist/types/features/utils/event-store-manager.d.ts +43 -0
- package/dist/types/features/utils/event-store-manager.d.ts.map +1 -0
- package/dist/types/features/utils/instrument-base.d.ts +0 -1
- package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
- package/dist/types/loaders/api/api.d.ts +1 -0
- package/dist/types/loaders/api/api.d.ts.map +1 -1
- package/dist/types/loaders/features/features.d.ts +15 -12
- package/dist/types/loaders/features/features.d.ts.map +1 -1
- package/dist/types/loaders/micro-agent-base.d.ts +7 -0
- package/dist/types/loaders/micro-agent-base.d.ts.map +1 -1
- package/dist/types/loaders/micro-agent.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/common/aggregate/event-aggregator.js +1 -1
- package/src/common/config/init.js +9 -10
- package/src/common/config/runtime.js +2 -1
- package/src/common/harvest/__mocks__/harvester.js +6 -0
- package/src/common/harvest/harvester.js +230 -0
- package/src/common/harvest/types.js +5 -21
- package/src/features/ajax/aggregate/index.js +3 -14
- package/src/features/generic_events/aggregate/index.js +20 -17
- package/src/features/generic_events/constants.js +2 -0
- package/src/features/jserrors/aggregate/index.js +4 -11
- package/src/features/logging/aggregate/index.js +4 -12
- package/src/features/metrics/aggregate/index.js +5 -12
- package/src/features/page_view_event/aggregate/index.js +38 -38
- package/src/features/page_view_timing/aggregate/index.js +1 -12
- package/src/features/session_replay/aggregate/index.js +19 -42
- package/src/features/session_replay/instrument/index.js +1 -1
- package/src/features/session_replay/shared/recorder.js +6 -6
- package/src/features/session_trace/aggregate/index.js +8 -25
- package/src/features/session_trace/aggregate/trace/storage.js +5 -2
- package/src/features/soft_navigations/aggregate/index.js +26 -23
- package/src/features/soft_navigations/aggregate/initial-page-load-interaction.js +2 -1
- package/src/features/soft_navigations/aggregate/interaction.js +13 -12
- package/src/features/soft_navigations/constants.js +3 -1
- package/src/features/spa/aggregate/index.js +8 -11
- package/src/features/utils/aggregate-base.js +59 -27
- package/src/features/utils/event-buffer.js +0 -1
- package/src/features/utils/event-store-manager.js +101 -0
- package/src/features/utils/instrument-base.js +2 -8
- package/src/loaders/api/api-methods.js +1 -1
- package/src/loaders/api/api.js +3 -1
- package/src/loaders/features/features.js +16 -9
- package/src/loaders/micro-agent-base.js +10 -0
- package/src/loaders/micro-agent.js +1 -0
- package/dist/cjs/common/harvest/harvest-scheduler.js +0 -168
- package/dist/cjs/common/harvest/harvest.js +0 -295
- package/dist/esm/common/harvest/harvest-scheduler.js +0 -160
- package/dist/esm/common/harvest/harvest.js +0 -286
- package/dist/types/common/harvest/harvest-scheduler.d.ts +0 -50
- package/dist/types/common/harvest/harvest-scheduler.d.ts.map +0 -1
- package/dist/types/common/harvest/harvest.d.ts +0 -65
- package/dist/types/common/harvest/harvest.d.ts.map +0 -1
- package/src/common/harvest/__mocks__/harvest.js +0 -13
- package/src/common/harvest/harvest-scheduler.js +0 -166
- package/src/common/harvest/harvest.js +0 -282
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2020 New Relic Corporation. All rights reserved.
|
|
3
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { obj as encodeObj, param as encodeParam } from '../url/encode';
|
|
7
|
-
import { stringify } from '../util/stringify';
|
|
8
|
-
import * as submitData from '../util/submit-data';
|
|
9
|
-
import { getLocation } from '../url/location';
|
|
10
|
-
import { getInfo } from '../config/info';
|
|
11
|
-
import { getConfigurationValue, getConfiguration } from '../config/init';
|
|
12
|
-
import { getRuntime } from '../config/runtime';
|
|
13
|
-
import { cleanURL } from '../url/clean-url';
|
|
14
|
-
import { eventListenerOpts } from '../event-listener/event-listener-opts';
|
|
15
|
-
import { SharedContext } from '../context/shared-context';
|
|
16
|
-
import { VERSION } from "../constants/env.npm";
|
|
17
|
-
import { isWorkerScope } from '../constants/runtime';
|
|
18
|
-
import { warn } from '../util/console';
|
|
19
|
-
import { now } from '../timing/now';
|
|
20
|
-
const warnings = {};
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @typedef {import('./types.js').NetworkSendSpec} NetworkSendSpec
|
|
24
|
-
* @typedef {import('./types.js').HarvestEndpointIdentifier} HarvestEndpointIdentifier
|
|
25
|
-
* @typedef {import('./types.js').HarvestPayload} HarvestPayload
|
|
26
|
-
* @typedef {import('./types.js').FeatureHarvestCallback} FeatureHarvestCallback
|
|
27
|
-
* @typedef {import('./types.js').FeatureHarvestCallbackOptions} FeatureHarvestCallbackOptions
|
|
28
|
-
*/
|
|
29
|
-
export class Harvest extends SharedContext {
|
|
30
|
-
constructor(parent) {
|
|
31
|
-
super(parent); // gets any allowed properties from the parent and stores them in `sharedContext`
|
|
32
|
-
|
|
33
|
-
this.tooManyRequestsDelay = getConfigurationValue(this.sharedContext.agentIdentifier, 'harvest.tooManyRequestsDelay') || 60;
|
|
34
|
-
this.obfuscator = getRuntime(this.sharedContext.agentIdentifier).obfuscator;
|
|
35
|
-
this._events = {};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Initiate a harvest from multiple sources. An event that corresponds to the endpoint
|
|
40
|
-
* name is emitted, which gives any listeners the opportunity to provide payload data.
|
|
41
|
-
* Note: Used by page_action
|
|
42
|
-
* @param {NetworkSendSpec} spec Specification for sending data
|
|
43
|
-
*/
|
|
44
|
-
sendX(spec = {}) {
|
|
45
|
-
const submitMethod = submitData.getSubmitMethod({
|
|
46
|
-
isFinalHarvest: spec.opts?.unload
|
|
47
|
-
});
|
|
48
|
-
const options = {
|
|
49
|
-
retry: !spec.opts?.unload && submitMethod === submitData.xhr,
|
|
50
|
-
isFinalHarvest: spec.opts?.unload === true
|
|
51
|
-
};
|
|
52
|
-
const payload = this.createPayload(spec.endpoint, options);
|
|
53
|
-
const caller = this._send.bind(this);
|
|
54
|
-
return caller({
|
|
55
|
-
...spec,
|
|
56
|
-
payload,
|
|
57
|
-
submitMethod
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Initiate a harvest call.
|
|
63
|
-
* @param {NetworkSendSpec} spec Specification for sending data
|
|
64
|
-
*/
|
|
65
|
-
send(spec = {}) {
|
|
66
|
-
const caller = this._send.bind(this);
|
|
67
|
-
return caller(spec);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Initiate a harvest call. Typically used by `sendX` and `send` methods or called directly
|
|
72
|
-
* for raw network calls.
|
|
73
|
-
* @param {NetworkSendSpec} param0 Specification for sending data
|
|
74
|
-
* @returns {boolean} True if the network call succeeded. For final harvest calls, the return
|
|
75
|
-
* value should not be relied upon because network calls will be made asynchronously.
|
|
76
|
-
*/
|
|
77
|
-
_send({
|
|
78
|
-
endpoint,
|
|
79
|
-
payload = {},
|
|
80
|
-
opts = {},
|
|
81
|
-
submitMethod,
|
|
82
|
-
cbFinished,
|
|
83
|
-
customUrl,
|
|
84
|
-
raw,
|
|
85
|
-
includeBaseParams = true
|
|
86
|
-
}) {
|
|
87
|
-
const info = getInfo(this.sharedContext.agentIdentifier);
|
|
88
|
-
if (!info.errorBeacon) return false;
|
|
89
|
-
const agentRuntime = getRuntime(this.sharedContext.agentIdentifier);
|
|
90
|
-
let {
|
|
91
|
-
body,
|
|
92
|
-
qs
|
|
93
|
-
} = this.cleanPayload(payload);
|
|
94
|
-
if (Object.keys(body).length === 0 && !opts?.sendEmptyBody) {
|
|
95
|
-
// no payload body? nothing to send, just run onfinish stuff and return
|
|
96
|
-
if (cbFinished) {
|
|
97
|
-
cbFinished({
|
|
98
|
-
sent: false
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
const init = getConfiguration(this.sharedContext.agentIdentifier);
|
|
104
|
-
const protocol = init.ssl === false ? 'http' : 'https';
|
|
105
|
-
const perceviedBeacon = init.proxy.beacon || info.errorBeacon;
|
|
106
|
-
const endpointURLPart = endpoint !== 'rum' ? "/".concat(endpoint) : '';
|
|
107
|
-
let url = "".concat(protocol, "://").concat(perceviedBeacon).concat(endpointURLPart, "/1/").concat(info.licenseKey);
|
|
108
|
-
if (customUrl) url = customUrl;
|
|
109
|
-
if (raw) url = "".concat(protocol, "://").concat(perceviedBeacon, "/").concat(endpoint);
|
|
110
|
-
const baseParams = !raw && includeBaseParams ? this.baseQueryString(qs, endpoint) : '';
|
|
111
|
-
let payloadParams = encodeObj(qs, agentRuntime.maxBytes);
|
|
112
|
-
if (!submitMethod) {
|
|
113
|
-
submitMethod = submitData.getSubmitMethod({
|
|
114
|
-
isFinalHarvest: opts.unload
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
if (baseParams === '' && payloadParams.startsWith('&')) {
|
|
118
|
-
payloadParams = payloadParams.substring(1);
|
|
119
|
-
}
|
|
120
|
-
const fullUrl = "".concat(url, "?").concat(baseParams).concat(payloadParams);
|
|
121
|
-
const gzip = !!qs?.attributes?.includes('gzip');
|
|
122
|
-
if (!gzip) {
|
|
123
|
-
if (endpoint !== 'events') body = stringify(body); // all features going to /events/ endpoint should already be serialized & stringified
|
|
124
|
-
/** Warn --once per endpoint-- if the agent tries to send large payloads */
|
|
125
|
-
if (body.length > 750000 && (warnings[endpoint] = (warnings?.[endpoint] || 0) + 1) === 1) warn(28, endpoint);
|
|
126
|
-
}
|
|
127
|
-
if (!body || body.length === 0 || body === '{}' || body === '[]') {
|
|
128
|
-
// If body is null, undefined, or an empty object or array, send an empty string instead
|
|
129
|
-
body = '';
|
|
130
|
-
}
|
|
131
|
-
const headers = [];
|
|
132
|
-
headers.push({
|
|
133
|
-
key: 'content-type',
|
|
134
|
-
value: 'text/plain'
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
/* Since workers don't support sendBeacon right now, they can only use XHR method.
|
|
138
|
-
Because they still do permit synch XHR, the idea is that at final harvest time (worker is closing),
|
|
139
|
-
we just make a BLOCKING request--trivial impact--with the remaining data as a temp fill-in for sendBeacon.
|
|
140
|
-
Following the removal of img-element method. */
|
|
141
|
-
let result = submitMethod({
|
|
142
|
-
url: fullUrl,
|
|
143
|
-
body,
|
|
144
|
-
sync: opts.unload && isWorkerScope,
|
|
145
|
-
headers
|
|
146
|
-
});
|
|
147
|
-
if (!opts.unload && cbFinished && submitMethod === submitData.xhr) {
|
|
148
|
-
const harvestScope = this;
|
|
149
|
-
result.addEventListener('loadend', function () {
|
|
150
|
-
// `this` refers to the XHR object in this scope, do not change this to a fat arrow
|
|
151
|
-
// status 0 refers to a local error, such as CORS or network failure, or a blocked request by the browser (e.g. adblocker)
|
|
152
|
-
const cbResult = {
|
|
153
|
-
sent: this.status !== 0,
|
|
154
|
-
status: this.status,
|
|
155
|
-
xhr: this,
|
|
156
|
-
fullUrl
|
|
157
|
-
};
|
|
158
|
-
if (this.status === 429) {
|
|
159
|
-
cbResult.retry = true;
|
|
160
|
-
cbResult.delay = harvestScope.tooManyRequestsDelay;
|
|
161
|
-
} else if (this.status === 408 || this.status === 500 || this.status === 503) {
|
|
162
|
-
cbResult.retry = true;
|
|
163
|
-
}
|
|
164
|
-
if (opts.needResponse) {
|
|
165
|
-
cbResult.responseText = this.responseText;
|
|
166
|
-
}
|
|
167
|
-
cbFinished(cbResult);
|
|
168
|
-
}, eventListenerOpts(false));
|
|
169
|
-
} else if (!opts.unload && cbFinished && submitMethod === submitData.xhrFetch) {
|
|
170
|
-
const harvestScope = this;
|
|
171
|
-
result.then(async function (response) {
|
|
172
|
-
const status = response.status;
|
|
173
|
-
const cbResult = {
|
|
174
|
-
sent: true,
|
|
175
|
-
status,
|
|
176
|
-
fullUrl,
|
|
177
|
-
fetchResponse: response
|
|
178
|
-
};
|
|
179
|
-
if (response.status === 429) {
|
|
180
|
-
cbResult.retry = true;
|
|
181
|
-
cbResult.delay = harvestScope.tooManyRequestsDelay;
|
|
182
|
-
} else if (status === 408 || status === 500 || status === 503) {
|
|
183
|
-
cbResult.retry = true;
|
|
184
|
-
}
|
|
185
|
-
if (opts.needResponse) {
|
|
186
|
-
cbResult.responseText = await response.text();
|
|
187
|
-
}
|
|
188
|
-
cbFinished(cbResult);
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
return result;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// The stuff that gets sent every time.
|
|
195
|
-
baseQueryString(qs, endpoint) {
|
|
196
|
-
const runtime = getRuntime(this.sharedContext.agentIdentifier);
|
|
197
|
-
const info = getInfo(this.sharedContext.agentIdentifier);
|
|
198
|
-
const ref = this.obfuscator.obfuscateString(cleanURL(getLocation()));
|
|
199
|
-
const hr = runtime?.session?.state.sessionReplayMode === 1 && endpoint !== 'jserrors';
|
|
200
|
-
const qps = ['a=' + info.applicationID, encodeParam('sa', info.sa ? '' + info.sa : ''), encodeParam('v', VERSION), transactionNameParam(info), encodeParam('ct', runtime.customTransaction), '&rst=' + now(), '&ck=0',
|
|
201
|
-
// ck param DEPRECATED - still expected by backend
|
|
202
|
-
'&s=' + (runtime.session?.state.value || '0'),
|
|
203
|
-
// the 0 id encaps all untrackable and default traffic
|
|
204
|
-
encodeParam('ref', ref), encodeParam('ptid', runtime.ptid ? '' + runtime.ptid : '')];
|
|
205
|
-
if (hr) qps.push(encodeParam('hr', '1', qs));
|
|
206
|
-
return qps.join('');
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Calls and accumulates data from registered harvesting functions based on
|
|
211
|
-
* the endpoint being harvested.
|
|
212
|
-
* @param {HarvestEndpointIdentifier} endpoint BAM endpoint identifier.
|
|
213
|
-
* @param {FeatureHarvestCallbackOptions} options Options to be passed to the
|
|
214
|
-
* feature harvest listener callback.
|
|
215
|
-
* @returns {HarvestPayload} Payload object to transmit to the bam endpoint.
|
|
216
|
-
*/
|
|
217
|
-
createPayload(endpoint, options) {
|
|
218
|
-
const listeners = this._events[endpoint];
|
|
219
|
-
const payload = {
|
|
220
|
-
body: {},
|
|
221
|
-
qs: {}
|
|
222
|
-
};
|
|
223
|
-
if (Array.isArray(listeners) && listeners.length > 0) {
|
|
224
|
-
for (let i = 0; i < listeners.length; i++) {
|
|
225
|
-
const singlePayload = listeners[i](options);
|
|
226
|
-
if (singlePayload) {
|
|
227
|
-
payload.body = {
|
|
228
|
-
...payload.body,
|
|
229
|
-
...(singlePayload.body || {})
|
|
230
|
-
};
|
|
231
|
-
payload.qs = {
|
|
232
|
-
...payload.qs,
|
|
233
|
-
...(singlePayload.qs || {})
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
return payload;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Cleans and returns a payload object containing a body and qs
|
|
243
|
-
* object with key/value pairs. KV pairs where the value is null,
|
|
244
|
-
* undefined, or an empty string are removed to save on transmission
|
|
245
|
-
* size.
|
|
246
|
-
* @param {HarvestPayload} payload Payload to be sent to the endpoint.
|
|
247
|
-
* @returns {HarvestPayload} Cleaned payload payload to be sent to the endpoint.
|
|
248
|
-
*/
|
|
249
|
-
cleanPayload(payload = {}) {
|
|
250
|
-
const clean = input => {
|
|
251
|
-
if (typeof Uint8Array !== 'undefined' && input instanceof Uint8Array || Array.isArray(input)) return input;
|
|
252
|
-
if (typeof input === 'string') return input.length > 0 ? input : null;
|
|
253
|
-
return Object.entries(input || {}).reduce((accumulator, [key, value]) => {
|
|
254
|
-
if (typeof value === 'number' || typeof value === 'string' && value.length > 0 || typeof value === 'object' && Object.keys(value || {}).length > 0) {
|
|
255
|
-
accumulator[key] = value;
|
|
256
|
-
}
|
|
257
|
-
return accumulator;
|
|
258
|
-
}, {});
|
|
259
|
-
};
|
|
260
|
-
return {
|
|
261
|
-
body: clean(payload.body),
|
|
262
|
-
qs: clean(payload.qs)
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Registers a function to be called when harvesting is triggered for a specific
|
|
268
|
-
* endpoint.
|
|
269
|
-
* @param {HarvestEndpointIdentifier} endpoint
|
|
270
|
-
* @param {FeatureHarvestCallback} listener
|
|
271
|
-
*/
|
|
272
|
-
on(endpoint, listener) {
|
|
273
|
-
if (!Array.isArray(this._events[endpoint])) {
|
|
274
|
-
this._events[endpoint] = [];
|
|
275
|
-
}
|
|
276
|
-
this._events[endpoint].push(listener);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Constructs the transaction name param for the beacon URL.
|
|
281
|
-
// Prefers the obfuscated transaction name over the plain text.
|
|
282
|
-
// Falls back to making up a name.
|
|
283
|
-
function transactionNameParam(info) {
|
|
284
|
-
if (info.transactionName) return encodeParam('to', info.transactionName);
|
|
285
|
-
return encodeParam('t', info.tNamePlain || 'Unnamed Transaction');
|
|
286
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Periodically invokes harvest calls and handles retries
|
|
3
|
-
*/
|
|
4
|
-
export class HarvestScheduler extends SharedContext {
|
|
5
|
-
/**
|
|
6
|
-
* Create a HarvestScheduler
|
|
7
|
-
* @param {string} endpoint - The base BAM endpoint name -- ex. 'events'
|
|
8
|
-
* @param {object} opts - The options used to configure the HarvestScheduler
|
|
9
|
-
* @param {Function} opts.onFinished - The callback to be fired when a harvest has finished
|
|
10
|
-
* @param {Function} opts.getPayload - A callback which can be triggered to return a payload for harvesting
|
|
11
|
-
* @param {number} opts.retryDelay - The number of seconds to wait before retrying after a network failure
|
|
12
|
-
* @param {boolean} opts.raw - Use a prefabricated payload shape as the harvest payload without the need for formatting
|
|
13
|
-
* @param {string} opts.customUrl - A custom url that falls outside of the shape of the standard BAM harvester url pattern. Will use directly instead of concatenating various pieces
|
|
14
|
-
* @param {*} parent - The parent object, whose state can be passed into SharedContext
|
|
15
|
-
*/
|
|
16
|
-
constructor(endpoint: string, opts: {
|
|
17
|
-
onFinished: Function;
|
|
18
|
-
getPayload: Function;
|
|
19
|
-
retryDelay: number;
|
|
20
|
-
raw: boolean;
|
|
21
|
-
customUrl: string;
|
|
22
|
-
}, parent: any);
|
|
23
|
-
endpoint: string;
|
|
24
|
-
opts: {
|
|
25
|
-
onFinished: Function;
|
|
26
|
-
getPayload: Function;
|
|
27
|
-
retryDelay: number;
|
|
28
|
-
raw: boolean;
|
|
29
|
-
customUrl: string;
|
|
30
|
-
};
|
|
31
|
-
started: boolean;
|
|
32
|
-
timeoutHandle: any;
|
|
33
|
-
aborted: boolean;
|
|
34
|
-
harvesting: boolean;
|
|
35
|
-
harvest: Harvest;
|
|
36
|
-
/**
|
|
37
|
-
* This function is only meant for the last outgoing harvest cycle of a page. It trickles down to using sendBeacon, which should not be used
|
|
38
|
-
* to send payloads while the page is still active, due to limitations on how much data can be buffered in the API at any one time.
|
|
39
|
-
*/
|
|
40
|
-
unload(): void;
|
|
41
|
-
startTimer(interval: any, initialDelay: any): void;
|
|
42
|
-
interval: any;
|
|
43
|
-
stopTimer(permanently?: boolean): void;
|
|
44
|
-
scheduleHarvest(delay: any, opts: any): void;
|
|
45
|
-
runHarvest(opts: any): false | undefined;
|
|
46
|
-
onHarvestFinished(opts: any, result: any): void;
|
|
47
|
-
}
|
|
48
|
-
import { SharedContext } from '../context/shared-context';
|
|
49
|
-
import { Harvest } from './harvest';
|
|
50
|
-
//# sourceMappingURL=harvest-scheduler.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"harvest-scheduler.d.ts","sourceRoot":"","sources":["../../../../src/common/harvest/harvest-scheduler.js"],"names":[],"mappings":"AAWA;;GAEG;AACH;IACE;;;;;;;;;;SAUK;IACL,sBATa,MAAM,QAEd;QAAuB,UAAU;QACV,UAAU;QACZ,UAAU,EAAvB,MAAM;QACQ,GAAG,EAAjB,OAAO;QACM,SAAS,EAAtB,MAAM;KACd,UAAQ,GAAC,EAoBb;IAhBC,iBAAwB;IACxB;;;oBARW,MAAM;aACN,OAAO;mBACP,MAAM;MAMK;IACtB,iBAAoB;IACpB,mBAAyB;IACzB,iBAAoB;IACpB,oBAAuB;IACvB,iBAA8C;IAYhD;;;OAGG;IACH,eAGC;IAED,mDAIC;IAHC,cAAwB;IAK1B,uCAMC;IAED,6CAUC;IAED,yCAgEC;IAED,gDAiBC;CACF;8BA/J6B,2BAA2B;wBACjC,WAAW"}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {import('./types.js').NetworkSendSpec} NetworkSendSpec
|
|
3
|
-
* @typedef {import('./types.js').HarvestEndpointIdentifier} HarvestEndpointIdentifier
|
|
4
|
-
* @typedef {import('./types.js').HarvestPayload} HarvestPayload
|
|
5
|
-
* @typedef {import('./types.js').FeatureHarvestCallback} FeatureHarvestCallback
|
|
6
|
-
* @typedef {import('./types.js').FeatureHarvestCallbackOptions} FeatureHarvestCallbackOptions
|
|
7
|
-
*/
|
|
8
|
-
export class Harvest extends SharedContext {
|
|
9
|
-
tooManyRequestsDelay: any;
|
|
10
|
-
obfuscator: any;
|
|
11
|
-
_events: {};
|
|
12
|
-
/**
|
|
13
|
-
* Initiate a harvest from multiple sources. An event that corresponds to the endpoint
|
|
14
|
-
* name is emitted, which gives any listeners the opportunity to provide payload data.
|
|
15
|
-
* Note: Used by page_action
|
|
16
|
-
* @param {NetworkSendSpec} spec Specification for sending data
|
|
17
|
-
*/
|
|
18
|
-
sendX(spec?: NetworkSendSpec): boolean;
|
|
19
|
-
/**
|
|
20
|
-
* Initiate a harvest call.
|
|
21
|
-
* @param {NetworkSendSpec} spec Specification for sending data
|
|
22
|
-
*/
|
|
23
|
-
send(spec?: NetworkSendSpec): boolean;
|
|
24
|
-
/**
|
|
25
|
-
* Initiate a harvest call. Typically used by `sendX` and `send` methods or called directly
|
|
26
|
-
* for raw network calls.
|
|
27
|
-
* @param {NetworkSendSpec} param0 Specification for sending data
|
|
28
|
-
* @returns {boolean} True if the network call succeeded. For final harvest calls, the return
|
|
29
|
-
* value should not be relied upon because network calls will be made asynchronously.
|
|
30
|
-
*/
|
|
31
|
-
_send({ endpoint, payload, opts, submitMethod, cbFinished, customUrl, raw, includeBaseParams }: NetworkSendSpec): boolean;
|
|
32
|
-
baseQueryString(qs: any, endpoint: any): string;
|
|
33
|
-
/**
|
|
34
|
-
* Calls and accumulates data from registered harvesting functions based on
|
|
35
|
-
* the endpoint being harvested.
|
|
36
|
-
* @param {HarvestEndpointIdentifier} endpoint BAM endpoint identifier.
|
|
37
|
-
* @param {FeatureHarvestCallbackOptions} options Options to be passed to the
|
|
38
|
-
* feature harvest listener callback.
|
|
39
|
-
* @returns {HarvestPayload} Payload object to transmit to the bam endpoint.
|
|
40
|
-
*/
|
|
41
|
-
createPayload(endpoint: HarvestEndpointIdentifier, options: FeatureHarvestCallbackOptions): HarvestPayload;
|
|
42
|
-
/**
|
|
43
|
-
* Cleans and returns a payload object containing a body and qs
|
|
44
|
-
* object with key/value pairs. KV pairs where the value is null,
|
|
45
|
-
* undefined, or an empty string are removed to save on transmission
|
|
46
|
-
* size.
|
|
47
|
-
* @param {HarvestPayload} payload Payload to be sent to the endpoint.
|
|
48
|
-
* @returns {HarvestPayload} Cleaned payload payload to be sent to the endpoint.
|
|
49
|
-
*/
|
|
50
|
-
cleanPayload(payload?: HarvestPayload): HarvestPayload;
|
|
51
|
-
/**
|
|
52
|
-
* Registers a function to be called when harvesting is triggered for a specific
|
|
53
|
-
* endpoint.
|
|
54
|
-
* @param {HarvestEndpointIdentifier} endpoint
|
|
55
|
-
* @param {FeatureHarvestCallback} listener
|
|
56
|
-
*/
|
|
57
|
-
on(endpoint: HarvestEndpointIdentifier, listener: FeatureHarvestCallback): void;
|
|
58
|
-
}
|
|
59
|
-
export type NetworkSendSpec = import("./types.js").NetworkSendSpec;
|
|
60
|
-
export type HarvestEndpointIdentifier = import("./types.js").HarvestEndpointIdentifier;
|
|
61
|
-
export type HarvestPayload = import("./types.js").HarvestPayload;
|
|
62
|
-
export type FeatureHarvestCallback = import("./types.js").FeatureHarvestCallback;
|
|
63
|
-
export type FeatureHarvestCallbackOptions = import("./types.js").FeatureHarvestCallbackOptions;
|
|
64
|
-
import { SharedContext } from '../context/shared-context';
|
|
65
|
-
//# sourceMappingURL=harvest.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"harvest.d.ts","sourceRoot":"","sources":["../../../../src/common/harvest/harvest.js"],"names":[],"mappings":"AAsBA;;;;;;GAMG;AACH;IAII,0BAA2H;IAC3H,gBAA2E;IAE3E,YAAiB;IAGnB;;;;;OAKG;IACH,aAFW,eAAe,WAWzB;IAED;;;OAGG;IACH,YAFW,eAAe,WAMzB;IAED;;;;;;OAMG;IACH,gGAJW,eAAe,GACb,OAAO,CAkGnB;IAGD,gDAqBC;IAED;;;;;;;OAOG;IACH,wBALW,yBAAyB,WACzB,6BAA6B,GAE3B,cAAc,CA2B1B;IAED;;;;;;;OAOG;IACH,uBAHW,cAAc,GACZ,cAAc,CAuB1B;IAED;;;;;OAKG;IACH,aAHW,yBAAyB,YACzB,sBAAsB,QAQhC;CACF;8BA1PY,OAAO,YAAY,EAAE,eAAe;wCACpC,OAAO,YAAY,EAAE,yBAAyB;6BAC9C,OAAO,YAAY,EAAE,cAAc;qCACnC,OAAO,YAAY,EAAE,sBAAsB;4CAC3C,OAAO,YAAY,EAAE,6BAA6B;8BAbjC,2BAA2B"}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export const Harvest = jest.fn(function () {
|
|
2
|
-
this.sharedContext = {
|
|
3
|
-
agentIdentifier: 'abcd'
|
|
4
|
-
}
|
|
5
|
-
this.sendX = jest.fn()
|
|
6
|
-
this.send = jest.fn()
|
|
7
|
-
this.obfuscateAndSend = jest.fn()
|
|
8
|
-
this._send = jest.fn()
|
|
9
|
-
this.baseQueryString = jest.fn()
|
|
10
|
-
this.createPayload = jest.fn()
|
|
11
|
-
this.cleanPayload = jest.fn()
|
|
12
|
-
this.on = jest.fn()
|
|
13
|
-
})
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2020 New Relic Corporation. All rights reserved.
|
|
3
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as submitData from '../util/submit-data'
|
|
7
|
-
import { SharedContext } from '../context/shared-context'
|
|
8
|
-
import { Harvest } from './harvest'
|
|
9
|
-
import { subscribeToEOL } from '../unload/eol'
|
|
10
|
-
import { SESSION_EVENTS } from '../session/constants'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Periodically invokes harvest calls and handles retries
|
|
14
|
-
*/
|
|
15
|
-
export class HarvestScheduler extends SharedContext {
|
|
16
|
-
/**
|
|
17
|
-
* Create a HarvestScheduler
|
|
18
|
-
* @param {string} endpoint - The base BAM endpoint name -- ex. 'events'
|
|
19
|
-
* @param {object} opts - The options used to configure the HarvestScheduler
|
|
20
|
-
* @param {Function} opts.onFinished - The callback to be fired when a harvest has finished
|
|
21
|
-
* @param {Function} opts.getPayload - A callback which can be triggered to return a payload for harvesting
|
|
22
|
-
* @param {number} opts.retryDelay - The number of seconds to wait before retrying after a network failure
|
|
23
|
-
* @param {boolean} opts.raw - Use a prefabricated payload shape as the harvest payload without the need for formatting
|
|
24
|
-
* @param {string} opts.customUrl - A custom url that falls outside of the shape of the standard BAM harvester url pattern. Will use directly instead of concatenating various pieces
|
|
25
|
-
* @param {*} parent - The parent object, whose state can be passed into SharedContext
|
|
26
|
-
*/
|
|
27
|
-
constructor (endpoint, opts, parent) {
|
|
28
|
-
super(parent) // gets any allowed properties from the parent and stores them in `sharedContext`
|
|
29
|
-
this.endpoint = endpoint
|
|
30
|
-
this.opts = opts || {}
|
|
31
|
-
this.started = false
|
|
32
|
-
this.timeoutHandle = null
|
|
33
|
-
this.aborted = false // this controls the per-interval and final harvests for the scheduler (currently per feature specific!)
|
|
34
|
-
this.harvesting = false
|
|
35
|
-
this.harvest = new Harvest(this.sharedContext)
|
|
36
|
-
|
|
37
|
-
// If a feature specifies stuff to be done on page unload, those are frontrunned (via capture phase) before ANY feature final harvests.
|
|
38
|
-
if (typeof this.opts.onUnload === 'function') subscribeToEOL(this.opts.onUnload, true)
|
|
39
|
-
subscribeToEOL(this.unload.bind(this)) // this should consist only of sending final harvest
|
|
40
|
-
|
|
41
|
-
/* Flush all buffered data if session resets and give up retries. This should be synchronous to ensure that the correct `session` value is sent.
|
|
42
|
-
Since session-reset generates a new session ID and the ID is grabbed at send-time, any delays or retries would cause the payload to be sent under
|
|
43
|
-
the wrong session ID. */
|
|
44
|
-
this.sharedContext?.ee.on(SESSION_EVENTS.RESET, () => this.runHarvest({ forceNoRetry: true }))
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* This function is only meant for the last outgoing harvest cycle of a page. It trickles down to using sendBeacon, which should not be used
|
|
49
|
-
* to send payloads while the page is still active, due to limitations on how much data can be buffered in the API at any one time.
|
|
50
|
-
*/
|
|
51
|
-
unload () {
|
|
52
|
-
if (this.aborted) return
|
|
53
|
-
this.runHarvest({ unload: true })
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
startTimer (interval, initialDelay) {
|
|
57
|
-
this.interval = interval
|
|
58
|
-
this.started = true
|
|
59
|
-
this.scheduleHarvest(initialDelay != null ? initialDelay : this.interval)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
stopTimer (permanently = false) {
|
|
63
|
-
this.aborted = permanently // stopping permanently is same as aborting, but this function also cleans up the setTimeout loop
|
|
64
|
-
this.started = false
|
|
65
|
-
if (this.timeoutHandle) {
|
|
66
|
-
clearTimeout(this.timeoutHandle)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
scheduleHarvest (delay, opts) {
|
|
71
|
-
if (this.timeoutHandle) return
|
|
72
|
-
|
|
73
|
-
if (delay == null) {
|
|
74
|
-
delay = this.interval
|
|
75
|
-
}
|
|
76
|
-
this.timeoutHandle = setTimeout(() => {
|
|
77
|
-
this.timeoutHandle = null
|
|
78
|
-
this.runHarvest(opts)
|
|
79
|
-
}, delay * 1000)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
runHarvest (opts) {
|
|
83
|
-
if (this.aborted) return
|
|
84
|
-
this.harvesting = true
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* This is executed immediately after harvest sends the data via XHR, or if there's nothing to send. Note that this excludes on unloading / sendBeacon.
|
|
88
|
-
* @param {Object} result
|
|
89
|
-
*/
|
|
90
|
-
const cbRanAfterSend = (result) => {
|
|
91
|
-
this.harvesting = false
|
|
92
|
-
if (opts?.forceNoRetry) result.retry = false // discard unsent data rather than re-queuing for next harvest attempt
|
|
93
|
-
this.onHarvestFinished(opts, result)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
let harvests = []
|
|
97
|
-
let submitMethod
|
|
98
|
-
let payload
|
|
99
|
-
|
|
100
|
-
if (this.opts.getPayload) {
|
|
101
|
-
// Ajax, PVT, Softnav, Logging, SR & ST features provide a single callback function to get data for harvesting
|
|
102
|
-
submitMethod = submitData.getSubmitMethod({ isFinalHarvest: opts?.unload })
|
|
103
|
-
if (!submitMethod) return false
|
|
104
|
-
|
|
105
|
-
const retry = !opts?.unload && submitMethod === submitData.xhr
|
|
106
|
-
payload = this.opts.getPayload({ retry, ...opts })
|
|
107
|
-
|
|
108
|
-
if (!payload) {
|
|
109
|
-
if (this.started) {
|
|
110
|
-
this.scheduleHarvest()
|
|
111
|
-
}
|
|
112
|
-
return
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
payload = Object.prototype.toString.call(payload) === '[object Array]' ? payload : [payload]
|
|
116
|
-
harvests.push(...payload)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/** sendX is used for features that do not supply a preformatted payload via "getPayload" */
|
|
120
|
-
let send = args => this.harvest.sendX(args)
|
|
121
|
-
if (harvests.length) {
|
|
122
|
-
/** _send is the underlying method for sending in the harvest, if sending raw we can bypass the other helpers completely which format the payloads */
|
|
123
|
-
if (this.opts.raw) send = args => this.harvest._send(args)
|
|
124
|
-
/** send is used to formated the payloads from "getPayload" and obfuscate before sending */
|
|
125
|
-
else send = args => this.harvest.send(args)
|
|
126
|
-
} else {
|
|
127
|
-
// force it to run at least once in sendX mode
|
|
128
|
-
harvests.push(undefined)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
harvests.forEach(payload => {
|
|
132
|
-
send({
|
|
133
|
-
endpoint: this.endpoint,
|
|
134
|
-
payload,
|
|
135
|
-
opts,
|
|
136
|
-
submitMethod,
|
|
137
|
-
cbFinished: cbRanAfterSend,
|
|
138
|
-
customUrl: this.opts.customUrl,
|
|
139
|
-
raw: this.opts.raw
|
|
140
|
-
})
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
if (this.started) {
|
|
144
|
-
this.scheduleHarvest()
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
onHarvestFinished (opts, result) {
|
|
149
|
-
if (this.opts.onFinished) {
|
|
150
|
-
this.opts.onFinished(result)
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (result.sent && result.retry) {
|
|
154
|
-
const delay = result.delay || this.opts.retryDelay
|
|
155
|
-
// reschedule next harvest if should be delayed longer
|
|
156
|
-
if (this.started && delay) {
|
|
157
|
-
clearTimeout(this.timeoutHandle)
|
|
158
|
-
this.timeoutHandle = null
|
|
159
|
-
this.scheduleHarvest(delay, opts)
|
|
160
|
-
} else if (!this.started && delay) {
|
|
161
|
-
// if not running on a timer, schedule a single retry
|
|
162
|
-
this.scheduleHarvest(delay, opts)
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|