@appland/telemetry 1.1.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 ADDED
@@ -0,0 +1,23 @@
1
+ # [@appland/telemetry-v1.1.0](https://github.com/getappmap/appmap-js/compare/@appland/telemetry-v1.0.0...@appland/telemetry-v1.1.0) (2025-10-07)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * `git status` is now run with the porcelain flag ([#1328](https://github.com/getappmap/appmap-js/issues/1328)) ([c16eb48](https://github.com/getappmap/appmap-js/commit/c16eb48c293d49c06c0c4a3bb91c9a070e991831))
7
+ * Don't hang when git is not found ([d2faa86](https://github.com/getappmap/appmap-js/commit/d2faa861b64b9ade63e729f894838fcf1efc8b2c))
8
+ * Include contributor counts in `scan` ([1c5de40](https://github.com/getappmap/appmap-js/commit/1c5de40410ae2e1794509fb29e9ba128eaac18c0))
9
+ * Include git contributor count in OpenAPI event ([4c9ee2c](https://github.com/getappmap/appmap-js/commit/4c9ee2c419d95ef0d09f5a12c90712bab890a8b7))
10
+ * Prevent `git status` from potentially hanging ([41f4a08](https://github.com/getappmap/appmap-js/commit/41f4a087d382e1f3ef7d942c4dd83df1cd75585f))
11
+ * Report git state ([a5d3797](https://github.com/getappmap/appmap-js/commit/a5d3797815b283ed114a57351067ed911ca36fa2))
12
+ * **telemetry:** Minor UX and correctness improvements in telemetry ([c41bfca](https://github.com/getappmap/appmap-js/commit/c41bfca46fd8897d828ba9fce51a883d873cfc1f))
13
+ * The Git proxy no longer alters the return type ([3c2be34](https://github.com/getappmap/appmap-js/commit/3c2be342dfc4548d96f7f3342b3146c058691143))
14
+
15
+
16
+ ### Features
17
+
18
+ * Add event usage updates to `appmap index` ([dd5dfdb](https://github.com/getappmap/appmap-js/commit/dd5dfdb10c906546bf750a382a1387ccaf14df14))
19
+ * **cli:** Use a configurable telemetry endpoint ([3de5d9f](https://github.com/getappmap/appmap-js/commit/3de5d9f1a322e6414d80d41f124f2e1d8995cc06))
20
+ * Report env var names ([6264cb6](https://github.com/getappmap/appmap-js/commit/6264cb6c73927a90fc350bfb1531d3a8dd97df69))
21
+ * Send git repository URL in appmap:openapi telemetry ([61de641](https://github.com/getappmap/appmap-js/commit/61de641ae05bdfb60558cfd91b69a5a15a6f0c98))
22
+ * **telemetry:** Add an additional telemetry backend ([52d561e](https://github.com/getappmap/appmap-js/commit/52d561ea978f3369b3a6e59f9141debd1bf9bc46))
23
+ * **telemetry:** Release @appland/telemetry as a package ([e001e9b](https://github.com/getappmap/appmap-js/commit/e001e9b2516b0e3c3d130be2dbe71d98e1849da7))
package/LICENSE.txt ADDED
@@ -0,0 +1,25 @@
1
+ The Software is provided to you by the Licensor under the License, as defined below, subject to the following condition.
2
+
3
+ Without limiting other conditions in the License, the grant of rights under the License will not include,
4
+ and the License does not grant to you, the right to Sell the Software.
5
+
6
+ For purposes of the foregoing, “Sell” means practicing any or all of the rights granted to you under the License to provide to third parties,
7
+ for a fee or other consideration (including without limitation fees for hosting or consulting/ support services related to the Software),
8
+ a product or service whose value derives, entirely or substantially, from the functionality of the Software.
9
+ Any license notice or attribution required by the License must also include this Commons Clause License Condition notice.
10
+
11
+ Software: @appland/telemetry
12
+
13
+ License: MIT License
14
+
15
+ Copyright 2025, AppLand Inc
16
+
17
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
18
+ to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
19
+ and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
20
+
21
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # AppMap Telemetry
2
+
3
+ This package provides a shared telemetry client for AppMap projects. It supports multiple backends and offers flexible configuration options.
4
+
5
+ ## Usage
6
+
7
+ ```typescript
8
+ import { Telemetry } from '@appland/telemetry';
9
+
10
+ const telemetry = new Telemetry();
11
+
12
+ telemetry.configure({
13
+ product: {
14
+ name: 'My App',
15
+ version: '1.0.0',
16
+ },
17
+ });
18
+
19
+ telemetry.sendEvent({
20
+ name: 'my-event',
21
+ properties: {
22
+ 'my-property': 'my-value',
23
+ },
24
+ metrics: {
25
+ 'my-metric': 123,
26
+ },
27
+ });
28
+ ```
29
+
30
+ ## Configuration
31
+
32
+ The telemetry client can be configured using environment variables.
33
+
34
+ ### General Configuration
35
+
36
+ * `APPMAP_TELEMETRY_DISABLED`: Set to `1`, `true`, or `yes` to disable telemetry.
37
+ * `APPMAP_TELEMETRY_DEBUG`: Set to `1`, `true`, or `yes` to enable debug logging, which will print telemetry events to the console.
38
+ * `APPMAP_TELEMETRY_BACKEND`: Specifies the telemetry backend to use. Supported values are `application-insights` (default) and `splunk`.
39
+
40
+ ### Splunk Backend
41
+
42
+ When `APPMAP_TELEMETRY_BACKEND` is set to `splunk`, the following environment variables are used:
43
+
44
+ * `SPLUNK_TOKEN`: **(Required)** The Splunk HTTP Event Collector (HEC) token.
45
+ * `SPLUNK_URL`: **(Required)** The Splunk HEC URL.
46
+ * `SPLUNK_CA_CERT`: Used to configure TLS verification and custom Certificate Authorities (CAs).
47
+ * **Not set (default):** TLS verification is disabled (`rejectUnauthorized: false`). This allows the use of self-signed certificates, which is common in Splunk deployments.
48
+ * `system`: Use the system's trusted Certificate Authorities. TLS verification is enabled.
49
+ * `@/path/to/ca.pem`: Use the specified file as the Certificate Authority. The path must be prefixed with `@`. TLS verification is enabled.
50
+ * `<certificate content>`: Use the value of the environment variable as the Certificate Authority. TLS verification is enabled.
51
+
52
+ ### Application Insights Backend
53
+
54
+ When `APPMAP_TELEMETRY_BACKEND` is set to `application-insights`, the client will use a default instrumentation key. This can be overridden by providing an `instrumentationKey` in the configuration:
55
+
56
+ ```typescript
57
+ telemetry.configure({
58
+ // ...
59
+ backend: {
60
+ type: 'application-insights',
61
+ instrumentationKey: 'YOUR_INSTRUMENTATION_KEY',
62
+ },
63
+ });
64
+ ```
@@ -0,0 +1,8 @@
1
+ import { ApplicationInsightsBackendConfiguration, FlushCallback, TelemetryBackend, TelemetryData } from '../types';
2
+ export declare class ApplicationInsightsBackend implements TelemetryBackend {
3
+ private readonly config;
4
+ private readonly client;
5
+ constructor(userId: string, sessionId: string, productName: string, config: ApplicationInsightsBackendConfiguration);
6
+ sendEvent(event: TelemetryData): void;
7
+ flush(callback?: FlushCallback): void;
8
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ApplicationInsightsBackend = void 0;
4
+ const applicationinsights_1 = require("applicationinsights");
5
+ // This key is meant to be publically shared. However, I'm adding a simple
6
+ // obfuscation to mitigate key scraping bots on GitHub. The key is split on
7
+ // hypens and base64 encoded without padding.
8
+ // key.split('-').map((x) => x.toString('base64').replace(/=*/, ''))
9
+ const INSTRUMENTATION_KEY = ['NTBjMWE1YzI', 'NDliNA', 'NDkxMw', 'YjdjYw', 'ODZhNzhkNDA3NDVm']
10
+ .map((x) => Buffer.from(x, 'base64').toString('utf8'))
11
+ .join('-');
12
+ class ApplicationInsightsBackend {
13
+ constructor(userId, sessionId, productName, config) {
14
+ this.config = config;
15
+ process.env.APPLICATION_INSIGHTS_NO_STATSBEAT = '1';
16
+ const instrumentationKey = this.config.instrumentationKey || INSTRUMENTATION_KEY;
17
+ // Disable everything we can, we don't any additional collection from Application Insights.
18
+ (0, applicationinsights_1.setup)(instrumentationKey)
19
+ .setAutoCollectRequests(false)
20
+ .setAutoCollectPerformance(false)
21
+ .setAutoCollectExceptions(false)
22
+ .setAutoCollectDependencies(false)
23
+ .setAutoCollectHeartbeat(false)
24
+ .setAutoDependencyCorrelation(false)
25
+ .setAutoCollectConsole(false)
26
+ .setInternalLogging(false, false)
27
+ .setSendLiveMetrics(false)
28
+ .setUseDiskRetryCaching(true);
29
+ const client = new applicationinsights_1.TelemetryClient(instrumentationKey);
30
+ client.context.tags[client.context.keys.userId] = userId;
31
+ client.context.tags[client.context.keys.sessionId] = sessionId;
32
+ client.context.tags[client.context.keys.cloudRole] = productName;
33
+ client.setAutoPopulateAzureProperties(false);
34
+ this.client = client;
35
+ }
36
+ sendEvent(event) {
37
+ this.client.trackEvent(event);
38
+ this.client.flush();
39
+ }
40
+ flush(callback) {
41
+ this.client.flush();
42
+ callback === null || callback === void 0 ? void 0 : callback();
43
+ }
44
+ }
45
+ exports.ApplicationInsightsBackend = ApplicationInsightsBackend;
46
+ //# sourceMappingURL=application-insights.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"application-insights.js","sourceRoot":"","sources":["../../src/backends/application-insights.ts"],"names":[],"mappings":";;;AAMA,6DAAsF;AAEtF,0EAA0E;AAC1E,2EAA2E;AAC3E,6CAA6C;AAC7C,oEAAoE;AACpE,MAAM,mBAAmB,GAAG,CAAC,aAAa,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,kBAAkB,CAAC;KAC1F,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;KACrD,IAAI,CAAC,GAAG,CAAC,CAAC;AAEb,MAAa,0BAA0B;IAGrC,YACE,MAAc,EACd,SAAiB,EACjB,WAAmB,EACF,MAA+C;QAA/C,WAAM,GAAN,MAAM,CAAyC;QAEhE,OAAO,CAAC,GAAG,CAAC,iCAAiC,GAAG,GAAG,CAAC;QAEpD,MAAM,kBAAkB,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,IAAI,mBAAmB,CAAC;QACjF,2FAA2F;QAC3F,IAAA,2BAAW,EAAC,kBAAkB,CAAC;aAC5B,sBAAsB,CAAC,KAAK,CAAC;aAC7B,yBAAyB,CAAC,KAAK,CAAC;aAChC,wBAAwB,CAAC,KAAK,CAAC;aAC/B,0BAA0B,CAAC,KAAK,CAAC;aACjC,uBAAuB,CAAC,KAAK,CAAC;aAC9B,4BAA4B,CAAC,KAAK,CAAC;aACnC,qBAAqB,CAAC,KAAK,CAAC;aAC5B,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC;aAChC,kBAAkB,CAAC,KAAK,CAAC;aACzB,sBAAsB,CAAC,IAAI,CAAC,CAAC;QAEhC,MAAM,MAAM,GAAG,IAAI,qCAAM,CAAC,kBAAkB,CAAC,CAAC;QAC9C,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;QACzD,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;QAC/D,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,WAAW,CAAC;QACjE,MAAM,CAAC,8BAA8B,CAAC,KAAK,CAAC,CAAC;QAE7C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,SAAS,CAAC,KAAoB;QAC5B,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,QAAwB;QAC5B,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACpB,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,EAAI,CAAC;IACf,CAAC;CACF;AA3CD,gEA2CC"}
@@ -0,0 +1,9 @@
1
+ import type { SplunkBackendConfiguration, TelemetryBackend, TelemetryData, FlushCallback } from '../types';
2
+ export declare class SplunkBackend implements TelemetryBackend {
3
+ private readonly config;
4
+ private readonly httpAgent;
5
+ private pendingRequests;
6
+ constructor(config?: Partial<SplunkBackendConfiguration>);
7
+ sendEvent(event: TelemetryData): void;
8
+ flush(callback?: FlushCallback): void;
9
+ }
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SplunkBackend = void 0;
37
+ const http = __importStar(require("node:http"));
38
+ const https = __importStar(require("node:https"));
39
+ const node_url_1 = require("node:url");
40
+ const fs = __importStar(require("node:fs"));
41
+ const DefaultSplunkPath = '/services/collector/event';
42
+ const MaxFlushTime = 5000; // 5 seconds
43
+ class SplunkBackend {
44
+ constructor(config = {}) {
45
+ var _a, _b;
46
+ this.pendingRequests = 0;
47
+ const token = (_a = config.token) !== null && _a !== void 0 ? _a : process.env.SPLUNK_TOKEN;
48
+ const url = (_b = config.url) !== null && _b !== void 0 ? _b : process.env.SPLUNK_URL;
49
+ if (!token) {
50
+ throw new Error('SPLUNK_TOKEN is required');
51
+ }
52
+ if (!url) {
53
+ throw new Error('SPLUNK_URL is required');
54
+ }
55
+ this.config = {
56
+ type: 'splunk',
57
+ token: token,
58
+ url: url,
59
+ };
60
+ const parsedUrl = new node_url_1.URL(this.config.url);
61
+ const isHttps = parsedUrl.protocol === 'https:';
62
+ if (isHttps) {
63
+ const splunkCaCert = process.env.SPLUNK_CA_CERT;
64
+ let httpsAgentOptions = {
65
+ rejectUnauthorized: false, // Self-signed certs are common in Splunk
66
+ };
67
+ if (splunkCaCert) {
68
+ if (splunkCaCert === 'system') {
69
+ httpsAgentOptions = {
70
+ rejectUnauthorized: true,
71
+ };
72
+ }
73
+ else if (splunkCaCert.startsWith('@')) {
74
+ const caPath = splunkCaCert.substring(1);
75
+ httpsAgentOptions = {
76
+ rejectUnauthorized: true,
77
+ ca: fs.readFileSync(caPath),
78
+ };
79
+ }
80
+ else {
81
+ httpsAgentOptions = {
82
+ rejectUnauthorized: true,
83
+ ca: splunkCaCert,
84
+ };
85
+ }
86
+ }
87
+ this.httpAgent = new https.Agent(httpsAgentOptions);
88
+ }
89
+ else {
90
+ this.httpAgent = new http.Agent();
91
+ }
92
+ }
93
+ sendEvent(event) {
94
+ this.pendingRequests++;
95
+ const parsedUrl = new node_url_1.URL(this.config.url);
96
+ const path = parsedUrl.pathname === '/' ? DefaultSplunkPath : parsedUrl.pathname;
97
+ const payload = JSON.stringify({ event });
98
+ const options = {
99
+ hostname: parsedUrl.hostname,
100
+ port: parsedUrl.port ? parseInt(parsedUrl.port, 10) : (parsedUrl.protocol === 'https:' ? 443 : 8088),
101
+ path: path,
102
+ method: 'POST',
103
+ headers: {
104
+ 'Content-Type': 'application/json',
105
+ 'Content-Length': Buffer.byteLength(payload),
106
+ Authorization: `Splunk ${this.config.token}`,
107
+ },
108
+ agent: this.httpAgent,
109
+ };
110
+ const req = (parsedUrl.protocol === 'https:' ? https : http).request(options, (res) => {
111
+ let responseBody = '';
112
+ res.on('data', (chunk) => {
113
+ responseBody += chunk;
114
+ });
115
+ res.on('end', () => {
116
+ this.pendingRequests--;
117
+ if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
118
+ console.warn(`SplunkBackend: Failed to send telemetry event. Status: ${res.statusCode}, Response: ${responseBody}`);
119
+ }
120
+ });
121
+ });
122
+ req.on('error', (e) => {
123
+ this.pendingRequests--;
124
+ console.warn(`SplunkBackend: Connectivity error sending telemetry event: ${e.message}`);
125
+ });
126
+ req.write(payload);
127
+ req.end();
128
+ }
129
+ flush(callback) {
130
+ const startTime = process.hrtime.bigint();
131
+ const checkPending = () => {
132
+ if (this.pendingRequests === 0) {
133
+ if (callback) {
134
+ callback();
135
+ }
136
+ return;
137
+ }
138
+ const elapsedNs = process.hrtime.bigint() - startTime;
139
+ const elapsedMs = Number(elapsedNs / BigInt(1000000));
140
+ if (elapsedMs > MaxFlushTime) {
141
+ console.warn(`SplunkBackend: Flush timed out after ${MaxFlushTime}ms`);
142
+ if (callback) {
143
+ callback();
144
+ }
145
+ return;
146
+ }
147
+ setTimeout(checkPending, 10); // Check again in 10ms
148
+ };
149
+ checkPending();
150
+ }
151
+ }
152
+ exports.SplunkBackend = SplunkBackend;
153
+ //# sourceMappingURL=splunk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"splunk.js","sourceRoot":"","sources":["../../src/backends/splunk.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { FlushCallback, ITelemetryClient, TelemetryConfiguration, TelemetryData, TelemetryOptions } from './types';
2
+ export declare class TelemetryClient implements ITelemetryClient {
3
+ private telemetryConfig?;
4
+ private backend?;
5
+ private userConfig?;
6
+ debug: boolean;
7
+ private session?;
8
+ enabled: boolean;
9
+ readonly machineId: string;
10
+ get sessionId(): string;
11
+ constructor(config?: Partial<TelemetryConfiguration>);
12
+ configure(config?: Partial<TelemetryConfiguration>): void;
13
+ sendEvent(data: TelemetryData, options?: TelemetryOptions): void;
14
+ flush(callback?: FlushCallback): void;
15
+ }
16
+ declare const defaultClient: TelemetryClient;
17
+ export default defaultClient;
package/dist/client.js ADDED
@@ -0,0 +1,229 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.TelemetryClient = void 0;
40
+ const conf_1 = __importDefault(require("conf"));
41
+ const os = __importStar(require("os"));
42
+ const identity_1 = require("./identity");
43
+ const session_1 = require("./session");
44
+ const read_pkg_up_1 = require("read-pkg-up");
45
+ const path_1 = __importDefault(require("path"));
46
+ const application_insights_1 = require("./backends/application-insights");
47
+ const splunk_1 = require("./backends/splunk");
48
+ /**
49
+ * Append the prefix to the name of each property and drop undefined values
50
+ */
51
+ const transformProps = (obj, propPrefix) => {
52
+ const result = {};
53
+ for (const [k, v] of Object.entries(obj)) {
54
+ if (v === undefined)
55
+ continue;
56
+ if (k.includes('.')) {
57
+ result[k] = v;
58
+ continue;
59
+ }
60
+ const prefixedKey = k.startsWith(propPrefix) ? k : `${propPrefix}${k}`;
61
+ result[prefixedKey] = v;
62
+ }
63
+ return result;
64
+ };
65
+ function resolvePackageJson() {
66
+ var _a;
67
+ try {
68
+ let myPath = __dirname;
69
+ // If we're in a node_modules directory, go up to the consuming package root
70
+ const nodeModuleIndex = myPath.indexOf(`${path_1.default.sep}node_modules${path_1.default.sep}`);
71
+ if (nodeModuleIndex !== -1) {
72
+ myPath = myPath.substring(0, nodeModuleIndex);
73
+ }
74
+ const result = (_a = (0, read_pkg_up_1.sync)({ cwd: myPath })) === null || _a === void 0 ? void 0 : _a.packageJson;
75
+ if (result) {
76
+ return {
77
+ name: result.name,
78
+ version: result.version,
79
+ };
80
+ }
81
+ }
82
+ catch (error) {
83
+ console.error('Error resolving package.json:', error);
84
+ return undefined;
85
+ }
86
+ }
87
+ function defaultBackend() {
88
+ switch (process.env.APPMAP_TELEMETRY_BACKEND) {
89
+ case 'application-insights':
90
+ case 'splunk':
91
+ return {
92
+ type: process.env.APPMAP_TELEMETRY_BACKEND
93
+ };
94
+ }
95
+ // Default to application insights if no backend is specified
96
+ // or if the specified backend is not recognized.
97
+ // This is to maintain backward compatibility.
98
+ return { type: 'application-insights' };
99
+ }
100
+ function buildDefaultConfiguration(base = {}) {
101
+ const product = Object.assign({ name: '', version: '' }, base.product);
102
+ if (!product.name || !product.version) {
103
+ const pkg = resolvePackageJson();
104
+ product.name || (product.name = (pkg === null || pkg === void 0 ? void 0 : pkg.name) || 'unknown');
105
+ product.version || (product.version = (pkg === null || pkg === void 0 ? void 0 : pkg.version) || '0.0.0');
106
+ }
107
+ // By default, the prop prefix will be the product name with non-word characters replaced by dots.
108
+ const propPrefix = base.propPrefix || product.name.replace(/\W/g, '.').replace(/^\./, '') + '.';
109
+ return Object.assign({ product,
110
+ propPrefix, backend: defaultBackend() }, base);
111
+ }
112
+ /** Interpret a boolean as coming from an environment variable.
113
+ * If the value is undefined, return the defaultValue.
114
+ * If the value is '1', 'true', or 'yes' (case insensitive), return true.
115
+ * If the value is '0', 'false', or 'no' (case insensitive), return false.
116
+ * Otherwise, return the defaultValue.
117
+ */
118
+ function stringToBool(value, defaultValue = false) {
119
+ if (value === undefined)
120
+ return defaultValue;
121
+ switch (value.toLowerCase()) {
122
+ case '1':
123
+ case 'true':
124
+ case 'yes':
125
+ return true;
126
+ case '0':
127
+ case 'false':
128
+ case 'no':
129
+ return false;
130
+ default:
131
+ return defaultValue;
132
+ }
133
+ }
134
+ class TelemetryClient {
135
+ get sessionId() {
136
+ if (!this.session) {
137
+ throw new Error('Session is not initialized');
138
+ }
139
+ return this.session.id;
140
+ }
141
+ constructor(config) {
142
+ this.debug = stringToBool(process.env.APPMAP_TELEMETRY_DEBUG);
143
+ this.enabled = !stringToBool(process.env.APPMAP_TELEMETRY_DISABLED);
144
+ this.machineId = (0, identity_1.getMachineId)();
145
+ if (config)
146
+ this.configure(config);
147
+ }
148
+ configure(config = {}) {
149
+ if (this.telemetryConfig) {
150
+ throw new Error('Telemetry client is already configured');
151
+ }
152
+ this.telemetryConfig = buildDefaultConfiguration(config);
153
+ this.userConfig = new conf_1.default({
154
+ projectName: this.telemetryConfig.product.name,
155
+ projectVersion: '0.0.1', // note this is actually config version
156
+ });
157
+ this.session = new session_1.Session(this.userConfig);
158
+ // Construct additional backends here as needed.
159
+ switch (this.telemetryConfig.backend.type) {
160
+ case 'application-insights':
161
+ this.backend = new application_insights_1.ApplicationInsightsBackend(this.machineId, this.session.id, this.telemetryConfig.product.name, this.telemetryConfig.backend);
162
+ break;
163
+ case 'custom':
164
+ this.backend = this.telemetryConfig.backend;
165
+ break;
166
+ case 'splunk':
167
+ this.backend = new splunk_1.SplunkBackend(this.telemetryConfig.backend);
168
+ break;
169
+ }
170
+ if (this.debug) {
171
+ console.warn('Telemetry configuration:', this.telemetryConfig);
172
+ }
173
+ }
174
+ sendEvent(data, options = { includeEnvironment: false }) {
175
+ if (!this.backend)
176
+ this.configure();
177
+ if (!this.backend || !this.telemetryConfig || !this.session)
178
+ throw new Error('Telemetry client is not configured');
179
+ try {
180
+ const { propPrefix } = this.telemetryConfig;
181
+ const { name, version } = this.telemetryConfig.product;
182
+ const transformedProperties = transformProps(Object.assign({ version: version, args: process.argv.slice(1).join(' ') }, data.properties), propPrefix);
183
+ const transformedMetrics = transformProps(data.metrics || {}, propPrefix);
184
+ const properties = Object.assign({ 'common.source': name, 'common.os': os.platform(), 'common.platformversion': os.release(), 'common.arch': os.arch(), 'appmap.cli.machineId': this.machineId, 'appmap.cli.sessionId': this.session.id }, transformedProperties);
185
+ if (options.includeEnvironment) {
186
+ properties['common.environmentVariables'] = Object.keys(process.env).sort().join(',');
187
+ }
188
+ const event = {
189
+ name: `${name}/${data.name}`,
190
+ measurements: transformedMetrics,
191
+ properties,
192
+ };
193
+ if (this.debug) {
194
+ console.warn(JSON.stringify(event, null, 2));
195
+ }
196
+ if (this.enabled) {
197
+ this.backend.sendEvent(event);
198
+ this.session.touch();
199
+ this.backend.flush();
200
+ }
201
+ }
202
+ catch (e) {
203
+ // Don't let telemetry fail the entire command
204
+ // Do nothing other than log for now, we can't do anything about it
205
+ if (this.debug) {
206
+ if (e instanceof Error) {
207
+ console.error(e.stack);
208
+ }
209
+ else {
210
+ console.error(e);
211
+ }
212
+ }
213
+ }
214
+ }
215
+ flush(callback) {
216
+ var _a;
217
+ if (this.enabled) {
218
+ (_a = this.backend) === null || _a === void 0 ? void 0 : _a.flush(callback);
219
+ }
220
+ else {
221
+ if (callback)
222
+ callback();
223
+ }
224
+ }
225
+ }
226
+ exports.TelemetryClient = TelemetryClient;
227
+ const defaultClient = new TelemetryClient();
228
+ exports.default = defaultClient;
229
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/dist/git.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { PathLike } from 'fs';
2
+ export declare enum GitState {
3
+ NotInstalled = 0,// The git cli was not found.
4
+ NoRepository = 1,// Git is installed but no repository was found.
5
+ Ok = 2
6
+ }
7
+ export declare const GitRepositoryEnvKeys: readonly ["GITHUB_REPOSITORY", "CIRCLE_REPOSITORY_URL", "GIT_URL", "CI_REPOSITORY_URL"];
8
+ export declare const GitBranchEnvKeys: readonly ["GITHUB_REF_NAME", "CIRCLE_BRANCH", "GIT_BRANCH", "TRAVIS_BRANCH", "CI_COMMIT_REF_NAME"];
9
+ export declare const GitCommitEnvKeys: readonly ["GITHUB_SHA", "CIRCLE_SHA1", "GIT_COMMIT", "TRAVIS_COMMIT", "CI_COMMIT_SHA"];
10
+ /**
11
+ * This is a wrapper around `execFile` that returns a promise containing the stdout. `execFile` will
12
+ * not spawn a shell, so it is less likely to be vulnerable to shell injection attacks.
13
+ *
14
+ * @param {string} command - The command to execute. This should be resolvable via `PATH`, and contain no arguments.
15
+ * @param {string[]} args - The arguments to pass to the command.
16
+ * @param {object} [options] - Options to pass to `execFile`.
17
+ * @returns {Promise<string>} A promise containing the stdout of the command.
18
+ * @throws {Error} If the command fails with a non-zero (or anything other than the specified) exit code.
19
+ */
20
+ export declare const execute: (command: string, args: string[], options?: {
21
+ cwd?: string;
22
+ exitCode?: number;
23
+ timeout?: number;
24
+ }) => Promise<string>;
25
+ declare class GitProperties {
26
+ static contributors(sinceDaysAgo: number, cwd?: PathLike): Promise<Array<string>>;
27
+ static repository(cwd?: PathLike): Promise<string | undefined>;
28
+ static branch(cwd?: PathLike): Promise<string | undefined>;
29
+ static commit(cwd?: PathLike): Promise<string | undefined>;
30
+ static state(cwd?: PathLike): Promise<GitState>;
31
+ static clearCache(): void;
32
+ }
33
+ export declare const Git: typeof GitProperties;
34
+ export {};
package/dist/git.js ADDED
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.Git = exports.execute = exports.GitCommitEnvKeys = exports.GitBranchEnvKeys = exports.GitRepositoryEnvKeys = exports.GitState = void 0;
13
+ const child_process_1 = require("child_process");
14
+ var GitState;
15
+ (function (GitState) {
16
+ GitState[GitState["NotInstalled"] = 0] = "NotInstalled";
17
+ GitState[GitState["NoRepository"] = 1] = "NoRepository";
18
+ GitState[GitState["Ok"] = 2] = "Ok";
19
+ })(GitState || (exports.GitState = GitState = {}));
20
+ exports.GitRepositoryEnvKeys = [
21
+ 'GITHUB_REPOSITORY', // GitHub
22
+ 'CIRCLE_REPOSITORY_URL', // CircleCI
23
+ 'GIT_URL', // Jenkins
24
+ 'CI_REPOSITORY_URL', // GitLab
25
+ ];
26
+ exports.GitBranchEnvKeys = [
27
+ 'GITHUB_REF_NAME', // GitHub
28
+ 'CIRCLE_BRANCH', // CircleCI
29
+ 'GIT_BRANCH', // Jenkins
30
+ 'TRAVIS_BRANCH', // TravisCI
31
+ 'CI_COMMIT_REF_NAME', // GitLab
32
+ ];
33
+ exports.GitCommitEnvKeys = [
34
+ 'GITHUB_SHA', // GitHub
35
+ 'CIRCLE_SHA1', // CircleCI
36
+ 'GIT_COMMIT', // Jenkins
37
+ 'TRAVIS_COMMIT', // TravisCI
38
+ 'CI_COMMIT_SHA', // GitLab
39
+ ];
40
+ /**
41
+ * This is a wrapper around `execFile` that returns a promise containing the stdout. `execFile` will
42
+ * not spawn a shell, so it is less likely to be vulnerable to shell injection attacks.
43
+ *
44
+ * @param {string} command - The command to execute. This should be resolvable via `PATH`, and contain no arguments.
45
+ * @param {string[]} args - The arguments to pass to the command.
46
+ * @param {object} [options] - Options to pass to `execFile`.
47
+ * @returns {Promise<string>} A promise containing the stdout of the command.
48
+ * @throws {Error} If the command fails with a non-zero (or anything other than the specified) exit code.
49
+ */
50
+ const execute = (command, args, options) => new Promise((resolve, reject) => {
51
+ var _a, _b, _c, _d;
52
+ const child = (0, child_process_1.execFile)(command, args, Object.assign({}, (options !== null && options !== void 0 ? options : {})));
53
+ let stdout = '';
54
+ (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.setEncoding('utf8');
55
+ (_b = child.stdout) === null || _b === void 0 ? void 0 : _b.on('data', (data) => {
56
+ stdout += data.toString();
57
+ });
58
+ let stderr = '';
59
+ (_c = child.stderr) === null || _c === void 0 ? void 0 : _c.setEncoding('utf8');
60
+ (_d = child.stderr) === null || _d === void 0 ? void 0 : _d.on('data', (data) => {
61
+ stderr += data.toString();
62
+ });
63
+ child.on('close', (code) => {
64
+ var _a;
65
+ if (code === ((_a = options === null || options === void 0 ? void 0 : options.exitCode) !== null && _a !== void 0 ? _a : 0))
66
+ resolve(stdout);
67
+ else
68
+ reject(new Error(stderr));
69
+ });
70
+ });
71
+ exports.execute = execute;
72
+ class GitProperties {
73
+ static contributors(sinceDaysAgo, cwd) {
74
+ return __awaiter(this, void 0, void 0, function* () {
75
+ const unixTimeNow = Math.floor(Number(new Date()) / 1000);
76
+ const unixTimeAgo = unixTimeNow - sinceDaysAgo * 24 * 60 * 60;
77
+ try {
78
+ const args = ['--no-pager', 'log', `--since=${unixTimeAgo}`, '--format="%ae"'];
79
+ if (cwd)
80
+ args.unshift('-C', cwd.toString());
81
+ const stdout = yield (0, exports.execute)('git', args);
82
+ return [
83
+ ...stdout
84
+ .trim()
85
+ .split('\n')
86
+ .reduce((acc, email) => {
87
+ acc.add(email);
88
+ return acc;
89
+ }, new Set()),
90
+ ];
91
+ }
92
+ catch (_a) {
93
+ return [];
94
+ }
95
+ });
96
+ }
97
+ // Returns the repository URL, first by checking the environment, then by
98
+ // shelling out to git.
99
+ static repository(cwd) {
100
+ return __awaiter(this, void 0, void 0, function* () {
101
+ const envKey = exports.GitRepositoryEnvKeys.find((key) => process.env[key]);
102
+ if (envKey)
103
+ return process.env[envKey];
104
+ try {
105
+ const args = ['config', '--get', 'remote.origin.url'];
106
+ if (cwd)
107
+ args.unshift('-C', cwd.toString());
108
+ const stdout = yield (0, exports.execute)('git', args);
109
+ return stdout.trim();
110
+ }
111
+ catch (_a) {
112
+ return undefined;
113
+ }
114
+ });
115
+ }
116
+ // Returns the branch, first by checking the environment, then by
117
+ // shelling out to git.
118
+ static branch(cwd) {
119
+ return __awaiter(this, void 0, void 0, function* () {
120
+ const envKey = exports.GitBranchEnvKeys.find((key) => process.env[key]);
121
+ if (envKey)
122
+ return process.env[envKey];
123
+ try {
124
+ const args = ['rev-parse', '--abbrev-ref', 'HEAD'];
125
+ if (cwd)
126
+ args.unshift('-C', cwd.toString());
127
+ const stdout = yield (0, exports.execute)('git', args);
128
+ return stdout.trim();
129
+ }
130
+ catch (_a) {
131
+ return undefined;
132
+ }
133
+ });
134
+ }
135
+ // Returns the commit SHA, first by checking the environment, then by
136
+ // shelling out to git.
137
+ static commit(cwd) {
138
+ return __awaiter(this, void 0, void 0, function* () {
139
+ const envKey = exports.GitCommitEnvKeys.find((key) => process.env[key]);
140
+ if (envKey)
141
+ return process.env[envKey];
142
+ try {
143
+ const args = ['rev-parse', 'HEAD'];
144
+ if (cwd)
145
+ args.unshift('-C', cwd.toString());
146
+ const stdout = yield (0, exports.execute)('git', args);
147
+ return stdout.trim();
148
+ }
149
+ catch (_a) {
150
+ return undefined;
151
+ }
152
+ });
153
+ }
154
+ static state(cwd) {
155
+ return __awaiter(this, void 0, void 0, function* () {
156
+ return new Promise((resolve) => {
157
+ try {
158
+ const commandProcess = (0, child_process_1.spawn)('git', ['status', '--porcelain'], {
159
+ shell: true,
160
+ cwd: cwd === null || cwd === void 0 ? void 0 : cwd.toString(),
161
+ stdio: 'ignore',
162
+ timeout: 2000,
163
+ });
164
+ commandProcess.on('exit', (code) => {
165
+ switch (code) {
166
+ case 127:
167
+ return resolve(GitState.NotInstalled);
168
+ case 128:
169
+ return resolve(GitState.NoRepository);
170
+ default:
171
+ return resolve(GitState.Ok);
172
+ }
173
+ });
174
+ commandProcess.on('error', () => resolve(GitState.NotInstalled));
175
+ }
176
+ catch (_a) {
177
+ resolve(GitState.NotInstalled);
178
+ }
179
+ });
180
+ });
181
+ }
182
+ static clearCache() {
183
+ gitCache.clear();
184
+ }
185
+ }
186
+ const gitCache = new Map();
187
+ const noCacheList = ['clearCache'];
188
+ // GitProperties is available externally as Git.
189
+ // This export provides a simple caching layer around GitProperties to avoid
190
+ // excessive shelling out to git.
191
+ exports.Git = new Proxy(GitProperties, {
192
+ get(target, prop) {
193
+ if (!noCacheList.includes(prop.toString()) &&
194
+ typeof target[prop] === 'function') {
195
+ return new Proxy(target[prop], {
196
+ apply(target, thisArg, argArray) {
197
+ const cacheKey = `${prop.toString()}(${JSON.stringify(argArray)})`;
198
+ if (gitCache.has(cacheKey)) {
199
+ return gitCache.get(cacheKey);
200
+ }
201
+ /* eslint-disable-next-line @typescript-eslint/no-unsafe-function-type */
202
+ const result = Reflect.apply(target, thisArg, argArray);
203
+ gitCache.set(cacheKey, result);
204
+ return result;
205
+ },
206
+ });
207
+ }
208
+ return Reflect.get(target, prop);
209
+ },
210
+ });
211
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type Conf from 'conf';
2
+ export declare function getMachineId(config?: Conf): string;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getMachineId = getMachineId;
4
+ const os_1 = require("os");
5
+ const crypto_1 = require("crypto");
6
+ const invalidMacAddresses = new Set([
7
+ '00:00:00:00:00:00',
8
+ 'ff:ff:ff:ff:ff:ff',
9
+ 'ac:de:48:00:11:22',
10
+ ]);
11
+ function getMachineId(config) {
12
+ const machineId = config === null || config === void 0 ? void 0 : config.get('machineId');
13
+ if (machineId) {
14
+ return machineId;
15
+ }
16
+ let machineIdSource;
17
+ // Derive a machine ID from the first network interface
18
+ machineIdSource = Object.values((0, os_1.networkInterfaces)())
19
+ .flat()
20
+ .map((iface) => iface === null || iface === void 0 ? void 0 : iface.mac)
21
+ .filter((mac) => mac && !invalidMacAddresses.has(mac))
22
+ .shift();
23
+ if (!machineIdSource) {
24
+ // Fallback to a random string
25
+ machineIdSource = (0, crypto_1.randomBytes)(32);
26
+ }
27
+ const machineIdHash = (0, crypto_1.createHash)('sha256')
28
+ .update(machineIdSource)
29
+ .digest('hex');
30
+ config === null || config === void 0 ? void 0 : config.set('machineId', machineIdHash);
31
+ return machineIdHash;
32
+ }
33
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":";;AAUA,oCA2BC;AArCD,2BAAuC;AACvC,mCAA6D;AAG7D,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IAClC,mBAAmB;IACnB,mBAAmB;IACnB,mBAAmB;CACpB,CAAC,CAAC;AAEH,SAAgB,YAAY,CAAC,MAAa;IACxC,MAAM,SAAS,GAAG,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,GAAG,CAAC,WAAW,CAAuB,CAAC;IACjE,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,eAAuC,CAAC;IAE5C,uDAAuD;IACvD,eAAe,GAAG,MAAM,CAAC,MAAM,CAAC,IAAA,sBAAiB,GAAE,CAAC;SACjD,IAAI,EAAE;SACN,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,GAAG,CAAC;SAC1B,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;SACrD,KAAK,EAAE,CAAC;IAEX,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,8BAA8B;QAC9B,eAAe,GAAG,IAAA,oBAAW,EAAC,EAAE,CAAC,CAAC;IACpC,CAAC;IAED,MAAM,aAAa,GAAG,IAAA,mBAAU,EAAC,QAAQ,CAAC;SACvC,MAAM,CAAC,eAA6B,CAAC;SACrC,MAAM,CAAC,KAAK,CAAC,CAAC;IAEjB,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,GAAG,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IAExC,OAAO,aAAa,CAAC;AACvB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { default as Telemetry } from './client';
2
+ export * from './types';
3
+ export * from './client';
4
+ export * from './git';
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.Telemetry = void 0;
21
+ var client_1 = require("./client");
22
+ Object.defineProperty(exports, "Telemetry", { enumerable: true, get: function () { return __importDefault(client_1).default; } });
23
+ __exportStar(require("./types"), exports);
24
+ __exportStar(require("./client"), exports);
25
+ __exportStar(require("./git"), exports);
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,mCAAgD;AAAvC,oHAAA,OAAO,OAAa;AAC7B,0CAAwB;AACxB,2CAAyB;AACzB,wCAAsB"}
@@ -0,0 +1,16 @@
1
+ import type Conf from 'conf';
2
+ export declare class Session {
3
+ private readonly config;
4
+ private _id?;
5
+ private _expiration?;
6
+ get id(): string;
7
+ get expiration(): number;
8
+ get valid(): boolean;
9
+ constructor(config: Conf);
10
+ static beyondExpiration(expiration: number): boolean;
11
+ static expirationFromNow(): number;
12
+ static newSessionId(): string;
13
+ private load;
14
+ renew(): void;
15
+ touch(): void;
16
+ }
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Session = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const SESSION_EXPIRATION = 1000 * 60 * 30; // 30 minutes
6
+ class Session {
7
+ get id() {
8
+ if (!this.valid)
9
+ this.renew();
10
+ if (!this._id)
11
+ throw new Error('Session ID is not set');
12
+ return this._id;
13
+ }
14
+ get expiration() {
15
+ if (!this.valid)
16
+ this.renew();
17
+ if (!this._expiration)
18
+ throw new Error('Session expiration is not set');
19
+ return this._expiration;
20
+ }
21
+ get valid() {
22
+ return (this._id !== undefined &&
23
+ this._expiration !== undefined &&
24
+ !Session.beyondExpiration(this._expiration));
25
+ }
26
+ constructor(config) {
27
+ this.config = config;
28
+ this.load();
29
+ }
30
+ static beyondExpiration(expiration) {
31
+ return expiration <= Date.now();
32
+ }
33
+ static expirationFromNow() {
34
+ return Date.now() + SESSION_EXPIRATION;
35
+ }
36
+ static newSessionId() {
37
+ return (0, crypto_1.createHash)('sha256').update((0, crypto_1.randomBytes)(32)).digest('hex');
38
+ }
39
+ load() {
40
+ this._id = this.config.get('sessionId');
41
+ this._expiration = this.config.get('sessionExpiration');
42
+ }
43
+ renew() {
44
+ this._id = Session.newSessionId();
45
+ this._expiration = Session.expirationFromNow();
46
+ this.config.set('sessionId', this._id);
47
+ this.config.set('sessionExpiration', this._expiration);
48
+ }
49
+ touch() {
50
+ this._expiration = Session.expirationFromNow();
51
+ this.config.set('sessionExpiration', this.expiration);
52
+ }
53
+ }
54
+ exports.Session = Session;
55
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":";;;AACA,mCAAiD;AAEjD,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,aAAa;AAExD,MAAa,OAAO;IAIlB,IAAI,EAAE;QACJ,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,IAAI,CAAC,KAAK,EAAE,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QACxD,OAAO,IAAI,CAAC,GAAG,CAAC;IAClB,CAAC;IAED,IAAI,UAAU;QACZ,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,IAAI,CAAC,KAAK,EAAE,CAAC;QAC9B,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;QACxE,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,IAAI,KAAK;QACP,OAAO,CACL,IAAI,CAAC,GAAG,KAAK,SAAS;YACtB,IAAI,CAAC,WAAW,KAAK,SAAS;YAC9B,CAAC,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,WAAW,CAAC,CAC5C,CAAC;IACJ,CAAC;IAED,YAA6B,MAAY;QAAZ,WAAM,GAAN,MAAM,CAAM;QACvC,IAAI,CAAC,IAAI,EAAE,CAAC;IACd,CAAC;IAED,MAAM,CAAC,gBAAgB,CAAC,UAAkB;QACxC,OAAO,UAAU,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;IAClC,CAAC;IAED,MAAM,CAAC,iBAAiB;QACtB,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,kBAAkB,CAAC;IACzC,CAAC;IAED,MAAM,CAAC,YAAY;QACjB,OAAO,IAAA,mBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAA,oBAAW,EAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACpE,CAAC;IAEO,IAAI;QACV,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,CAAuB,CAAC;QAC9D,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAuB,CAAC;IAChF,CAAC;IAED,KAAK;QACH,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;QAClC,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;QAC/C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACzD,CAAC;IAED,KAAK;QACH,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;QAC/C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,mBAAmB,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACxD,CAAC;CACF;AAxDD,0BAwDC"}
@@ -0,0 +1,48 @@
1
+ export interface TelemetryData {
2
+ name: string;
3
+ properties?: Record<string, string | undefined>;
4
+ metrics?: Record<string, number | undefined>;
5
+ }
6
+ export interface TelemetryOptions {
7
+ includeEnvironment: boolean;
8
+ }
9
+ export type FlushCallback = () => void;
10
+ export interface ITelemetryClient {
11
+ enabled: boolean;
12
+ machineId: Readonly<string>;
13
+ sessionId: Readonly<string>;
14
+ configure(config: Partial<TelemetryConfiguration>): void;
15
+ sendEvent(data: TelemetryData, options?: TelemetryOptions): void;
16
+ flush(callback?: FlushCallback): void;
17
+ }
18
+ export interface TelemetryBackend {
19
+ sendEvent(event: TelemetryData): void;
20
+ flush(callback?: FlushCallback): void;
21
+ }
22
+ export type BaseBackendConfiguration = {
23
+ type: string;
24
+ };
25
+ export type ProductConfiguration = {
26
+ name: string;
27
+ version: string;
28
+ };
29
+ export interface TelemetryConfiguration {
30
+ product: ProductConfiguration;
31
+ propPrefix: string;
32
+ backend: BackendConfiguration;
33
+ }
34
+ export interface SplunkBackendConfiguration extends BaseBackendConfiguration {
35
+ type: 'splunk';
36
+ token?: string;
37
+ url?: string;
38
+ }
39
+ export interface ApplicationInsightsBackendConfiguration extends BaseBackendConfiguration {
40
+ type: 'application-insights';
41
+ instrumentationKey?: string;
42
+ }
43
+ export interface CustomBackendConfiguration extends BaseBackendConfiguration, TelemetryBackend {
44
+ type: 'custom';
45
+ sendEvent: (event: TelemetryData) => void;
46
+ flush: (callback?: FlushCallback) => void;
47
+ }
48
+ export type BackendConfiguration = CustomBackendConfiguration | ApplicationInsightsBackendConfiguration | SplunkBackendConfiguration;
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@appland/telemetry",
3
+ "version": "1.1.0",
4
+ "license": "MIT",
5
+ "scripts": {
6
+ "test": "jest",
7
+ "build": "tsc",
8
+ "lint": "eslint src --ext .ts",
9
+ "pre-commit": "lint-staged"
10
+ },
11
+ "main": "dist/index.js",
12
+ "types": "dist/index.d.ts",
13
+ "files": [
14
+ "dist",
15
+ "package.json",
16
+ "LICENSE.txt"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "lint-staged": {
22
+ "**/*": "prettier --write --ignore-unknown"
23
+ },
24
+ "dependencies": {
25
+ "applicationinsights": "^2.1.4",
26
+ "conf": "<11",
27
+ "read-pkg-up": "^7.0.1"
28
+ },
29
+ "devDependencies": {
30
+ "@types/jest": "^29.4.1",
31
+ "@types/node": "^22",
32
+ "@typescript-eslint/eslint-plugin": "^8.38.0",
33
+ "@typescript-eslint/parser": "^8.38.0",
34
+ "eslint": "^9.32.0",
35
+ "eslint-config-prettier": "^10.1.8",
36
+ "jest": "^29.5.0",
37
+ "lint-staged": "^10.5.4",
38
+ "prettier": "^2.7.1",
39
+ "sinon": "^13.0.1",
40
+ "ts-jest": "^29.0.5",
41
+ "typescript": "^5.8.3"
42
+ }
43
+ }