impulso 0.22.0 → 0.24.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/harnesses/claude/hooks/task-exit.cjs +106 -1
- package/harnesses/claude/hooks/tdd-observer.cjs +56 -11
- package/harnesses/claude/hooks/telematics.cjs +216 -0
- package/harnesses/opencode/bin/darwin-amd64/impulso-hub +0 -0
- package/harnesses/opencode/bin/darwin-arm64/impulso-hub +0 -0
- package/harnesses/opencode/bin/linux-amd64/impulso-hub +0 -0
- package/harnesses/opencode/bin/linux-arm64/impulso-hub +0 -0
- package/harnesses/opencode/plugin.js +4 -0
- package/harnesses/opencode/telematics/plugin.js +175 -0
- package/package.json +5 -2
- package/scripts/install-hub.js +44 -3
- package/shared/scout-dispatch.cjs +499 -0
- package/shared/telematics.cjs +444 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// impulso — shared secure telematics writer
|
|
3
|
+
//
|
|
4
|
+
// NDJSON append-only event log. Config is loaded once from
|
|
5
|
+
// <home>/config.json. All emit failures are silent —
|
|
6
|
+
// telematics must never throw or disrupt the harness.
|
|
7
|
+
//
|
|
8
|
+
// Interfaces:
|
|
9
|
+
// loadTelematicsConfig(home, _fs?) -> { enabled, sensitiveKeys }
|
|
10
|
+
// redact(value, sensitiveKeys) -> redacted copy
|
|
11
|
+
// sanitizeId(raw) -> string
|
|
12
|
+
// createTelematics({ harness, sessionId, processId, cwd, home,
|
|
13
|
+
// now, uuid, fs })
|
|
14
|
+
// -> { enabled, sessionId, emit(eventType, payload), close() }
|
|
15
|
+
|
|
16
|
+
const path = require('node:path');
|
|
17
|
+
const crypto = require('node:crypto');
|
|
18
|
+
|
|
19
|
+
// ----------------------------------------------------------------
|
|
20
|
+
// BUILTIN_SENSITIVE_KEYS — always active, case-insensitive.
|
|
21
|
+
// Matches common credential / token / secret key names across
|
|
22
|
+
// providers. Supplemented by config telematics.sensitiveKeys.
|
|
23
|
+
// ----------------------------------------------------------------
|
|
24
|
+
const BUILTIN_SENSITIVE_KEYS = [
|
|
25
|
+
'apiKey',
|
|
26
|
+
'api_key',
|
|
27
|
+
'authorization',
|
|
28
|
+
'token',
|
|
29
|
+
'accessToken',
|
|
30
|
+
'refreshToken',
|
|
31
|
+
'secret',
|
|
32
|
+
'password',
|
|
33
|
+
'passwd',
|
|
34
|
+
'credential',
|
|
35
|
+
'cookie',
|
|
36
|
+
'set-cookie',
|
|
37
|
+
'privateKey',
|
|
38
|
+
'clientSecret',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
// ----------------------------------------------------------------
|
|
42
|
+
// Pattern-based redaction. Applied to string values during walk.
|
|
43
|
+
// ----------------------------------------------------------------
|
|
44
|
+
const PATTERN_REDACTIONS = [
|
|
45
|
+
// Bearer tokens: "Bearer eyJ..." -> "Bearer [REDACTED]"
|
|
46
|
+
{ pattern: /\bBearer\s+([^\s"',;)}\]]+)/gi, replace: 'Bearer [REDACTED]' },
|
|
47
|
+
// OpenAI / similar sk- keys: sk-<40+ chars>
|
|
48
|
+
{ pattern: /\bsk-[a-zA-Z0-9_-]{20,}\b/g, replace: '[REDACTED_SK_KEY]' },
|
|
49
|
+
// GitHub classic tokens: ghp_xxx, gho_xxx, ghu_xxx, ghs_xxx, ghr_xxx
|
|
50
|
+
{ pattern: /\bgh[pousr]_[a-zA-Z0-9]{36,}\b/g, replace: '[REDACTED_GH_TOKEN]' },
|
|
51
|
+
// AWS access key IDs: AKIA... (20 uppercase alphanumeric)
|
|
52
|
+
{ pattern: /\bAKIA[A-Z0-9]{16}\b/g, replace: '[REDACTED_AWS_KEY]' },
|
|
53
|
+
// PEM private key blocks (detect header line)
|
|
54
|
+
{
|
|
55
|
+
pattern:
|
|
56
|
+
/-----BEGIN (?:RSA |EC |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY-----/g,
|
|
57
|
+
replace: '[REDACTED_PEM]',
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
// ----------------------------------------------------------------
|
|
62
|
+
// loadTelematicsConfig
|
|
63
|
+
//
|
|
64
|
+
// Reads <home>/config.json. Returns { enabled, sensitiveKeys }.
|
|
65
|
+
// Missing file, malformed JSON, absent telematics key,
|
|
66
|
+
// telematics.enabled !== true → disabled.
|
|
67
|
+
// telematics.sensitiveKeys: string array or absent.
|
|
68
|
+
// Unknown keys ignored. Never writes or repairs config.
|
|
69
|
+
// ----------------------------------------------------------------
|
|
70
|
+
function loadTelematicsConfig(homeDir, _fs, _path) {
|
|
71
|
+
const fs = _fs || require('node:fs');
|
|
72
|
+
const p = _path || path;
|
|
73
|
+
const configPath = p.join(homeDir, 'config.json');
|
|
74
|
+
try {
|
|
75
|
+
if (!fs.existsSync(configPath)) {
|
|
76
|
+
return { enabled: false, sensitiveKeys: [] };
|
|
77
|
+
}
|
|
78
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
79
|
+
const cfg = JSON.parse(raw);
|
|
80
|
+
if (!cfg || typeof cfg !== 'object') {
|
|
81
|
+
return { enabled: false, sensitiveKeys: [] };
|
|
82
|
+
}
|
|
83
|
+
const telematics = cfg.telematics;
|
|
84
|
+
if (!telematics || typeof telematics !== 'object') {
|
|
85
|
+
return { enabled: false, sensitiveKeys: [] };
|
|
86
|
+
}
|
|
87
|
+
if (telematics.enabled !== true) {
|
|
88
|
+
return { enabled: false, sensitiveKeys: [] };
|
|
89
|
+
}
|
|
90
|
+
// Parse sensitiveKeys: optional array of nonempty strings
|
|
91
|
+
let extra = [];
|
|
92
|
+
if (Array.isArray(telematics.sensitiveKeys)) {
|
|
93
|
+
extra = telematics.sensitiveKeys
|
|
94
|
+
.filter(function (k) {
|
|
95
|
+
return typeof k === 'string' && k.length > 0;
|
|
96
|
+
})
|
|
97
|
+
.map(function (k) {
|
|
98
|
+
return k.toLowerCase();
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return { enabled: true, sensitiveKeys: extra };
|
|
102
|
+
} catch (_) {
|
|
103
|
+
return { enabled: false, sensitiveKeys: [] };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ----------------------------------------------------------------
|
|
108
|
+
// sanitizeId
|
|
109
|
+
//
|
|
110
|
+
// Trims whitespace and strips everything except [A-Za-z0-9._-].
|
|
111
|
+
// Rejects `.`, `..`, and empty results to prevent path traversal
|
|
112
|
+
// when the ID is used as a directory name in path.join.
|
|
113
|
+
// Returns empty string when input is null/undefined or result is
|
|
114
|
+
// empty/rejected after filtering.
|
|
115
|
+
// ----------------------------------------------------------------
|
|
116
|
+
function sanitizeId(raw) {
|
|
117
|
+
if (raw == null || typeof raw !== 'string') return '';
|
|
118
|
+
const cleaned = raw.trim().replace(/[^A-Za-z0-9._-]/g, '');
|
|
119
|
+
// Reject dot, dot-dot, and empty — these cause path.join traversal
|
|
120
|
+
if (cleaned === '.' || cleaned === '..' || cleaned.length === 0) return '';
|
|
121
|
+
return cleaned;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ----------------------------------------------------------------
|
|
125
|
+
// redact
|
|
126
|
+
//
|
|
127
|
+
// Deep-walks value without mutation.
|
|
128
|
+
// 1. Key name matching (case-insensitive) → "[REDACTED]", no descent.
|
|
129
|
+
// 2. String pattern scanning for secrets (Bearer, sk-, GitHub, AWS, PEM).
|
|
130
|
+
// 3. key=value pairs: if key matches sensitive set, redact value.
|
|
131
|
+
// 4. JSON/query strings: URL query-style &key=value redaction.
|
|
132
|
+
// 5. Cycles, unsupported values, getters/proxies →
|
|
133
|
+
// "[UNSERIALIZABLE]"; never throws.
|
|
134
|
+
//
|
|
135
|
+
// Returns a redacted deep copy.
|
|
136
|
+
// ----------------------------------------------------------------
|
|
137
|
+
function redact(value, sensitiveKeys) {
|
|
138
|
+
// Built-ins always apply. Configured keys are additive.
|
|
139
|
+
const extraLen = Array.isArray(sensitiveKeys) ? sensitiveKeys.length : 0;
|
|
140
|
+
|
|
141
|
+
const keySet = new Set();
|
|
142
|
+
for (let i = 0; i < BUILTIN_SENSITIVE_KEYS.length; i++) {
|
|
143
|
+
keySet.add(BUILTIN_SENSITIVE_KEYS[i].toLowerCase());
|
|
144
|
+
}
|
|
145
|
+
for (let j = 0; j < extraLen; j++) {
|
|
146
|
+
keySet.add(String(sensitiveKeys[j]).toLowerCase());
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const seen = new WeakSet(); // cycle detection
|
|
150
|
+
|
|
151
|
+
function walk(v, depth) {
|
|
152
|
+
if (depth === undefined) depth = 0;
|
|
153
|
+
if (depth > 100) return '[UNSERIALIZABLE]'; // depth guard
|
|
154
|
+
if (v === null || v === undefined) return v;
|
|
155
|
+
if (typeof v !== 'object') {
|
|
156
|
+
if (typeof v === 'string') {
|
|
157
|
+
return redactString(v, keySet);
|
|
158
|
+
}
|
|
159
|
+
return v;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Cycle / unsupported check
|
|
163
|
+
if (typeof v === 'object') {
|
|
164
|
+
try {
|
|
165
|
+
// Detect getters/proxies by attempting key enumeration
|
|
166
|
+
Object.keys(v);
|
|
167
|
+
} catch (_) {
|
|
168
|
+
return '[UNSERIALIZABLE]';
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
if (seen.has(v)) return '[UNSERIALIZABLE]';
|
|
172
|
+
seen.add(v);
|
|
173
|
+
} catch (_) {
|
|
174
|
+
return '[UNSERIALIZABLE]';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (Array.isArray(v)) {
|
|
179
|
+
const arrOut = [];
|
|
180
|
+
for (let ai = 0; ai < v.length; ai++) {
|
|
181
|
+
try {
|
|
182
|
+
arrOut.push(walk(v[ai], depth + 1));
|
|
183
|
+
} catch (_) {
|
|
184
|
+
arrOut.push('[UNSERIALIZABLE]');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return arrOut;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Object
|
|
191
|
+
const out = {};
|
|
192
|
+
let keys;
|
|
193
|
+
try {
|
|
194
|
+
keys = Object.keys(v);
|
|
195
|
+
} catch (_) {
|
|
196
|
+
return '[UNSERIALIZABLE]';
|
|
197
|
+
}
|
|
198
|
+
for (let ki = 0; ki < keys.length; ki++) {
|
|
199
|
+
const k = keys[ki];
|
|
200
|
+
const klower = String(k).toLowerCase();
|
|
201
|
+
try {
|
|
202
|
+
if (keySet.has(klower)) {
|
|
203
|
+
out[k] = '[REDACTED]';
|
|
204
|
+
} else {
|
|
205
|
+
out[k] = walk(v[k], depth + 1);
|
|
206
|
+
}
|
|
207
|
+
} catch (_) {
|
|
208
|
+
out[k] = '[UNSERIALIZABLE]';
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return out;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return walk(value);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ----------------------------------------------------------------
|
|
218
|
+
// redactString — apply pattern redactions to string values
|
|
219
|
+
// ----------------------------------------------------------------
|
|
220
|
+
function redactString(str, keySet) {
|
|
221
|
+
let result = str;
|
|
222
|
+
for (let i = 0; i < PATTERN_REDACTIONS.length; i++) {
|
|
223
|
+
result = result.replace(PATTERN_REDACTIONS[i].pattern, PATTERN_REDACTIONS[i].replace);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Redact key=value pairs where key matches sensitive set.
|
|
227
|
+
// Matches sequences like: key=value, key="value", key='value'
|
|
228
|
+
// in URLs, headers, JSON-embedded query strings.
|
|
229
|
+
const eqPattern = /([\w.-]+)\s*=\s*("[^"]*"|'[^']*'|[^\s&;,}'"\]]+)/gi;
|
|
230
|
+
result = result.replace(eqPattern, function (_match, key, _value) {
|
|
231
|
+
const lower = key.toLowerCase();
|
|
232
|
+
if (keySet.has(lower)) {
|
|
233
|
+
return key + '=[REDACTED]';
|
|
234
|
+
}
|
|
235
|
+
return _match;
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ----------------------------------------------------------------
|
|
242
|
+
// createTelematics
|
|
243
|
+
//
|
|
244
|
+
// Factory. Loads config exactly once during construction. Returns
|
|
245
|
+
// a telematics instance:
|
|
246
|
+
//
|
|
247
|
+
// .enabled boolean — false when config absent/disabled
|
|
248
|
+
// .sessionId string | null — sanitised session id (null when disabled)
|
|
249
|
+
// .emit(eventType, payload) writes a single NDJSON envelope; never throws
|
|
250
|
+
// .close() closes the underlying fd; never throws
|
|
251
|
+
//
|
|
252
|
+
// Envelope keys per spec:
|
|
253
|
+
// schemaVersion — integer 1
|
|
254
|
+
// eventId — UUID per event
|
|
255
|
+
// eventType — string
|
|
256
|
+
// timestamp — UTC ISO-8601
|
|
257
|
+
// monotonicMs — ms since construction
|
|
258
|
+
// harness — "claude" | "opencode"
|
|
259
|
+
// sessionId — sanitised session id
|
|
260
|
+
// processId — numeric PID
|
|
261
|
+
// cwd — current working directory
|
|
262
|
+
// payload — redacted event payload
|
|
263
|
+
// ----------------------------------------------------------------
|
|
264
|
+
function createTelematics(deps) {
|
|
265
|
+
const harness = deps.harness;
|
|
266
|
+
const sessionId = deps.sessionId;
|
|
267
|
+
const processId = deps.processId;
|
|
268
|
+
const cwd = deps.cwd;
|
|
269
|
+
const home = deps.home;
|
|
270
|
+
const now = deps.now;
|
|
271
|
+
const uuidFn = deps.uuid;
|
|
272
|
+
const fs = deps.fs;
|
|
273
|
+
|
|
274
|
+
// One-time config load
|
|
275
|
+
const cfg = loadTelematicsConfig(home, fs, path);
|
|
276
|
+
|
|
277
|
+
if (!cfg.enabled) {
|
|
278
|
+
return {
|
|
279
|
+
enabled: false,
|
|
280
|
+
sessionId: null,
|
|
281
|
+
emit: function () {
|
|
282
|
+
/* noop */
|
|
283
|
+
},
|
|
284
|
+
close: function () {
|
|
285
|
+
/* noop */
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Build effective sensitive key set
|
|
291
|
+
const sensitiveKeys = [];
|
|
292
|
+
for (let i = 0; i < BUILTIN_SENSITIVE_KEYS.length; i++) {
|
|
293
|
+
if (sensitiveKeys.indexOf(BUILTIN_SENSITIVE_KEYS[i].toLowerCase()) === -1) {
|
|
294
|
+
sensitiveKeys.push(BUILTIN_SENSITIVE_KEYS[i].toLowerCase());
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (Array.isArray(cfg.sensitiveKeys)) {
|
|
298
|
+
for (let j = 0; j < cfg.sensitiveKeys.length; j++) {
|
|
299
|
+
const ek = String(cfg.sensitiveKeys[j]).toLowerCase();
|
|
300
|
+
if (sensitiveKeys.indexOf(ek) === -1) {
|
|
301
|
+
sensitiveKeys.push(ek);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Sanitize sessionId or generate one
|
|
307
|
+
let sid = sanitizeId(sessionId);
|
|
308
|
+
if (!sid) {
|
|
309
|
+
if (typeof uuidFn === 'function') {
|
|
310
|
+
sid = sanitizeId(String(uuidFn()));
|
|
311
|
+
}
|
|
312
|
+
if (!sid) {
|
|
313
|
+
sid = 'gen-' + Date.now() + '-' + Math.random().toString(36).slice(2, 10);
|
|
314
|
+
sid = sanitizeId(sid);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (!sid) {
|
|
318
|
+
sid = 'gen-' + Date.now();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Construction timestamp for monotonicMs baseline
|
|
322
|
+
const startMs = Date.now();
|
|
323
|
+
|
|
324
|
+
// Log directory: <home>/logs/<sanitized-session-id>/
|
|
325
|
+
const logDir = path.join(home, 'logs', sid);
|
|
326
|
+
const logFile = path.join(logDir, 'events.ndjson');
|
|
327
|
+
|
|
328
|
+
let fd = null;
|
|
329
|
+
let ensured = false;
|
|
330
|
+
|
|
331
|
+
// ----------------------------------------------------------------
|
|
332
|
+
// ensureLogDir — create dir mode 0700, open file mode 0600
|
|
333
|
+
// Refuses symlinks, nonregular files, and paths that resolve
|
|
334
|
+
// outside <home>/logs/. Fails closed: if we can't secure it,
|
|
335
|
+
// telemetry stays disabled for the process (emit becomes noop
|
|
336
|
+
// after failed ensure).
|
|
337
|
+
// ----------------------------------------------------------------
|
|
338
|
+
function ensureLogDir() {
|
|
339
|
+
if (ensured) return fd !== null;
|
|
340
|
+
ensured = true;
|
|
341
|
+
try {
|
|
342
|
+
// Resolve logDir and verify it stays under <home>/logs/
|
|
343
|
+
const resolved = path.resolve(logDir);
|
|
344
|
+
const logsPrefix = path.resolve(home, 'logs') + path.sep;
|
|
345
|
+
if (!resolved.startsWith(logsPrefix)) {
|
|
346
|
+
return false; // path escaped the logs sandbox — fail closed
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Create log directory chain with 0700
|
|
350
|
+
fs.mkdirSync(logDir, { recursive: true, mode: 0o700 });
|
|
351
|
+
// Explicit chmod to counter umask
|
|
352
|
+
fs.chmodSync(logDir, 0o700);
|
|
353
|
+
|
|
354
|
+
// Check if log file exists and is regular + not symlink
|
|
355
|
+
let stat;
|
|
356
|
+
let exists = false;
|
|
357
|
+
try {
|
|
358
|
+
stat = fs.lstatSync(logFile);
|
|
359
|
+
exists = true;
|
|
360
|
+
} catch (_) {
|
|
361
|
+
/* file does not exist — ok */
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (exists) {
|
|
365
|
+
// Refuse nonregular targets or symlinks
|
|
366
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
367
|
+
return false; // fail closed
|
|
368
|
+
}
|
|
369
|
+
// Chmod existing file before use
|
|
370
|
+
fs.chmodSync(logFile, 0o600);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Open for append
|
|
374
|
+
fd = fs.openSync(logFile, 'a', 0o600);
|
|
375
|
+
// Chmod again after open (best-effort)
|
|
376
|
+
try {
|
|
377
|
+
fs.fchmodSync(fd, 0o600);
|
|
378
|
+
} catch (_) {
|
|
379
|
+
/* best effort */
|
|
380
|
+
}
|
|
381
|
+
return true;
|
|
382
|
+
} catch (_) {
|
|
383
|
+
return false; // fail closed
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function emit(eventType, payload) {
|
|
388
|
+
try {
|
|
389
|
+
if (!ensureLogDir()) return;
|
|
390
|
+
const monotonicMs = Date.now() - startMs;
|
|
391
|
+
let eventId;
|
|
392
|
+
try {
|
|
393
|
+
eventId = uuidFn ? String(uuidFn()) : crypto.randomUUID();
|
|
394
|
+
} catch (_) {
|
|
395
|
+
eventId = 'fallback-' + Date.now() + '-' + Math.random().toString(36).slice(2, 10);
|
|
396
|
+
}
|
|
397
|
+
const redactedPayload = redact(payload, sensitiveKeys);
|
|
398
|
+
const envelope = {
|
|
399
|
+
schemaVersion: 1,
|
|
400
|
+
eventId: eventId,
|
|
401
|
+
eventType: eventType,
|
|
402
|
+
timestamp: typeof now === 'function' ? now() : new Date().toISOString(),
|
|
403
|
+
monotonicMs: monotonicMs,
|
|
404
|
+
harness: harness,
|
|
405
|
+
sessionId: sid,
|
|
406
|
+
processId: processId,
|
|
407
|
+
cwd: cwd,
|
|
408
|
+
payload: redactedPayload,
|
|
409
|
+
};
|
|
410
|
+
const line = JSON.stringify(envelope) + '\n';
|
|
411
|
+
if (fd !== null) {
|
|
412
|
+
fs.writeSync(fd, line);
|
|
413
|
+
}
|
|
414
|
+
} catch (_) {
|
|
415
|
+
// Silent — telematics must never throw
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function close() {
|
|
420
|
+
if (fd !== null) {
|
|
421
|
+
try {
|
|
422
|
+
fs.closeSync(fd);
|
|
423
|
+
} catch (_) {
|
|
424
|
+
/* silent */
|
|
425
|
+
}
|
|
426
|
+
fd = null;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
enabled: true,
|
|
432
|
+
sessionId: sid,
|
|
433
|
+
emit: emit,
|
|
434
|
+
close: close,
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
module.exports = {
|
|
439
|
+
createTelematics: createTelematics,
|
|
440
|
+
redact: redact,
|
|
441
|
+
loadTelematicsConfig: loadTelematicsConfig,
|
|
442
|
+
sanitizeId: sanitizeId,
|
|
443
|
+
BUILTIN_SENSITIVE_KEYS: BUILTIN_SENSITIVE_KEYS,
|
|
444
|
+
};
|