@traffic-twin/sdk 0.0.0-stage → 0.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/LICENSE +202 -0
- package/README.md +171 -2
- package/dist/cjs/agent.d.ts +143 -0
- package/dist/cjs/agent.js +927 -0
- package/dist/cjs/capture/body.d.ts +11 -0
- package/dist/cjs/capture/body.js +73 -0
- package/dist/cjs/capture/common.d.ts +75 -0
- package/dist/cjs/capture/common.js +180 -0
- package/dist/cjs/capture/context.d.ts +28 -0
- package/dist/cjs/capture/context.js +92 -0
- package/dist/cjs/capture/inbound.d.ts +12 -0
- package/dist/cjs/capture/inbound.js +195 -0
- package/dist/cjs/capture/outbound-http.d.ts +4 -0
- package/dist/cjs/capture/outbound-http.js +200 -0
- package/dist/cjs/capture/outbound-undici.d.ts +2 -0
- package/dist/cjs/capture/outbound-undici.js +242 -0
- package/dist/cjs/capture/wire.d.ts +27 -0
- package/dist/cjs/capture/wire.js +68 -0
- package/dist/cjs/export/event.d.ts +46 -0
- package/dist/cjs/export/event.js +91 -0
- package/dist/cjs/export/exporter.d.ts +50 -0
- package/dist/cjs/export/exporter.js +192 -0
- package/dist/cjs/export/pipeline.d.ts +20 -0
- package/dist/cjs/export/pipeline.js +40 -0
- package/dist/cjs/fleet/asn1.d.ts +44 -0
- package/dist/cjs/fleet/asn1.js +150 -0
- package/dist/cjs/fleet/client.d.ts +84 -0
- package/dist/cjs/fleet/client.js +216 -0
- package/dist/cjs/fleet/http.d.ts +17 -0
- package/dist/cjs/fleet/http.js +80 -0
- package/dist/cjs/fleet/store.d.ts +18 -0
- package/dist/cjs/fleet/store.js +115 -0
- package/dist/cjs/index.d.ts +14 -0
- package/dist/cjs/index.js +69 -0
- package/dist/cjs/log.d.ts +4 -0
- package/dist/cjs/log.js +30 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/register.d.ts +1 -0
- package/dist/cjs/register.js +18 -0
- package/dist/cjs/sanitizer/bodies.d.ts +55 -0
- package/dist/cjs/sanitizer/bodies.js +710 -0
- package/dist/cjs/sanitizer/formats.d.ts +26 -0
- package/dist/cjs/sanitizer/formats.js +110 -0
- package/dist/cjs/sanitizer/graphql.d.ts +23 -0
- package/dist/cjs/sanitizer/graphql.js +478 -0
- package/dist/cjs/sanitizer/hostpat.d.ts +15 -0
- package/dist/cjs/sanitizer/hostpat.js +129 -0
- package/dist/cjs/sanitizer/json.d.ts +60 -0
- package/dist/cjs/sanitizer/json.js +460 -0
- package/dist/cjs/sanitizer/policy.d.ts +224 -0
- package/dist/cjs/sanitizer/policy.js +820 -0
- package/dist/cjs/sanitizer/sanitizer.d.ts +149 -0
- package/dist/cjs/sanitizer/sanitizer.js +1473 -0
- package/dist/cjs/sanitizer/utf8.d.ts +22 -0
- package/dist/cjs/sanitizer/utf8.js +180 -0
- package/dist/cjs/sanitizer/vectors.d.ts +22 -0
- package/dist/cjs/sanitizer/vectors.js +130 -0
- package/dist/cjs/version.d.ts +2 -0
- package/dist/cjs/version.js +5 -0
- package/dist/cjs/worker/dir.d.ts +1 -0
- package/dist/cjs/worker/dir.js +8 -0
- package/dist/cjs/worker/pool.d.ts +47 -0
- package/dist/cjs/worker/pool.js +179 -0
- package/dist/cjs/worker/protocol.d.ts +37 -0
- package/dist/cjs/worker/protocol.js +3 -0
- package/dist/cjs/worker/sanitizer-worker.d.ts +1 -0
- package/dist/cjs/worker/sanitizer-worker.js +53 -0
- package/dist/esm/agent.d.ts +143 -0
- package/dist/esm/agent.js +921 -0
- package/dist/esm/capture/body.d.ts +11 -0
- package/dist/esm/capture/body.js +69 -0
- package/dist/esm/capture/common.d.ts +75 -0
- package/dist/esm/capture/common.js +166 -0
- package/dist/esm/capture/context.d.ts +28 -0
- package/dist/esm/capture/context.js +84 -0
- package/dist/esm/capture/inbound.d.ts +12 -0
- package/dist/esm/capture/inbound.js +187 -0
- package/dist/esm/capture/outbound-http.d.ts +4 -0
- package/dist/esm/capture/outbound-http.js +193 -0
- package/dist/esm/capture/outbound-undici.d.ts +2 -0
- package/dist/esm/capture/outbound-undici.js +236 -0
- package/dist/esm/capture/wire.d.ts +27 -0
- package/dist/esm/capture/wire.js +65 -0
- package/dist/esm/export/event.d.ts +46 -0
- package/dist/esm/export/event.js +85 -0
- package/dist/esm/export/exporter.d.ts +50 -0
- package/dist/esm/export/exporter.js +188 -0
- package/dist/esm/export/pipeline.d.ts +20 -0
- package/dist/esm/export/pipeline.js +37 -0
- package/dist/esm/fleet/asn1.d.ts +44 -0
- package/dist/esm/fleet/asn1.js +128 -0
- package/dist/esm/fleet/client.d.ts +84 -0
- package/dist/esm/fleet/client.js +204 -0
- package/dist/esm/fleet/http.d.ts +17 -0
- package/dist/esm/fleet/http.js +73 -0
- package/dist/esm/fleet/store.d.ts +18 -0
- package/dist/esm/fleet/store.js +110 -0
- package/dist/esm/index.d.ts +14 -0
- package/dist/esm/index.js +48 -0
- package/dist/esm/log.d.ts +4 -0
- package/dist/esm/log.js +25 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/register.d.ts +1 -0
- package/dist/esm/register.js +16 -0
- package/dist/esm/sanitizer/bodies.d.ts +55 -0
- package/dist/esm/sanitizer/bodies.js +694 -0
- package/dist/esm/sanitizer/formats.d.ts +26 -0
- package/dist/esm/sanitizer/formats.js +101 -0
- package/dist/esm/sanitizer/graphql.d.ts +23 -0
- package/dist/esm/sanitizer/graphql.js +472 -0
- package/dist/esm/sanitizer/hostpat.d.ts +15 -0
- package/dist/esm/sanitizer/hostpat.js +122 -0
- package/dist/esm/sanitizer/json.d.ts +60 -0
- package/dist/esm/sanitizer/json.js +447 -0
- package/dist/esm/sanitizer/policy.d.ts +224 -0
- package/dist/esm/sanitizer/policy.js +800 -0
- package/dist/esm/sanitizer/sanitizer.d.ts +149 -0
- package/dist/esm/sanitizer/sanitizer.js +1459 -0
- package/dist/esm/sanitizer/utf8.d.ts +22 -0
- package/dist/esm/sanitizer/utf8.js +166 -0
- package/dist/esm/sanitizer/vectors.d.ts +22 -0
- package/dist/esm/sanitizer/vectors.js +125 -0
- package/dist/esm/version.d.ts +2 -0
- package/dist/esm/version.js +2 -0
- package/dist/esm/worker/dir.d.ts +1 -0
- package/dist/esm/worker/dir.js +5 -0
- package/dist/esm/worker/pool.d.ts +47 -0
- package/dist/esm/worker/pool.js +175 -0
- package/dist/esm/worker/protocol.d.ts +37 -0
- package/dist/esm/worker/protocol.js +2 -0
- package/dist/esm/worker/sanitizer-worker.d.ts +1 -0
- package/dist/esm/worker/sanitizer-worker.js +51 -0
- package/package.json +75 -4
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Fleet API client (api/fleet-api.v1.openapi.yaml): enrollment with a `tte_` token and CA
|
|
3
|
+
// pin, then mTLS calls for heartbeat, policy and certificate renewal.
|
|
4
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
6
|
+
};
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.FleetClient = exports.FleetError = exports.TOKEN_RE = void 0;
|
|
9
|
+
exports.tokenPin = tokenPin;
|
|
10
|
+
exports.fetchPinnedRoot = fetchPinnedRoot;
|
|
11
|
+
exports.enroll = enroll;
|
|
12
|
+
exports.mtlsAgent = mtlsAgent;
|
|
13
|
+
const node_https_1 = __importDefault(require("node:https"));
|
|
14
|
+
const node_tls_1 = __importDefault(require("node:tls"));
|
|
15
|
+
const node_crypto_1 = require("node:crypto");
|
|
16
|
+
const asn1_js_1 = require("./asn1.js");
|
|
17
|
+
const http_js_1 = require("./http.js");
|
|
18
|
+
const common_js_1 = require("../capture/common.js");
|
|
19
|
+
exports.TOKEN_RE = /^tte_[A-Za-z0-9_-]{32,128}\.([A-Za-z0-9_-]{43})$/;
|
|
20
|
+
class FleetError extends Error {
|
|
21
|
+
code;
|
|
22
|
+
status;
|
|
23
|
+
constructor(code, status) {
|
|
24
|
+
super(code);
|
|
25
|
+
this.code = code;
|
|
26
|
+
this.status = status;
|
|
27
|
+
this.name = 'FleetError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.FleetError = FleetError;
|
|
31
|
+
/** Extracts the CA pin from an enrollment token. */
|
|
32
|
+
function tokenPin(token) {
|
|
33
|
+
const m = exports.TOKEN_RE.exec(token);
|
|
34
|
+
return m ? m[1] : null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Opens a TLS connection without trusting anything, walks the chain the server presents
|
|
38
|
+
* to its top (the root, which tt-core must include) and checks it against the pin.
|
|
39
|
+
* Returns the root as PEM, to be used as the only trust anchor for the real request.
|
|
40
|
+
*/
|
|
41
|
+
function fetchPinnedRoot(url, pin, timeoutMs = 10_000) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const host = url.hostname.replace(/^\[|\]$/g, '');
|
|
44
|
+
const sock = node_tls_1.default.connect({
|
|
45
|
+
host,
|
|
46
|
+
port: Number(url.port || 443),
|
|
47
|
+
servername: /^[0-9.]+$|:/.test(host) ? undefined : host,
|
|
48
|
+
rejectUnauthorized: false,
|
|
49
|
+
});
|
|
50
|
+
const fail = (code) => {
|
|
51
|
+
sock.destroy();
|
|
52
|
+
reject(new FleetError(code));
|
|
53
|
+
};
|
|
54
|
+
sock.setTimeout(timeoutMs, () => fail('enroll_tls_timeout'));
|
|
55
|
+
sock.once('error', () => fail('enroll_tls_error'));
|
|
56
|
+
sock.once('secureConnect', () => {
|
|
57
|
+
try {
|
|
58
|
+
let c = sock.getPeerCertificate(true);
|
|
59
|
+
if (!c || !c.raw)
|
|
60
|
+
return fail('enroll_no_peer_cert');
|
|
61
|
+
for (let i = 0; i < 16; i++) {
|
|
62
|
+
const up = c.issuerCertificate;
|
|
63
|
+
if (!up || up === c || up.fingerprint256 === c.fingerprint256 || !up.raw)
|
|
64
|
+
break;
|
|
65
|
+
c = up;
|
|
66
|
+
}
|
|
67
|
+
const root = new node_crypto_1.X509Certificate(c.raw);
|
|
68
|
+
if (!root.ca || root.issuer !== root.subject)
|
|
69
|
+
return fail('enroll_root_missing');
|
|
70
|
+
if ((0, asn1_js_1.pinOf)(c.raw) !== pin)
|
|
71
|
+
return fail('enroll_pin_mismatch');
|
|
72
|
+
sock.end();
|
|
73
|
+
resolve((0, asn1_js_1.pem)('CERTIFICATE', c.raw));
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
fail('enroll_pin_check');
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function isHttpsUrl(v) {
|
|
82
|
+
try {
|
|
83
|
+
return typeof v === 'string' && new URL(v).protocol === 'https:';
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Enrolls with a token. Returns the enrollment and the private key PEM (generated locally when useCsr). */
|
|
90
|
+
async function enroll(o) {
|
|
91
|
+
const pin = tokenPin(o.token);
|
|
92
|
+
if (!pin)
|
|
93
|
+
throw new FleetError('enroll_token_malformed');
|
|
94
|
+
const base = new URL(o.enrollUrl);
|
|
95
|
+
if (base.protocol !== 'https:')
|
|
96
|
+
throw new FleetError('enroll_url_not_https');
|
|
97
|
+
const rootPem = await fetchPinnedRoot(base, pin, o.timeoutMs);
|
|
98
|
+
const agent = new node_https_1.default.Agent({ ca: rootPem, keepAlive: false });
|
|
99
|
+
common_js_1.internalAgents.add(agent);
|
|
100
|
+
let keyPem = '';
|
|
101
|
+
const body = { token: o.token, source: o.source };
|
|
102
|
+
if (o.useCsr) {
|
|
103
|
+
const g = (0, asn1_js_1.generateCsr)(o.source.service_name);
|
|
104
|
+
keyPem = g.keyPem;
|
|
105
|
+
body.csr = g.csrPem;
|
|
106
|
+
}
|
|
107
|
+
const url = new URL('/fleet/v1/enroll', base).toString();
|
|
108
|
+
let res;
|
|
109
|
+
try {
|
|
110
|
+
res = await (0, http_js_1.request)({ method: 'POST', url, agent, headers: { 'content-type': 'application/json' }, body: JSON.stringify(body), timeoutMs: o.timeoutMs });
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
throw new FleetError('enroll_network');
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
agent.destroy();
|
|
117
|
+
}
|
|
118
|
+
if (res.status !== 201 && res.status !== 200)
|
|
119
|
+
throw new FleetError('enroll_http_' + res.status, res.status);
|
|
120
|
+
const j = (0, http_js_1.parseJsonBody)(res);
|
|
121
|
+
if (!j || typeof j !== 'object')
|
|
122
|
+
throw new FleetError('enroll_bad_response');
|
|
123
|
+
if (!o.useCsr) {
|
|
124
|
+
if (typeof j.client_key !== 'string')
|
|
125
|
+
throw new FleetError('enroll_no_key');
|
|
126
|
+
keyPem = j.client_key;
|
|
127
|
+
}
|
|
128
|
+
const keys = Array.isArray(j.policy_signing_keys) ? j.policy_signing_keys : [];
|
|
129
|
+
const enrollment = {
|
|
130
|
+
source_id: String(j.source_id ?? ''),
|
|
131
|
+
group: String(j.group ?? ''),
|
|
132
|
+
service_name: String(j.service_name ?? ''),
|
|
133
|
+
client_cert: String(j.client_cert ?? ''),
|
|
134
|
+
ca_chain: String(j.ca_chain ?? ''),
|
|
135
|
+
not_after: typeof j.not_after === 'string' ? j.not_after : undefined,
|
|
136
|
+
policy_signing_keys: keys
|
|
137
|
+
.filter((k) => typeof k?.kid === 'string' && /^[A-Za-z0-9._-]{1,64}$/.test(k.kid) && k.alg === 'Ed25519' &&
|
|
138
|
+
typeof k.public_key === 'string' && /^[A-Za-z0-9_-]{43}$/.test(k.public_key))
|
|
139
|
+
.map((k) => ({ kid: k.kid, alg: 'Ed25519', public_key: k.public_key })),
|
|
140
|
+
intake_url: String(j.intake_url ?? ''),
|
|
141
|
+
fleet_url: String(j.fleet_url ?? ''),
|
|
142
|
+
policy_max_age_s: typeof j.policy_max_age_s === 'number' ? j.policy_max_age_s : 86400,
|
|
143
|
+
heartbeat_interval_s: typeof j.heartbeat_interval_s === 'number' ? j.heartbeat_interval_s : 30,
|
|
144
|
+
policy_poll_interval_s: typeof j.policy_poll_interval_s === 'number' ? j.policy_poll_interval_s : 60,
|
|
145
|
+
enroll_url: o.enrollUrl,
|
|
146
|
+
pin,
|
|
147
|
+
};
|
|
148
|
+
if (!/^src-[0-9A-Za-z]{1,32}$/.test(enrollment.source_id))
|
|
149
|
+
throw new FleetError('enroll_bad_source_id');
|
|
150
|
+
if (!/^[a-z0-9][a-z0-9-]{0,62}$/.test(enrollment.group))
|
|
151
|
+
throw new FleetError('enroll_bad_group');
|
|
152
|
+
if (!/^[a-z0-9][a-z0-9._-]{0,62}$/.test(enrollment.service_name))
|
|
153
|
+
throw new FleetError('enroll_bad_service_name');
|
|
154
|
+
if ((0, asn1_js_1.pemBlocks)(enrollment.client_cert, 'CERTIFICATE').length === 0)
|
|
155
|
+
throw new FleetError('enroll_bad_cert');
|
|
156
|
+
if (!(0, asn1_js_1.pemBlocks)(enrollment.ca_chain, 'CERTIFICATE').some((d) => (0, asn1_js_1.pinOf)(d) === pin))
|
|
157
|
+
throw new FleetError('enroll_chain_without_pinned_root');
|
|
158
|
+
if (enrollment.policy_signing_keys.length === 0)
|
|
159
|
+
throw new FleetError('enroll_no_signing_keys');
|
|
160
|
+
if (!isHttpsUrl(enrollment.intake_url) || !isHttpsUrl(enrollment.fleet_url))
|
|
161
|
+
throw new FleetError('enroll_bad_urls');
|
|
162
|
+
return { enrollment, keyPem };
|
|
163
|
+
}
|
|
164
|
+
/** mTLS agent for the intake and Fleet calls. Trust anchors: the CA chain received over the pinned channel. */
|
|
165
|
+
function mtlsAgent(e, keyPem) {
|
|
166
|
+
const a = new node_https_1.default.Agent({ cert: e.client_cert, key: keyPem, ca: e.ca_chain, keepAlive: true, maxSockets: 2 });
|
|
167
|
+
common_js_1.internalAgents.add(a);
|
|
168
|
+
return a;
|
|
169
|
+
}
|
|
170
|
+
class FleetClient {
|
|
171
|
+
e;
|
|
172
|
+
agent;
|
|
173
|
+
constructor(e, agent) {
|
|
174
|
+
this.e = e;
|
|
175
|
+
this.agent = agent;
|
|
176
|
+
}
|
|
177
|
+
setAgent(agent) {
|
|
178
|
+
this.agent = agent;
|
|
179
|
+
}
|
|
180
|
+
url(path) {
|
|
181
|
+
return new URL(path, this.e.fleet_url).toString();
|
|
182
|
+
}
|
|
183
|
+
async heartbeat(body) {
|
|
184
|
+
const r = await (0, http_js_1.request)({ method: 'POST', url: this.url('/fleet/v1/heartbeat'), agent: this.agent, headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });
|
|
185
|
+
return { status: r.status, json: r.status === 200 ? (0, http_js_1.parseJsonBody)(r) : undefined };
|
|
186
|
+
}
|
|
187
|
+
async getPolicy(etag) {
|
|
188
|
+
const headers = {};
|
|
189
|
+
if (etag)
|
|
190
|
+
headers['if-none-match'] = etag;
|
|
191
|
+
const r = await (0, http_js_1.request)({ method: 'GET', url: this.url('/fleet/v1/policy'), agent: this.agent, headers, maxResponseBytes: 8 << 20 });
|
|
192
|
+
const et = r.headers.etag;
|
|
193
|
+
return { status: r.status, body: r.status === 200 ? r.body : undefined, etag: typeof et === 'string' ? et : undefined };
|
|
194
|
+
}
|
|
195
|
+
async renew(useCsr, commonName) {
|
|
196
|
+
let keyPem;
|
|
197
|
+
const body = {};
|
|
198
|
+
if (useCsr) {
|
|
199
|
+
const g = (0, asn1_js_1.generateCsr)(commonName);
|
|
200
|
+
keyPem = g.keyPem;
|
|
201
|
+
body.csr = g.csrPem;
|
|
202
|
+
}
|
|
203
|
+
const r = await (0, http_js_1.request)({ method: 'POST', url: this.url('/fleet/v1/renew'), agent: this.agent, headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });
|
|
204
|
+
if (r.status !== 200)
|
|
205
|
+
return null;
|
|
206
|
+
const j = (0, http_js_1.parseJsonBody)(r);
|
|
207
|
+
if (!j || typeof j.client_cert !== 'string')
|
|
208
|
+
return null;
|
|
209
|
+
if (!useCsr)
|
|
210
|
+
keyPem = typeof j.client_key === 'string' ? j.client_key : undefined;
|
|
211
|
+
if (!keyPem)
|
|
212
|
+
return null;
|
|
213
|
+
return { cert: j.client_cert, keyPem, notAfter: typeof j.not_after === 'string' ? j.not_after : undefined };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
exports.FleetClient = FleetClient;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import https from 'node:https';
|
|
2
|
+
export interface HttpResult {
|
|
3
|
+
status: number;
|
|
4
|
+
headers: Record<string, string | string[] | undefined>;
|
|
5
|
+
body: Buffer;
|
|
6
|
+
}
|
|
7
|
+
export interface HttpOptions {
|
|
8
|
+
method: 'GET' | 'POST';
|
|
9
|
+
url: string;
|
|
10
|
+
agent: https.Agent;
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
body?: string | Buffer;
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
maxResponseBytes?: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function request(o: HttpOptions): Promise<HttpResult>;
|
|
17
|
+
export declare function parseJsonBody(r: HttpResult): unknown;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Minimal HTTPS client used by the exporter and the Fleet client. Requests made here are
|
|
3
|
+
// marked internal so the capture hooks never see them.
|
|
4
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
6
|
+
};
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.request = request;
|
|
9
|
+
exports.parseJsonBody = parseJsonBody;
|
|
10
|
+
const node_https_1 = __importDefault(require("node:https"));
|
|
11
|
+
const common_js_1 = require("../capture/common.js");
|
|
12
|
+
function request(o) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
let settled = false;
|
|
15
|
+
const done = (err, res) => {
|
|
16
|
+
if (settled)
|
|
17
|
+
return;
|
|
18
|
+
settled = true;
|
|
19
|
+
if (err)
|
|
20
|
+
reject(err);
|
|
21
|
+
else
|
|
22
|
+
resolve(res);
|
|
23
|
+
};
|
|
24
|
+
const u = new URL(o.url);
|
|
25
|
+
if (u.protocol !== 'https:') {
|
|
26
|
+
done(new Error('https_required'));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const body = o.body === undefined ? undefined : Buffer.isBuffer(o.body) ? o.body : Buffer.from(o.body, 'utf8');
|
|
30
|
+
const headers = { ...(o.headers ?? {}) };
|
|
31
|
+
if (body)
|
|
32
|
+
headers['content-length'] = String(body.length);
|
|
33
|
+
const max = o.maxResponseBytes ?? 1 << 20;
|
|
34
|
+
const req = (0, common_js_1.runInternal)(() => node_https_1.default.request({
|
|
35
|
+
method: o.method,
|
|
36
|
+
hostname: u.hostname.replace(/^\[|\]$/g, ''),
|
|
37
|
+
port: u.port || 443,
|
|
38
|
+
path: u.pathname + u.search,
|
|
39
|
+
agent: o.agent,
|
|
40
|
+
headers,
|
|
41
|
+
servername: /^[0-9.]+$|:/.test(u.hostname) ? undefined : u.hostname,
|
|
42
|
+
}, (res) => {
|
|
43
|
+
const chunks = [];
|
|
44
|
+
let n = 0;
|
|
45
|
+
const tooBig = () => {
|
|
46
|
+
// Over the cap: hang up now instead of reading what the peer decides to send.
|
|
47
|
+
const e = Object.assign(new Error('response_too_large'), { code: 'RESPONSE_TOO_LARGE' });
|
|
48
|
+
done(e);
|
|
49
|
+
req.destroy(e);
|
|
50
|
+
};
|
|
51
|
+
const declared = Number(res.headers['content-length']);
|
|
52
|
+
if (Number.isFinite(declared) && declared > max) {
|
|
53
|
+
tooBig();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
res.on('data', (c) => {
|
|
57
|
+
n += c.length;
|
|
58
|
+
if (n > max) {
|
|
59
|
+
chunks.length = 0;
|
|
60
|
+
tooBig();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
chunks.push(c);
|
|
64
|
+
});
|
|
65
|
+
res.on('end', () => done(null, { status: res.statusCode ?? 0, headers: res.headers, body: Buffer.concat(chunks) }));
|
|
66
|
+
res.on('error', (e) => done(e));
|
|
67
|
+
}));
|
|
68
|
+
req.setTimeout(o.timeoutMs ?? 10_000, () => req.destroy(Object.assign(new Error('timeout'), { code: 'ETIMEDOUT' })));
|
|
69
|
+
req.on('error', (e) => done(e));
|
|
70
|
+
req.end(body);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function parseJsonBody(r) {
|
|
74
|
+
try {
|
|
75
|
+
return JSON.parse(r.body.toString('utf8'));
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare function defaultStateDir(enrollUrl: string, serviceName: string): string;
|
|
2
|
+
export declare class StateStore {
|
|
3
|
+
readonly dir: string;
|
|
4
|
+
/** Called with the file name when a state file or the directory is not trusted (reported, never the content). */
|
|
5
|
+
onUntrusted: ((what: string) => void) | undefined;
|
|
6
|
+
constructor(dir: string);
|
|
7
|
+
/**
|
|
8
|
+
* The directory must be ours. If it is ours but open to others (a pre-existing 0755
|
|
9
|
+
* directory) it is tightened to 0700; anything else is refused. Files are checked one by one.
|
|
10
|
+
*/
|
|
11
|
+
private dirTrusted;
|
|
12
|
+
private ensure;
|
|
13
|
+
readText(name: string): string | null;
|
|
14
|
+
readJson<T>(name: string): T | null;
|
|
15
|
+
writeText(name: string, text: string): void;
|
|
16
|
+
writeJson(name: string, v: unknown): void;
|
|
17
|
+
remove(name: string): void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Local state of the source: enrollment (certificate, CA chain, pinned keys), private key,
|
|
3
|
+
// signed policy cache, kill switch. Directory 0700, files 0600, atomic replace.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.StateStore = void 0;
|
|
6
|
+
exports.defaultStateDir = defaultStateDir;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_os_1 = require("node:os");
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const node_crypto_1 = require("node:crypto");
|
|
11
|
+
function defaultStateDir(enrollUrl, serviceName) {
|
|
12
|
+
const id = (0, node_crypto_1.createHash)('sha256').update(enrollUrl + '\0' + serviceName).digest('hex').slice(0, 16);
|
|
13
|
+
return (0, node_path_1.join)((0, node_os_1.homedir)(), '.traffic-twin', id);
|
|
14
|
+
}
|
|
15
|
+
function statExists(p) {
|
|
16
|
+
try {
|
|
17
|
+
(0, node_fs_1.statSync)(p);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const POSIX = process.platform !== 'win32';
|
|
25
|
+
const myUid = typeof process.getuid === 'function' ? process.getuid() : -1;
|
|
26
|
+
class StateStore {
|
|
27
|
+
dir;
|
|
28
|
+
/** Called with the file name when a state file or the directory is not trusted (reported, never the content). */
|
|
29
|
+
onUntrusted;
|
|
30
|
+
constructor(dir) {
|
|
31
|
+
this.dir = dir;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The directory must be ours. If it is ours but open to others (a pre-existing 0755
|
|
35
|
+
* directory) it is tightened to 0700; anything else is refused. Files are checked one by one.
|
|
36
|
+
*/
|
|
37
|
+
dirTrusted() {
|
|
38
|
+
if (!POSIX)
|
|
39
|
+
return true;
|
|
40
|
+
try {
|
|
41
|
+
const st = (0, node_fs_1.statSync)(this.dir);
|
|
42
|
+
if (!st.isDirectory())
|
|
43
|
+
return false;
|
|
44
|
+
if (myUid >= 0 && st.uid !== myUid)
|
|
45
|
+
return false;
|
|
46
|
+
if ((st.mode & 0o077) !== 0) {
|
|
47
|
+
(0, node_fs_1.chmodSync)(this.dir, 0o700);
|
|
48
|
+
if (((0, node_fs_1.statSync)(this.dir).mode & 0o077) !== 0)
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
ensure() {
|
|
58
|
+
(0, node_fs_1.mkdirSync)(this.dir, { recursive: true, mode: 0o700 });
|
|
59
|
+
if (!this.dirTrusted()) {
|
|
60
|
+
this.onUntrusted?.('dir');
|
|
61
|
+
throw new Error('state_dir_untrusted');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
readText(name) {
|
|
65
|
+
let fd = -1;
|
|
66
|
+
try {
|
|
67
|
+
if (!this.dirTrusted()) {
|
|
68
|
+
if (statExists(this.dir))
|
|
69
|
+
this.onUntrusted?.('dir');
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
// O_NOFOLLOW: a symlink planted in place of a state file is not followed; the checks
|
|
73
|
+
// run on the opened descriptor, so they apply to the bytes that are then read.
|
|
74
|
+
fd = (0, node_fs_1.openSync)((0, node_path_1.join)(this.dir, name), node_fs_1.constants.O_RDONLY | (POSIX ? node_fs_1.constants.O_NOFOLLOW : 0));
|
|
75
|
+
const st = (0, node_fs_1.fstatSync)(fd);
|
|
76
|
+
if (POSIX && (!st.isFile() || (myUid >= 0 && st.uid !== myUid) || (st.mode & 0o077) !== 0)) {
|
|
77
|
+
this.onUntrusted?.(name);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return (0, node_fs_1.readFileSync)(fd, 'utf8');
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
if (fd >= 0)
|
|
87
|
+
(0, node_fs_1.closeSync)(fd);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
readJson(name) {
|
|
91
|
+
const t = this.readText(name);
|
|
92
|
+
if (t === null)
|
|
93
|
+
return null;
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(t);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
writeText(name, text) {
|
|
102
|
+
this.ensure();
|
|
103
|
+
const tmp = (0, node_path_1.join)(this.dir, '.' + name + '.' + process.pid + '.tmp');
|
|
104
|
+
(0, node_fs_1.writeFileSync)(tmp, text, { mode: 0o600 });
|
|
105
|
+
(0, node_fs_1.chmodSync)(tmp, 0o600);
|
|
106
|
+
(0, node_fs_1.renameSync)(tmp, (0, node_path_1.join)(this.dir, name));
|
|
107
|
+
}
|
|
108
|
+
writeJson(name, v) {
|
|
109
|
+
this.writeText(name, JSON.stringify(v));
|
|
110
|
+
}
|
|
111
|
+
remove(name) {
|
|
112
|
+
(0, node_fs_1.rmSync)((0, node_path_1.join)(this.dir, name), { force: true });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
exports.StateStore = StateStore;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Agent, optionsFromEnv, type AgentOptions, type SourceState } from './agent.js';
|
|
2
|
+
export type { AgentOptions, SourceState };
|
|
3
|
+
export { Agent, optionsFromEnv };
|
|
4
|
+
export { SDK_VERSION } from './version.js';
|
|
5
|
+
export { Sanitizer, type Exchange, type Result, type Decision } from './sanitizer/sanitizer.js';
|
|
6
|
+
export { Policy, Refused, parseKeyRing, selectKey, verifyEnvelope, validateBodySchema } from './sanitizer/policy.js';
|
|
7
|
+
export { runVector, parseVector, vectorExchange, type VectorResult } from './sanitizer/vectors.js';
|
|
8
|
+
export { parseJson, writeCanonical, canonicalNumber } from './sanitizer/json.js';
|
|
9
|
+
/** Starts the agent once per process (later calls return the same one). Never throws. */
|
|
10
|
+
export declare function start(options?: AgentOptions): Promise<Agent>;
|
|
11
|
+
/** The running agent, if any. */
|
|
12
|
+
export declare function current(): Agent | undefined;
|
|
13
|
+
/** Stops capture and timers; the process keeps working untouched. */
|
|
14
|
+
export declare function stop(): Promise<void>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Public API of @traffic-twin/sdk.
|
|
3
|
+
//
|
|
4
|
+
// import { start } from '@traffic-twin/sdk';
|
|
5
|
+
// await start({ enrollUrl, enrollToken, serviceName, hmacKeyFile });
|
|
6
|
+
//
|
|
7
|
+
// or zero-config: node --import @traffic-twin/sdk/register app.js
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.canonicalNumber = exports.writeCanonical = exports.parseJson = exports.vectorExchange = exports.parseVector = exports.runVector = exports.validateBodySchema = exports.verifyEnvelope = exports.selectKey = exports.parseKeyRing = exports.Refused = exports.Policy = exports.Sanitizer = exports.SDK_VERSION = exports.optionsFromEnv = exports.Agent = void 0;
|
|
10
|
+
exports.start = start;
|
|
11
|
+
exports.current = current;
|
|
12
|
+
exports.stop = stop;
|
|
13
|
+
const agent_js_1 = require("./agent.js");
|
|
14
|
+
Object.defineProperty(exports, "Agent", { enumerable: true, get: function () { return agent_js_1.Agent; } });
|
|
15
|
+
Object.defineProperty(exports, "optionsFromEnv", { enumerable: true, get: function () { return agent_js_1.optionsFromEnv; } });
|
|
16
|
+
var version_js_1 = require("./version.js");
|
|
17
|
+
Object.defineProperty(exports, "SDK_VERSION", { enumerable: true, get: function () { return version_js_1.SDK_VERSION; } });
|
|
18
|
+
// Sanitizer and conformance test function (sanitizer.md 16).
|
|
19
|
+
var sanitizer_js_1 = require("./sanitizer/sanitizer.js");
|
|
20
|
+
Object.defineProperty(exports, "Sanitizer", { enumerable: true, get: function () { return sanitizer_js_1.Sanitizer; } });
|
|
21
|
+
var policy_js_1 = require("./sanitizer/policy.js");
|
|
22
|
+
Object.defineProperty(exports, "Policy", { enumerable: true, get: function () { return policy_js_1.Policy; } });
|
|
23
|
+
Object.defineProperty(exports, "Refused", { enumerable: true, get: function () { return policy_js_1.Refused; } });
|
|
24
|
+
Object.defineProperty(exports, "parseKeyRing", { enumerable: true, get: function () { return policy_js_1.parseKeyRing; } });
|
|
25
|
+
Object.defineProperty(exports, "selectKey", { enumerable: true, get: function () { return policy_js_1.selectKey; } });
|
|
26
|
+
Object.defineProperty(exports, "verifyEnvelope", { enumerable: true, get: function () { return policy_js_1.verifyEnvelope; } });
|
|
27
|
+
Object.defineProperty(exports, "validateBodySchema", { enumerable: true, get: function () { return policy_js_1.validateBodySchema; } });
|
|
28
|
+
var vectors_js_1 = require("./sanitizer/vectors.js");
|
|
29
|
+
Object.defineProperty(exports, "runVector", { enumerable: true, get: function () { return vectors_js_1.runVector; } });
|
|
30
|
+
Object.defineProperty(exports, "parseVector", { enumerable: true, get: function () { return vectors_js_1.parseVector; } });
|
|
31
|
+
Object.defineProperty(exports, "vectorExchange", { enumerable: true, get: function () { return vectors_js_1.vectorExchange; } });
|
|
32
|
+
var json_js_1 = require("./sanitizer/json.js");
|
|
33
|
+
Object.defineProperty(exports, "parseJson", { enumerable: true, get: function () { return json_js_1.parseJson; } });
|
|
34
|
+
Object.defineProperty(exports, "writeCanonical", { enumerable: true, get: function () { return json_js_1.writeCanonical; } });
|
|
35
|
+
Object.defineProperty(exports, "canonicalNumber", { enumerable: true, get: function () { return json_js_1.canonicalNumber; } });
|
|
36
|
+
// One agent per process, shared by the ESM and CommonJS builds.
|
|
37
|
+
const KEY = Symbol.for('traffic-twin.sdk.agent');
|
|
38
|
+
/** Starts the agent once per process (later calls return the same one). Never throws. */
|
|
39
|
+
function start(options = {}) {
|
|
40
|
+
const g = globalThis;
|
|
41
|
+
const existing = g[KEY];
|
|
42
|
+
if (existing)
|
|
43
|
+
return existing.ready.then(() => existing.agent);
|
|
44
|
+
const merged = { ...(0, agent_js_1.optionsFromEnv)(), ...stripUndefined(options) };
|
|
45
|
+
const agent = new agent_js_1.Agent(merged);
|
|
46
|
+
const ready = agent.start();
|
|
47
|
+
g[KEY] = { agent, ready };
|
|
48
|
+
return ready.then(() => agent);
|
|
49
|
+
}
|
|
50
|
+
/** The running agent, if any. */
|
|
51
|
+
function current() {
|
|
52
|
+
return globalThis[KEY]?.agent;
|
|
53
|
+
}
|
|
54
|
+
/** Stops capture and timers; the process keeps working untouched. */
|
|
55
|
+
async function stop() {
|
|
56
|
+
const g = globalThis;
|
|
57
|
+
const e = g[KEY];
|
|
58
|
+
if (!e)
|
|
59
|
+
return;
|
|
60
|
+
delete g[KEY];
|
|
61
|
+
await e.agent.stop();
|
|
62
|
+
}
|
|
63
|
+
function stripUndefined(o) {
|
|
64
|
+
const out = {};
|
|
65
|
+
for (const k of Object.keys(o))
|
|
66
|
+
if (o[k] !== undefined)
|
|
67
|
+
out[k] = o[k];
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function setDebug(on: boolean): void;
|
|
2
|
+
export declare function debug(code: string, detail?: string | number): void;
|
|
3
|
+
/** A line that is always written (configuration problems that stop the capture). Codes and fixed text only. */
|
|
4
|
+
export declare function warn(text: string): void;
|
package/dist/cjs/log.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Diagnostic log. Only error codes and counts are ever written, never payload values
|
|
3
|
+
// (threat model M14). Off unless TT_DEBUG is set.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.setDebug = setDebug;
|
|
6
|
+
exports.debug = debug;
|
|
7
|
+
exports.warn = warn;
|
|
8
|
+
let enabled = /^(1|true|yes|debug)$/i.test(process.env.TT_DEBUG ?? '');
|
|
9
|
+
function setDebug(on) {
|
|
10
|
+
enabled = on;
|
|
11
|
+
}
|
|
12
|
+
function debug(code, detail) {
|
|
13
|
+
if (!enabled)
|
|
14
|
+
return;
|
|
15
|
+
try {
|
|
16
|
+
process.stderr.write('[traffic-twin] ' + code + (detail !== undefined ? ' ' + String(detail) : '') + '\n');
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// ignore
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** A line that is always written (configuration problems that stop the capture). Codes and fixed text only. */
|
|
23
|
+
function warn(text) {
|
|
24
|
+
try {
|
|
25
|
+
process.stderr.write('[traffic-twin] ' + text + '\n');
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// ignore
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Zero-config entry point:
|
|
3
|
+
//
|
|
4
|
+
// TT_ENROLL_URL=https://tt-core.internal:4318 TT_ENROLL_TOKEN=tte_... TT_HMAC_KEY_FILE=/run/tt/key \
|
|
5
|
+
// node --import @traffic-twin/sdk/register app.js
|
|
6
|
+
//
|
|
7
|
+
// (or `node --require @traffic-twin/sdk/register app.js` for CommonJS apps).
|
|
8
|
+
// Hooks are installed synchronously before the app loads; enrollment, heartbeat and policy
|
|
9
|
+
// fetch run in the background and never delay or break the app. TT_DISABLED=1 turns it off.
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
const node_worker_threads_1 = require("node:worker_threads");
|
|
12
|
+
const index_js_1 = require("./index.js");
|
|
13
|
+
const ownWorker = !node_worker_threads_1.isMainThread && node_worker_threads_1.workerData?.ttSanitizer === true;
|
|
14
|
+
if (!ownWorker && !/^(1|true|yes)$/i.test(process.env.TT_DISABLED ?? '')) {
|
|
15
|
+
(0, index_js_1.start)().catch(() => {
|
|
16
|
+
// never surface SDK failures to the app
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** 22.4: document class on the decoded length. */
|
|
2
|
+
export declare function docClass(n: number): string;
|
|
3
|
+
/** 22.4 point 3: kind of a document from its bytes, or null. */
|
|
4
|
+
export declare function magicKind(b: Buffer): string | null;
|
|
5
|
+
/** 22.4: 'drop:doc:<kind>:<class>' if the string is the base64 of a document, else null. */
|
|
6
|
+
export declare function base64Doc(str: string): string | null;
|
|
7
|
+
/**
|
|
8
|
+
* Parameters of a normalized content-type value: lowercase name -> value, first occurrence
|
|
9
|
+
* wins. Split on ';', each piece trimmed and split on the first '='; a value starting with '"'
|
|
10
|
+
* must end with '"' (length >= 2) and loses both quotes, no escapes. Not UTF-8: no parameters.
|
|
11
|
+
*/
|
|
12
|
+
export declare function mediaParams(value: Buffer | null | undefined): Map<string, string>;
|
|
13
|
+
export declare class InvalidMultipart extends Error {
|
|
14
|
+
}
|
|
15
|
+
export interface MultipartPart {
|
|
16
|
+
name: string;
|
|
17
|
+
isFile: boolean;
|
|
18
|
+
content: Buffer;
|
|
19
|
+
ct: string | null;
|
|
20
|
+
}
|
|
21
|
+
/** 22.3 points 1-5: the parts of a multipart body. Throws InvalidMultipart. */
|
|
22
|
+
export declare function parseMultipart(data: Buffer, boundary: string | undefined): MultipartPart[];
|
|
23
|
+
/** 22.3: drop:file:<kind>:<class> of a file part. */
|
|
24
|
+
export declare function fileToken(content: Buffer, ct: string | null): string;
|
|
25
|
+
export declare const NS_XML = "http://www.w3.org/XML/1998/namespace";
|
|
26
|
+
export declare const SOAP11 = "http://schemas.xmlsoap.org/soap/envelope/";
|
|
27
|
+
export declare const SOAP12 = "http://www.w3.org/2003/05/soap-envelope";
|
|
28
|
+
export declare const XML_MAX_NESTING = 256;
|
|
29
|
+
export declare class InvalidXml extends Error {
|
|
30
|
+
}
|
|
31
|
+
export interface XmlElem {
|
|
32
|
+
local: string;
|
|
33
|
+
ns: string;
|
|
34
|
+
/** (local name, value) without namespace declarations. */
|
|
35
|
+
attrs: Array<[string, string]>;
|
|
36
|
+
children: XmlElem[];
|
|
37
|
+
text: string[];
|
|
38
|
+
}
|
|
39
|
+
/** 22.2 points 1-6 -> root element. Throws InvalidXml for anything outside the accepted subset. */
|
|
40
|
+
export declare function parseXml(input: Buffer): XmlElem;
|
|
41
|
+
/** Tree value of an element (22.2 point 7): a string, or an object as ordered members. */
|
|
42
|
+
export type XmlValue = string | XmlObj | XmlValue[];
|
|
43
|
+
export interface XmlObj {
|
|
44
|
+
members: Array<[string, XmlValue]>;
|
|
45
|
+
}
|
|
46
|
+
export declare function xmlValue(el: XmlElem): XmlValue;
|
|
47
|
+
/** 22.2 point 9: 'ns:' + pattern of a namespace URI. */
|
|
48
|
+
export declare function nsPattern(uri: string): string;
|
|
49
|
+
/** 22.2 points 9-10: xmlns entries [(path, token)] in document order (no limit) and fmt. */
|
|
50
|
+
export declare function xmlNamespaces(root: XmlElem, literal: (k: string) => boolean, maxDepth: number): {
|
|
51
|
+
entries: Array<[string, string]>;
|
|
52
|
+
fmt: string;
|
|
53
|
+
};
|
|
54
|
+
/** 22.2 point 11: the operation name of a SOAPAction value, or null when empty. */
|
|
55
|
+
export declare function soapActionName(raw: string, literal: (k: string) => boolean): string | null;
|