dd-trace 6.13.0 → 6.14.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/LICENSE-3rdparty.csv +3 -1
- package/ci/init.js +8 -3
- package/ci/test-optimization-validation/ci-package-scripts.js +2 -2
- package/ci/test-optimization-validation/ci-remediation.js +6 -6
- package/ci/test-optimization-validation/command-blocker.js +7 -5
- package/ci/test-optimization-validation/framework-adapters/cucumber.js +1 -1
- package/ci/test-optimization-validation/framework-adapters/vitest.js +3 -3
- package/ci/test-optimization-validation/package-check.js +1 -1
- package/ci/test-optimization-validation/report-writer.js +7 -5
- package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
- package/ci/vitest-no-worker-init-setup.mjs +13 -13
- package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
- package/package.json +13 -10
- package/packages/datadog-esbuild/index.js +3 -1
- package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
- package/packages/datadog-instrumentations/src/fs.js +24 -20
- package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
- package/packages/datadog-instrumentations/src/graphql.js +16 -16
- package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +265 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +13 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +251 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +11 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
- package/packages/datadog-instrumentations/src/jest.js +25 -20
- package/packages/datadog-instrumentations/src/knex.js +76 -0
- package/packages/datadog-instrumentations/src/mariadb.js +6 -0
- package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
- package/packages/datadog-instrumentations/src/mocha/main.js +25 -15
- package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
- package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
- package/packages/datadog-instrumentations/src/net.js +14 -17
- package/packages/datadog-instrumentations/src/next.js +212 -62
- package/packages/datadog-instrumentations/src/openai.js +42 -40
- package/packages/datadog-instrumentations/src/pg.js +15 -0
- package/packages/datadog-instrumentations/src/playwright.js +109 -13
- package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
- package/packages/datadog-instrumentations/src/sequelize.js +94 -2
- package/packages/datadog-instrumentations/src/undici.js +53 -4
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +37 -3
- package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
- package/packages/datadog-instrumentations/src/vitest-worker.js +71 -14
- package/packages/datadog-instrumentations/src/webdriverio.js +965 -6
- package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
- package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
- package/packages/datadog-plugin-amqp10/src/util.js +2 -1
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
- package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
- package/packages/datadog-plugin-cucumber/src/index.js +0 -2
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
- package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
- package/packages/datadog-plugin-graphql/src/execute.js +998 -196
- package/packages/datadog-plugin-graphql/src/index.js +27 -7
- package/packages/datadog-plugin-graphql/src/jit.js +136 -0
- package/packages/datadog-plugin-graphql/src/request.js +18 -27
- package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
- package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
- package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
- package/packages/datadog-plugin-graphql/src/utils.js +208 -120
- package/packages/datadog-plugin-graphql/src/validate.js +10 -23
- package/packages/datadog-plugin-jest/src/index.js +0 -6
- package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
- package/packages/datadog-plugin-mocha/src/index.js +403 -75
- package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
- package/packages/datadog-plugin-mysql/src/index.js +41 -15
- package/packages/datadog-plugin-net/src/tcp.js +3 -1
- package/packages/datadog-plugin-next/src/index.js +63 -29
- package/packages/datadog-plugin-playwright/src/index.js +167 -27
- package/packages/datadog-plugin-undici/src/index.js +27 -41
- package/packages/datadog-plugin-vitest/src/index.js +6 -12
- package/packages/dd-trace/src/aiguard/evaluation.js +2 -1
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +8 -1
- package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
- package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
- package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
- package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
- package/packages/dd-trace/src/appsec/graphql.js +6 -2
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
- package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +11 -36
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/lambda.js +44 -26
- package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +5 -173
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +394 -225
- package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
- package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
- package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
- package/packages/dd-trace/src/ci-visibility/final-flush.js +2 -1
- package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
- package/packages/dd-trace/src/ci-visibility/requests/video-request.js +176 -0
- package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
- package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
- package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
- package/packages/dd-trace/src/config/index.js +73 -13
- package/packages/dd-trace/src/config/parsers.js +11 -1
- package/packages/dd-trace/src/config/stable.js +3 -3
- package/packages/dd-trace/src/config/supported-configurations.json +20 -2
- package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
- package/packages/dd-trace/src/debugger/config.js +12 -1
- package/packages/dd-trace/src/debugger/constants.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/request-options.js +33 -0
- package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -40
- package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
- package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
- package/packages/dd-trace/src/debugger/index.js +23 -3
- package/packages/dd-trace/src/dogstatsd.js +510 -76
- package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
- package/packages/dd-trace/src/encode/0.4.js +5 -5
- package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
- package/packages/dd-trace/src/evp_proxy/direct.js +27 -23
- package/packages/dd-trace/src/exporters/agent/index.js +2 -1
- package/packages/dd-trace/src/exporters/agent/writer.js +24 -7
- package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
- package/packages/dd-trace/src/exporters/agentless/writer.js +100 -118
- package/packages/dd-trace/src/exporters/common/agents.js +28 -6
- package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
- package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
- package/packages/dd-trace/src/exporters/common/request.js +31 -11
- package/packages/dd-trace/src/exporters/common/url.js +40 -1
- package/packages/dd-trace/src/exporters/common/writer.js +9 -7
- package/packages/dd-trace/src/flush.js +6 -3
- package/packages/dd-trace/src/id.js +14 -0
- package/packages/dd-trace/src/index.js +5 -2
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +2 -1
- package/packages/dd-trace/src/llmobs/index.js +32 -32
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
- package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
- package/packages/dd-trace/src/llmobs/util.js +0 -33
- package/packages/dd-trace/src/llmobs/writers/base.js +49 -32
- package/packages/dd-trace/src/log/index.js +7 -1
- package/packages/dd-trace/src/{ci-visibility/log-submission → log-submission}/log-submission-plugin.js +48 -19
- package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
- package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
- package/packages/dd-trace/src/opentelemetry/logs/index.js +5 -24
- package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +15 -22
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +14 -4
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +10 -0
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
- package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
- package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
- package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +93 -18
- package/packages/dd-trace/src/payload-tagging/index.js +2 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
- package/packages/dd-trace/src/plugins/tracing.js +12 -10
- package/packages/dd-trace/src/plugins/util/ci.js +3 -1
- package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
- package/packages/dd-trace/src/plugins/util/test.js +83 -20
- package/packages/dd-trace/src/plugins/util/url.js +2 -2
- package/packages/dd-trace/src/plugins/util/web.js +4 -9
- package/packages/dd-trace/src/profiling/config.js +1 -0
- package/packages/dd-trace/src/profiling/oom.js +7 -2
- package/packages/dd-trace/src/profiling/profiler.js +23 -13
- package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
- package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
- package/packages/dd-trace/src/proxy.js +75 -5
- package/packages/dd-trace/src/remote_config/index.js +319 -101
- package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
- package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
- package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -1
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -1
- package/packages/dd-trace/src/sampling_rule.js +36 -9
- package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +4 -8
- package/packages/dd-trace/src/serverless/vercel.js +53 -20
- package/packages/dd-trace/src/serverless.js +10 -3
- package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
- package/packages/dd-trace/src/span_stats.js +24 -28
- package/packages/dd-trace/src/startup-log.js +3 -2
- package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
- package/packages/dd-trace/src/telemetry/send-data.js +18 -16
- package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
- package/packages/dd-trace/src/tracer.js +4 -5
- package/packages/dd-trace/src/tracer_metadata.js +11 -3
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
- package/packages/dd-trace/src/encode/agentless-json.js +0 -210
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { hostname: getHostname } = require('node:os')
|
|
4
|
+
|
|
5
|
+
const { channel } = require('dc-polyfill')
|
|
6
|
+
|
|
3
7
|
const uuid = require('../../../../vendor/dist/crypto-randomuuid')
|
|
4
8
|
const tracerVersion = require('../../../../package.json').version
|
|
5
9
|
const request = require('../exporters/common/request')
|
|
10
|
+
const { createSiteUrl } = require('../exporters/common/url')
|
|
6
11
|
const log = require('../log')
|
|
7
12
|
const { getExtraServices } = require('../service-naming/extra-services')
|
|
8
13
|
const getGitMetadata = require('../git_metadata')
|
|
@@ -10,17 +15,39 @@ const { GIT_REPOSITORY_URL, GIT_COMMIT_SHA } = require('../plugins/util/tags')
|
|
|
10
15
|
const tagger = require('../tagger')
|
|
11
16
|
const processTags = require('../process-tags')
|
|
12
17
|
const Scheduler = require('./scheduler')
|
|
18
|
+
const capabilities = require('./capabilities')
|
|
13
19
|
const { UNACKNOWLEDGED, ACKNOWLEDGED, ERROR } = require('./apply_states')
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {object} RemoteConfigChange
|
|
23
|
+
* @property {'add' | 'update' | 'remove'} kind
|
|
24
|
+
* @property {string} path
|
|
25
|
+
* @property {string} product
|
|
26
|
+
* @property {string} configId
|
|
27
|
+
* @property {number} version
|
|
28
|
+
* @property {string} [contents]
|
|
29
|
+
* @property {number} [length]
|
|
30
|
+
* @property {Record<string, string>} [hashes]
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
let clientId = uuid()
|
|
34
|
+
/** @type {{ id: string, client_tracer: { runtime_id: string, tags: string[] } } | undefined} */
|
|
35
|
+
let client
|
|
36
|
+
|
|
37
|
+
channel('datadog:identity:update').subscribe(refreshIdentity)
|
|
16
38
|
|
|
17
39
|
const DEFAULT_CAPABILITY = Buffer.alloc(1).toString('base64') // 0x00
|
|
40
|
+
const AGENTLESS_REQUEST_TIMEOUT_MS = 5000
|
|
41
|
+
const capabilityEntries = Object.entries(capabilities)
|
|
18
42
|
|
|
19
43
|
const kSupportsAckCallback = Symbol('kSupportsAckCallback')
|
|
20
44
|
|
|
21
45
|
// There MUST NOT exist separate instances of RC clients in a tracer making separate ClientGetConfigsRequest
|
|
22
46
|
// with their own separated Client.ClientState.
|
|
23
47
|
class RemoteConfig {
|
|
48
|
+
#agentless
|
|
49
|
+
/** @type {import('@datadog/libdatadog/remote-config').RemoteConfigFetcher | undefined} */
|
|
50
|
+
#fetcher
|
|
24
51
|
#handlers = new Map()
|
|
25
52
|
#products = new Set()
|
|
26
53
|
#batchHandlers = new Map()
|
|
@@ -31,6 +58,7 @@ class RemoteConfig {
|
|
|
31
58
|
constructor (config) {
|
|
32
59
|
const pollInterval = Math.floor(config.remoteConfig.pollInterval * 1000)
|
|
33
60
|
|
|
61
|
+
this.#agentless = config.DD_AGENTLESS_ENABLED
|
|
34
62
|
this.url = config.url
|
|
35
63
|
|
|
36
64
|
tagger.add(config.tags, {
|
|
@@ -38,13 +66,7 @@ class RemoteConfig {
|
|
|
38
66
|
})
|
|
39
67
|
|
|
40
68
|
const { commitSHA, repositoryUrl } = getGitMetadata(config)
|
|
41
|
-
const
|
|
42
|
-
? {
|
|
43
|
-
...config.tags,
|
|
44
|
-
[GIT_REPOSITORY_URL]: repositoryUrl,
|
|
45
|
-
[GIT_COMMIT_SHA]: commitSHA,
|
|
46
|
-
}
|
|
47
|
-
: config.tags
|
|
69
|
+
const tracerTags = getTagsString(config, repositoryUrl, commitSHA)
|
|
48
70
|
|
|
49
71
|
const appliedConfigs = this.appliedConfigs = new Map()
|
|
50
72
|
|
|
@@ -84,13 +106,43 @@ class RemoteConfig {
|
|
|
84
106
|
env: config.env,
|
|
85
107
|
app_version: config.version,
|
|
86
108
|
extra_services: /** @type {string[]} */ ([]),
|
|
87
|
-
tags:
|
|
109
|
+
tags: tracerTags,
|
|
88
110
|
[processTags.REMOTE_CONFIG_FIELD_NAME]: processTags.tagsArray,
|
|
89
111
|
},
|
|
90
112
|
capabilities: DEFAULT_CAPABILITY, // updated by `updateCapabilities()`
|
|
91
113
|
},
|
|
92
114
|
cached_target_files: /** @type {RcCachedTargetFile[]} */ ([]), // updated by `parseConfig()`
|
|
93
115
|
}
|
|
116
|
+
|
|
117
|
+
client = this.state.client
|
|
118
|
+
|
|
119
|
+
if (this.#agentless) {
|
|
120
|
+
if (config.DD_API_KEY === undefined) {
|
|
121
|
+
log.error('[RC] DD_API_KEY is required for agentless Remote Config; Remote Config is disabled')
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
const siteUrl = createSiteUrl(config.site)
|
|
125
|
+
if (siteUrl === undefined) {
|
|
126
|
+
log.error('[RC] Invalid DD_SITE for agentless Remote Config: %s; Remote Config is disabled', config.site)
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.#fetcher = createAgentlessFetcher({
|
|
131
|
+
clientId,
|
|
132
|
+
runtimeId: config.tags['runtime-id'],
|
|
133
|
+
service: config.service,
|
|
134
|
+
env: config.env ?? '',
|
|
135
|
+
appVersion: config.version ?? '',
|
|
136
|
+
tags: tracerTags,
|
|
137
|
+
processTags: processTags.tagsArray,
|
|
138
|
+
language: 'node',
|
|
139
|
+
tracerVersion,
|
|
140
|
+
url: siteUrl.origin,
|
|
141
|
+
timeoutMs: AGENTLESS_REQUEST_TIMEOUT_MS,
|
|
142
|
+
apiKey: config.DD_API_KEY,
|
|
143
|
+
hostname: getHostname(),
|
|
144
|
+
})
|
|
145
|
+
}
|
|
94
146
|
}
|
|
95
147
|
|
|
96
148
|
/**
|
|
@@ -99,20 +151,15 @@ class RemoteConfig {
|
|
|
99
151
|
*/
|
|
100
152
|
updateCapabilities (mask, value) {
|
|
101
153
|
const hex = Buffer.from(this.state.client.capabilities, 'base64').toString('hex')
|
|
154
|
+
let capabilities = BigInt(`0x${hex}`)
|
|
102
155
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (value) {
|
|
106
|
-
num |= mask
|
|
107
|
-
} else {
|
|
108
|
-
num &= ~mask
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
let str = num.toString(16)
|
|
156
|
+
capabilities = value ? capabilities | mask : capabilities & ~mask
|
|
157
|
+
let str = capabilities.toString(16)
|
|
112
158
|
|
|
113
159
|
if (str.length % 2) str = `0${str}`
|
|
114
160
|
|
|
115
161
|
this.state.client.capabilities = Buffer.from(str, 'hex').toString('base64')
|
|
162
|
+
this.#updateProductCapabilities()
|
|
116
163
|
}
|
|
117
164
|
|
|
118
165
|
/**
|
|
@@ -155,7 +202,7 @@ class RemoteConfig {
|
|
|
155
202
|
this.#products.add(product)
|
|
156
203
|
}
|
|
157
204
|
this.updateProducts()
|
|
158
|
-
if (!hadProducts && this.#products.size > 0) {
|
|
205
|
+
if ((!this.#agentless || this.#fetcher) && !hadProducts && this.#products.size > 0) {
|
|
159
206
|
this.scheduler.start()
|
|
160
207
|
}
|
|
161
208
|
}
|
|
@@ -181,6 +228,25 @@ class RemoteConfig {
|
|
|
181
228
|
|
|
182
229
|
updateProducts () {
|
|
183
230
|
this.state.client.products = [...this.#products]
|
|
231
|
+
this.#updateProductCapabilities()
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Updates the native client with the current products and capabilities.
|
|
236
|
+
*
|
|
237
|
+
* @returns {void}
|
|
238
|
+
*/
|
|
239
|
+
#updateProductCapabilities () {
|
|
240
|
+
const fetcher = this.#fetcher
|
|
241
|
+
if (fetcher === undefined) return
|
|
242
|
+
|
|
243
|
+
const unrecognizedNames = fetcher.setProductCapabilities(
|
|
244
|
+
this.state.client.products,
|
|
245
|
+
decodeCapabilities(this.state.client.capabilities)
|
|
246
|
+
)
|
|
247
|
+
if (unrecognizedNames.length !== 0) {
|
|
248
|
+
log.error('[RC] Unrecognized remote config products or capabilities: %s', unrecognizedNames.join(', '))
|
|
249
|
+
}
|
|
184
250
|
}
|
|
185
251
|
|
|
186
252
|
/**
|
|
@@ -214,6 +280,11 @@ class RemoteConfig {
|
|
|
214
280
|
}
|
|
215
281
|
|
|
216
282
|
poll (cb) {
|
|
283
|
+
if (this.#agentless) {
|
|
284
|
+
this.#pollAgentless(cb)
|
|
285
|
+
return
|
|
286
|
+
}
|
|
287
|
+
|
|
217
288
|
const options = {
|
|
218
289
|
url: this.url,
|
|
219
290
|
method: 'POST',
|
|
@@ -253,6 +324,111 @@ class RemoteConfig {
|
|
|
253
324
|
})
|
|
254
325
|
}
|
|
255
326
|
|
|
327
|
+
/**
|
|
328
|
+
* @param {() => void} cb
|
|
329
|
+
*/
|
|
330
|
+
#pollAgentless (cb) {
|
|
331
|
+
const fetcher = /** @type {import('@datadog/libdatadog/remote-config').RemoteConfigFetcher} */ (this.#fetcher)
|
|
332
|
+
|
|
333
|
+
fetcher.setExtraServices(getExtraServices())
|
|
334
|
+
|
|
335
|
+
fetcher.fetchChanges().then(
|
|
336
|
+
(changes) => {
|
|
337
|
+
if (changes.length !== 0) this.#applyChanges(changes)
|
|
338
|
+
cb()
|
|
339
|
+
},
|
|
340
|
+
(error) => {
|
|
341
|
+
log.errorWithoutTelemetry('[RC] Error in request', error)
|
|
342
|
+
cb()
|
|
343
|
+
}
|
|
344
|
+
)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* @param {RemoteConfigChange[]} changes
|
|
349
|
+
*/
|
|
350
|
+
#applyChanges (changes) {
|
|
351
|
+
const toUnapply = /** @type {RcConfigState[]} */ ([])
|
|
352
|
+
const toApply = /** @type {RcConfigState[]} */ ([])
|
|
353
|
+
const toModify = /** @type {RcConfigState[]} */ ([])
|
|
354
|
+
const transactionByPath = new Map()
|
|
355
|
+
const transactionOutcomes = new Map()
|
|
356
|
+
|
|
357
|
+
for (const change of changes) {
|
|
358
|
+
const { path } = change
|
|
359
|
+
if (transactionByPath.has(path)) continue
|
|
360
|
+
|
|
361
|
+
const current = this.appliedConfigs.get(path)
|
|
362
|
+
if (change.kind === 'remove') {
|
|
363
|
+
if (current === undefined) continue
|
|
364
|
+
toUnapply.push(current)
|
|
365
|
+
transactionByPath.set(path, current)
|
|
366
|
+
continue
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
let file
|
|
370
|
+
try {
|
|
371
|
+
file = change.contents === '' ? null : JSON.parse(change.contents)
|
|
372
|
+
} catch (error) {
|
|
373
|
+
if (this.#fetcher === undefined) throw error
|
|
374
|
+
|
|
375
|
+
log.error('[RC] Could not parse the config file at path %s', path, error)
|
|
376
|
+
this.#fetcher.setConfigState(path, ERROR, error.toString())
|
|
377
|
+
continue
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const config = /** @type {RcConfigState} */ ({
|
|
381
|
+
path,
|
|
382
|
+
product: change.product,
|
|
383
|
+
id: change.configId,
|
|
384
|
+
version: change.version,
|
|
385
|
+
apply_state: UNACKNOWLEDGED,
|
|
386
|
+
apply_error: '',
|
|
387
|
+
file,
|
|
388
|
+
})
|
|
389
|
+
if (change.hashes !== undefined) {
|
|
390
|
+
config.length = change.length
|
|
391
|
+
config.hashes = change.hashes
|
|
392
|
+
}
|
|
393
|
+
transactionByPath.set(path, config)
|
|
394
|
+
|
|
395
|
+
if (change.kind === 'update' && current !== undefined) {
|
|
396
|
+
toModify.push(config)
|
|
397
|
+
} else {
|
|
398
|
+
toApply.push(config)
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (transactionByPath.size === 0) return
|
|
403
|
+
|
|
404
|
+
const transaction = createUpdateTransaction(
|
|
405
|
+
{ toUnapply, toApply, toModify },
|
|
406
|
+
transactionOutcomes
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
if (this.#batchHandlers.size) {
|
|
410
|
+
for (const [handler, products] of this.#batchHandlers) {
|
|
411
|
+
const transactionView = filterTransactionByProducts(transaction, products)
|
|
412
|
+
if (transactionView.toUnapply.length || transactionView.toApply.length || transactionView.toModify.length) {
|
|
413
|
+
handler(transactionView)
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
for (const [path, outcome] of transactionOutcomes) {
|
|
419
|
+
const item = transactionByPath.get(path)
|
|
420
|
+
if (item !== undefined) {
|
|
421
|
+
item.apply_state = outcome.state
|
|
422
|
+
item.apply_error = outcome.error
|
|
423
|
+
this.#fetcher?.setConfigState(path, outcome.state, outcome.error)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
this.dispatch(toUnapply, 'unapply', transactionOutcomes)
|
|
428
|
+
this.dispatch(toApply, 'apply', transactionOutcomes)
|
|
429
|
+
this.dispatch(toModify, 'modify', transactionOutcomes)
|
|
430
|
+
}
|
|
431
|
+
|
|
256
432
|
// `client_configs` is the list of config paths to have applied
|
|
257
433
|
// `targets` is the signed index with metadata for config files
|
|
258
434
|
// `target_files` is the list of config files containing the actual config data
|
|
@@ -261,17 +437,17 @@ class RemoteConfig {
|
|
|
261
437
|
targets,
|
|
262
438
|
target_files: targetFiles = [],
|
|
263
439
|
}) {
|
|
264
|
-
const
|
|
265
|
-
const toApply = /** @type {RcConfigState[]} */ ([])
|
|
266
|
-
const toModify = /** @type {RcConfigState[]} */ ([])
|
|
267
|
-
const transactionByPath = new Map()
|
|
268
|
-
const transactionHandledPaths = new Set()
|
|
269
|
-
const transactionOutcomes = new Map()
|
|
440
|
+
const changes = /** @type {RemoteConfigChange[]} */ ([])
|
|
270
441
|
|
|
271
442
|
for (const appliedConfig of this.appliedConfigs.values()) {
|
|
272
443
|
if (!clientConfigs.includes(appliedConfig.path)) {
|
|
273
|
-
|
|
274
|
-
|
|
444
|
+
changes.push({
|
|
445
|
+
kind: 'remove',
|
|
446
|
+
path: appliedConfig.path,
|
|
447
|
+
product: appliedConfig.product,
|
|
448
|
+
configId: appliedConfig.id,
|
|
449
|
+
version: appliedConfig.version,
|
|
450
|
+
})
|
|
275
451
|
}
|
|
276
452
|
}
|
|
277
453
|
|
|
@@ -284,15 +460,7 @@ class RemoteConfig {
|
|
|
284
460
|
|
|
285
461
|
const current = this.appliedConfigs.get(path)
|
|
286
462
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (current) {
|
|
290
|
-
if (current.hashes.sha256 === meta.hashes.sha256) continue
|
|
291
|
-
|
|
292
|
-
toModify.push(newConf)
|
|
293
|
-
} else {
|
|
294
|
-
toApply.push(newConf)
|
|
295
|
-
}
|
|
463
|
+
if (current?.hashes.sha256 === meta.hashes.sha256) continue
|
|
296
464
|
|
|
297
465
|
const file = targetFiles.find(file => file.path === path)
|
|
298
466
|
if (!file) throw new Error(`Unable to find file for path ${path}`)
|
|
@@ -305,45 +473,21 @@ class RemoteConfig {
|
|
|
305
473
|
|
|
306
474
|
const { product, id } = parseConfigPath(path)
|
|
307
475
|
|
|
308
|
-
|
|
476
|
+
changes.push({
|
|
477
|
+
kind: current === undefined ? 'add' : 'update',
|
|
309
478
|
path,
|
|
310
479
|
product,
|
|
311
|
-
id,
|
|
480
|
+
configId: id,
|
|
312
481
|
version: meta.custom.v,
|
|
313
|
-
|
|
314
|
-
apply_error: '',
|
|
482
|
+
contents: Buffer.from(file.raw, 'base64').toString(),
|
|
315
483
|
length: meta.length,
|
|
316
484
|
hashes: meta.hashes,
|
|
317
|
-
file: fromBase64JSON(file.raw),
|
|
318
485
|
})
|
|
319
|
-
transactionByPath.set(path, newConf)
|
|
320
486
|
}
|
|
321
|
-
|
|
322
|
-
this.state.client.state.targets_version = targets.signed.version
|
|
323
|
-
this.state.client.state.backend_client_state = targets.signed.custom.opaque_backend_state
|
|
324
487
|
}
|
|
325
488
|
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
{ toUnapply, toApply, toModify },
|
|
329
|
-
transactionHandledPaths,
|
|
330
|
-
transactionOutcomes
|
|
331
|
-
)
|
|
332
|
-
|
|
333
|
-
if (this.#batchHandlers.size) {
|
|
334
|
-
for (const [handler, products] of this.#batchHandlers) {
|
|
335
|
-
const transactionView = filterTransactionByProducts(transaction, products)
|
|
336
|
-
if (transactionView.toUnapply.length || transactionView.toApply.length || transactionView.toModify.length) {
|
|
337
|
-
handler(transactionView)
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
applyOutcomes(transactionByPath, transactionOutcomes)
|
|
343
|
-
|
|
344
|
-
this.dispatch(toUnapply, 'unapply', transactionHandledPaths)
|
|
345
|
-
this.dispatch(toApply, 'apply', transactionHandledPaths)
|
|
346
|
-
this.dispatch(toModify, 'modify', transactionHandledPaths)
|
|
489
|
+
if (changes.length !== 0) {
|
|
490
|
+
this.#applyChanges(changes)
|
|
347
491
|
|
|
348
492
|
this.state.cached_target_files = /** @type {RcCachedTargetFile[]} */ ([])
|
|
349
493
|
|
|
@@ -359,6 +503,11 @@ class RemoteConfig {
|
|
|
359
503
|
})
|
|
360
504
|
}
|
|
361
505
|
}
|
|
506
|
+
|
|
507
|
+
if (targets) {
|
|
508
|
+
this.state.client.state.targets_version = targets.signed.version
|
|
509
|
+
this.state.client.state.backend_client_state = targets.signed.custom.opaque_backend_state
|
|
510
|
+
}
|
|
362
511
|
}
|
|
363
512
|
|
|
364
513
|
/**
|
|
@@ -367,22 +516,41 @@ class RemoteConfig {
|
|
|
367
516
|
*
|
|
368
517
|
* @param {RcConfigState[]} list
|
|
369
518
|
* @param {'apply' | 'modify' | 'unapply'} action
|
|
370
|
-
* @param {
|
|
519
|
+
* @param {Map<string, {state: number, error: string}>} outcomes
|
|
371
520
|
*/
|
|
372
|
-
dispatch (list, action,
|
|
521
|
+
dispatch (list, action, outcomes) {
|
|
373
522
|
for (const item of list) {
|
|
374
|
-
if (
|
|
523
|
+
if (action !== 'unapply') {
|
|
524
|
+
this.appliedConfigs.set(item.path, item)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (!outcomes.has(item.path)) {
|
|
375
528
|
this.#callHandlerFor(action, item)
|
|
376
529
|
}
|
|
377
530
|
|
|
378
531
|
if (action === 'unapply') {
|
|
379
532
|
this.appliedConfigs.delete(item.path)
|
|
380
|
-
} else {
|
|
381
|
-
this
|
|
533
|
+
} else if (item.apply_state === UNACKNOWLEDGED) {
|
|
534
|
+
this.#fetcher?.setConfigState(item.path, UNACKNOWLEDGED, '')
|
|
382
535
|
}
|
|
383
536
|
}
|
|
384
537
|
}
|
|
385
538
|
|
|
539
|
+
/**
|
|
540
|
+
* @param {RcConfigState} item
|
|
541
|
+
* @param {'apply' | 'modify' | 'unapply'} action
|
|
542
|
+
* @param {number} applyState
|
|
543
|
+
* @param {string} applyError
|
|
544
|
+
*/
|
|
545
|
+
#setApplyState (item, action, applyState, applyError) {
|
|
546
|
+
const current = this.appliedConfigs.get(item.path)
|
|
547
|
+
if (current !== item && (action !== 'unapply' || current !== undefined)) return
|
|
548
|
+
|
|
549
|
+
item.apply_state = applyState
|
|
550
|
+
item.apply_error = applyError
|
|
551
|
+
this.#fetcher?.setConfigState(item.path, applyState, applyError)
|
|
552
|
+
}
|
|
553
|
+
|
|
386
554
|
/**
|
|
387
555
|
* @param {'apply' | 'modify' | 'unapply'} action
|
|
388
556
|
* @param {RcConfigState} item
|
|
@@ -399,12 +567,11 @@ class RemoteConfig {
|
|
|
399
567
|
if (supportsAckCallback(handler)) {
|
|
400
568
|
// If the handler accepts an `ack` callback, expect that to be called and set `apply_state` accordingly
|
|
401
569
|
// TODO: do we want to pass old and new config ?
|
|
402
|
-
handler(action, item.file, item.id, (
|
|
403
|
-
if (
|
|
404
|
-
item
|
|
405
|
-
item.apply_error = err.toString()
|
|
570
|
+
handler(action, item.file, item.id, (error) => {
|
|
571
|
+
if (error) {
|
|
572
|
+
this.#setApplyState(item, action, ERROR, error.toString())
|
|
406
573
|
} else if (item.apply_state !== ERROR) {
|
|
407
|
-
item
|
|
574
|
+
this.#setApplyState(item, action, ACKNOWLEDGED, '')
|
|
408
575
|
}
|
|
409
576
|
})
|
|
410
577
|
} else {
|
|
@@ -414,19 +581,15 @@ class RemoteConfig {
|
|
|
414
581
|
const result = handler(action, item.file, item.id)
|
|
415
582
|
if (result instanceof Promise) {
|
|
416
583
|
result.then(
|
|
417
|
-
() =>
|
|
418
|
-
(
|
|
419
|
-
item.apply_state = ERROR
|
|
420
|
-
item.apply_error = err.toString()
|
|
421
|
-
}
|
|
584
|
+
() => this.#setApplyState(item, action, ACKNOWLEDGED, ''),
|
|
585
|
+
(error) => this.#setApplyState(item, action, ERROR, error.toString())
|
|
422
586
|
)
|
|
423
587
|
} else {
|
|
424
|
-
item
|
|
588
|
+
this.#setApplyState(item, action, ACKNOWLEDGED, '')
|
|
425
589
|
}
|
|
426
590
|
}
|
|
427
|
-
} catch (
|
|
428
|
-
item
|
|
429
|
-
item.apply_error = err.toString()
|
|
591
|
+
} catch (error) {
|
|
592
|
+
this.#setApplyState(item, action, ERROR, error.toString())
|
|
430
593
|
}
|
|
431
594
|
}
|
|
432
595
|
}
|
|
@@ -443,8 +606,8 @@ class RemoteConfig {
|
|
|
443
606
|
* @property {unknown} file
|
|
444
607
|
* @property {number} apply_state
|
|
445
608
|
* @property {string} apply_error
|
|
446
|
-
* @property {number} length
|
|
447
|
-
* @property {Record<string, string>} hashes
|
|
609
|
+
* @property {number} [length]
|
|
610
|
+
* @property {Record<string, string>} [hashes]
|
|
448
611
|
*/
|
|
449
612
|
|
|
450
613
|
/**
|
|
@@ -472,22 +635,19 @@ class RemoteConfig {
|
|
|
472
635
|
* Create an immutable "view" of the batch changes and attach explicit outcome reporting.
|
|
473
636
|
*
|
|
474
637
|
* @param {{toUnapply: RcConfigState[], toApply: RcConfigState[], toModify: RcConfigState[]}} changes
|
|
475
|
-
* @param {Set<string>} handledPaths
|
|
476
638
|
* @param {Map<string, {state: number, error: string}>} outcomes
|
|
477
639
|
* @returns {RcBatchUpdateTransaction}
|
|
478
640
|
*/
|
|
479
|
-
function createUpdateTransaction ({ toUnapply, toApply, toModify },
|
|
641
|
+
function createUpdateTransaction ({ toUnapply, toApply, toModify }, outcomes) {
|
|
480
642
|
return {
|
|
481
643
|
toUnapply,
|
|
482
644
|
toApply,
|
|
483
645
|
toModify,
|
|
484
646
|
ack (path) {
|
|
485
647
|
outcomes.set(path, { state: ACKNOWLEDGED, error: '' })
|
|
486
|
-
handledPaths.add(path)
|
|
487
648
|
},
|
|
488
649
|
error (path, err) {
|
|
489
650
|
outcomes.set(path, { state: ERROR, error: err ? err.toString() : 'Error' })
|
|
490
|
-
handledPaths.add(path)
|
|
491
651
|
},
|
|
492
652
|
}
|
|
493
653
|
}
|
|
@@ -526,16 +686,6 @@ function filterTransactionByProducts (transaction, products) {
|
|
|
526
686
|
}
|
|
527
687
|
}
|
|
528
688
|
|
|
529
|
-
function applyOutcomes (byPath, outcomes) {
|
|
530
|
-
for (const [path, outcome] of outcomes) {
|
|
531
|
-
const item = byPath.get(path)
|
|
532
|
-
if (item) {
|
|
533
|
-
item.apply_state = outcome.state
|
|
534
|
-
item.apply_error = outcome.error
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
689
|
function fromBase64JSON (str) {
|
|
540
690
|
if (!str) return null
|
|
541
691
|
|
|
@@ -575,4 +725,72 @@ function supportsAckCallback (handler) {
|
|
|
575
725
|
return result
|
|
576
726
|
}
|
|
577
727
|
|
|
728
|
+
/**
|
|
729
|
+
* @param {import('@datadog/libdatadog/remote-config').RemoteConfigFetcherOptions} options
|
|
730
|
+
* @returns {import('@datadog/libdatadog/remote-config').RemoteConfigFetcher}
|
|
731
|
+
*/
|
|
732
|
+
function createAgentlessFetcher (options) {
|
|
733
|
+
const { RemoteConfigFetcher, setStorage } = require('@datadog/libdatadog/remote-config')
|
|
734
|
+
const { storage } = require('../../../datadog-core')
|
|
735
|
+
const legacyStorage = storage('legacy')
|
|
736
|
+
|
|
737
|
+
/** @param {() => void} callback */
|
|
738
|
+
function runWithoutTracing (callback) {
|
|
739
|
+
legacyStorage.run({ noop: true }, callback)
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
setStorage(runWithoutTracing)
|
|
743
|
+
return new RemoteConfigFetcher(options)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* @param {string} encodedCapabilities
|
|
748
|
+
* @returns {string[]}
|
|
749
|
+
*/
|
|
750
|
+
function decodeCapabilities (encodedCapabilities) {
|
|
751
|
+
const hex = Buffer.from(encodedCapabilities, 'base64').toString('hex')
|
|
752
|
+
const mask = BigInt(`0x${hex}`)
|
|
753
|
+
const names = []
|
|
754
|
+
|
|
755
|
+
for (const [name, capability] of capabilityEntries) {
|
|
756
|
+
if ((mask & capability) !== 0n) names.push(name)
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
return names
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* @param {import('../config/config-base')} config
|
|
764
|
+
* @param {string} repositoryUrl
|
|
765
|
+
* @param {string} commitSHA
|
|
766
|
+
* @returns {string[]}
|
|
767
|
+
*/
|
|
768
|
+
function getTagsString (config, repositoryUrl, commitSHA) {
|
|
769
|
+
const tags = repositoryUrl
|
|
770
|
+
? {
|
|
771
|
+
...config.tags,
|
|
772
|
+
[GIT_REPOSITORY_URL]: repositoryUrl,
|
|
773
|
+
[GIT_COMMIT_SHA]: commitSHA,
|
|
774
|
+
}
|
|
775
|
+
: config.tags
|
|
776
|
+
return Object.entries(tags).map((pair) => pair.join(':'))
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Regenerates the RC client ID and rebuilds the RC tag list, so subsequent RC polls report the
|
|
781
|
+
* clone's identity. No-ops on the tags before the first `RemoteConfig` is constructed.
|
|
782
|
+
*
|
|
783
|
+
* @param {import('../config/config-base')} config
|
|
784
|
+
*/
|
|
785
|
+
function refreshIdentity (config) {
|
|
786
|
+
clientId = uuid()
|
|
787
|
+
if (client !== undefined) {
|
|
788
|
+
config.tags['_dd.rc.client_id'] = clientId
|
|
789
|
+
client.id = clientId
|
|
790
|
+
client.client_tracer.runtime_id = config.tags['runtime-id']
|
|
791
|
+
const { commitSHA, repositoryUrl } = getGitMetadata(config)
|
|
792
|
+
client.client_tracer.tags = getTagsString(config, repositoryUrl, commitSHA)
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
578
796
|
module.exports = RemoteConfig
|
|
@@ -1,29 +1,54 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
class Scheduler {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
#active = false
|
|
5
|
+
#callback
|
|
6
|
+
#generation = 0
|
|
7
|
+
#inFlight = false
|
|
8
|
+
#interval
|
|
9
|
+
#timer
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {(done: () => void) => void} callback
|
|
13
|
+
* @param {number} interval
|
|
14
|
+
*/
|
|
6
15
|
constructor (callback, interval) {
|
|
7
|
-
this
|
|
8
|
-
this
|
|
16
|
+
this.#callback = callback
|
|
17
|
+
this.#interval = interval
|
|
9
18
|
}
|
|
10
19
|
|
|
11
20
|
start () {
|
|
12
|
-
if (this
|
|
21
|
+
if (this.#active) return
|
|
13
22
|
|
|
14
|
-
this
|
|
23
|
+
this.#active = true
|
|
24
|
+
this.#generation++
|
|
25
|
+
if (!this.#inFlight) this.#runAfterDelay(0)
|
|
15
26
|
}
|
|
16
27
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
28
|
+
/**
|
|
29
|
+
* @param {number} interval
|
|
30
|
+
*/
|
|
31
|
+
#runAfterDelay (interval) {
|
|
32
|
+
const generation = this.#generation
|
|
33
|
+
this.#timer = setTimeout(() => {
|
|
34
|
+
this.#timer = undefined
|
|
35
|
+
this.#inFlight = true
|
|
36
|
+
this.#callback(() => {
|
|
37
|
+
this.#inFlight = false
|
|
38
|
+
if (!this.#active) return
|
|
39
|
+
|
|
40
|
+
this.#runAfterDelay(this.#generation === generation ? this.#interval : 0)
|
|
41
|
+
})
|
|
42
|
+
}, interval)
|
|
43
|
+
|
|
44
|
+
this.#timer.unref?.()
|
|
21
45
|
}
|
|
22
46
|
|
|
23
47
|
stop () {
|
|
24
|
-
|
|
48
|
+
this.#active = false
|
|
49
|
+
clearTimeout(this.#timer)
|
|
25
50
|
|
|
26
|
-
this
|
|
51
|
+
this.#timer = undefined
|
|
27
52
|
}
|
|
28
53
|
}
|
|
29
54
|
|