-
Notifications
You must be signed in to change notification settings - Fork 55
Expand file tree
/
Copy pathtelemetryReporter.ts
More file actions
137 lines (128 loc) · 5.48 KB
/
telemetryReporter.ts
File metadata and controls
137 lines (128 loc) · 5.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/
import type { IPayloadData, IXHROverride } from "@microsoft/1ds-post-js";
import * as https from "https";
import * as os from "os";
import * as vscode from "vscode";
import { oneDataSystemClientFactory } from "../common/1dsClientFactory";
import { AppInsightsClientOptions, appInsightsClientFactory } from "../common/appInsightsClientFactory";
import { BaseTelemetryReporter, ReplacementOption } from "../common/baseTelemetryReporter";
import { BaseTelemetrySender } from "../common/baseTelemetrySender";
import { TelemetryUtil } from "../common/util";
// Re-export AppInsightsClientOptions for consumers
export type { AppInsightsClientOptions } from "../common/appInsightsClientFactory";
/**
* A custom fetcher function that can be used to send telemetry data.
* Compatible with the Node.js fetch API signature.
* @param url The URL to send the request to
* @param init The request initialization options including method, headers, and body
* @returns A promise that resolves to a Response object
*/
export type CustomFetcher = (
url: string,
init?: { method: "POST"; headers?: Record<string, string>; body?: string }
) => Promise<{text: () => Promise<string>; status: number; headers: Iterable<[string, string]>}>;
/**
* Create a replacement for the XHTMLRequest object utilizing nodes HTTP module.
* @returns A XHR override object used to override the XHTMLRequest object in the 1DS SDK
*/
function getDefaultXHROverride(): IXHROverride {
// Override the way events get sent since node doesn't have XHTMLRequest
const customHttpXHROverride: IXHROverride = {
sendPOST: (payload: IPayloadData, oncomplete) => {
const options = {
method: "POST",
headers: {
...payload.headers,
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(payload.data)
}
};
try {
const req = https.request(payload.urlString, options, res => {
res.on("data", function (responseData) {
oncomplete(res.statusCode ?? 200, res.headers as Record<string, string>, responseData.toString());
});
// On response with error send status of 0 and a blank response to oncomplete so we can retry events
res.on("error", function () {
oncomplete(0, {});
});
});
req.write(payload.data, (err) => {
if (err) {
oncomplete(0, {});
}
});
req.end();
} catch {
// If it errors out, send status of 0 and a blank response to oncomplete so we can retry events
oncomplete(0, {});
}
}
};
return customHttpXHROverride;
}
/**
* Create an XHR override from a custom fetcher function.
* @param fetcher The custom fetcher function to use for sending telemetry data
* @returns A XHR override object used to override the XHTMLRequest object in the 1DS SDK
*/
function createXHROverrideFromFetcher(fetcher: CustomFetcher): IXHROverride {
const xhrOverride: IXHROverride = {
sendPOST: (payload: IPayloadData, oncomplete) => {
const dataString = typeof payload.data === "string" ? payload.data : Buffer.from(payload.data).toString();
const headers: Record<string, string> = { ...payload.headers };
// if the content-type header isn't already set, default it to application/json
if (!Object.keys(headers).some(k => k.toLowerCase() === "content-type")) {
headers["Content-Type"] = "application/json";
}
fetcher(payload.urlString, { method: "POST", headers, body: dataString })
.then(async (response) => {
const responseHeaders: Record<string, string> = {};
for (const [key, value] of response.headers) {
responseHeaders[key] = value;
}
const body = await response.text();
oncomplete(response.status, responseHeaders, body);
})
.catch(() => {
// If it errors out, send status of 0 and a blank response to oncomplete so we can retry events
oncomplete(0, {});
});
}
};
return xhrOverride;
}
export class TelemetryReporter extends BaseTelemetryReporter {
constructor(
connectionString: string,
replacementOptions?: ReplacementOption[],
initializationOptions?: vscode.TelemetryLoggerOptions,
customFetcher?: CustomFetcher,
appInsightsOptions?: AppInsightsClientOptions
) {
const xhrOverride = customFetcher ? createXHROverrideFromFetcher(customFetcher) : getDefaultXHROverride();
let clientFactory = (connectionString: string) => appInsightsClientFactory(connectionString, vscode.env.machineId, vscode.env.sessionId, xhrOverride, replacementOptions, appInsightsOptions);
// If connection string is usable by 1DS use the 1DS SDk
if (TelemetryUtil.shouldUseOneDataSystemSDK(connectionString)) {
clientFactory = (key: string) => oneDataSystemClientFactory(key, vscode, xhrOverride);
}
const osShim = {
release: os.release(),
platform: os.platform(),
architecture: os.arch(),
};
const sender = new BaseTelemetrySender(connectionString, clientFactory,);
if (connectionString && connectionString.indexOf("AIF-") === 0) {
throw new Error("AIF keys are no longer supported. Please switch to 1DS keys for 1st party extensions");
}
const initializationOpts = {
...initializationOptions,
additionalCommonProperties: initializationOptions?.additionalCommonProperties ?
{ ...initializationOptions.additionalCommonProperties, ...TelemetryUtil.getAdditionalCommonProperties(osShim) }
: TelemetryUtil.getAdditionalCommonProperties(osShim)
};
super(sender, vscode, initializationOpts);
}
}