facturas 0.4.2 → 0.5.1
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/README.md +4 -0
- package/dist/errors.d.ts +27 -1
- package/dist/errors.js +25 -0
- package/dist/errors.js.map +1 -1
- package/dist/{index-BLq3d6xg.d.ts → index-CqXVyU0E.d.ts} +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +421 -84
- package/dist/index.js.map +1 -1
- package/dist/padron.d.ts +2 -2
- package/dist/padron.js.map +1 -1
- package/dist/{types-SloiIQkT.d.ts → types-CKKe1Oo4.d.ts} +13 -1
- package/dist/types.d.ts +1 -1
- package/dist/wsfe.d.ts +18 -15
- package/dist/wsfe.js +31 -22
- package/dist/wsfe.js.map +1 -1
- package/dist/wsmtxca.d.ts +5 -2
- package/dist/wsmtxca.js +17 -6
- package/dist/wsmtxca.js.map +1 -1
- package/package.json +14 -15
package/dist/index.js
CHANGED
|
@@ -24,10 +24,12 @@ var ArcaInputError = class extends ArcaError {
|
|
|
24
24
|
var ArcaTransportError = class extends ArcaError {
|
|
25
25
|
name = "ArcaTransportError";
|
|
26
26
|
statusCode;
|
|
27
|
+
contentType;
|
|
27
28
|
responseBody;
|
|
28
29
|
constructor(message, options) {
|
|
29
30
|
super(message, "ARCA_TRANSPORT_ERROR", options);
|
|
30
31
|
this.statusCode = options?.statusCode;
|
|
32
|
+
this.contentType = options?.contentType;
|
|
31
33
|
this.responseBody = options?.responseBody;
|
|
32
34
|
}
|
|
33
35
|
};
|
|
@@ -41,6 +43,28 @@ var ArcaSoapFaultError = class extends ArcaError {
|
|
|
41
43
|
this.detail = options?.detail;
|
|
42
44
|
}
|
|
43
45
|
};
|
|
46
|
+
var ArcaInvalidSoapResponseError = class extends ArcaError {
|
|
47
|
+
name = "ArcaInvalidSoapResponseError";
|
|
48
|
+
service;
|
|
49
|
+
operation;
|
|
50
|
+
endpointUrl;
|
|
51
|
+
statusCode;
|
|
52
|
+
contentType;
|
|
53
|
+
responseBodyLength;
|
|
54
|
+
responseBodyPreview;
|
|
55
|
+
parsedDetail;
|
|
56
|
+
constructor(message, options) {
|
|
57
|
+
super(message, "ARCA_INVALID_SOAP_RESPONSE", options);
|
|
58
|
+
this.service = options?.service;
|
|
59
|
+
this.operation = options?.operation;
|
|
60
|
+
this.endpointUrl = options?.endpointUrl;
|
|
61
|
+
this.statusCode = options?.statusCode;
|
|
62
|
+
this.contentType = options?.contentType;
|
|
63
|
+
this.responseBodyLength = options?.responseBodyLength;
|
|
64
|
+
this.responseBodyPreview = options?.responseBodyPreview;
|
|
65
|
+
this.parsedDetail = options?.parsedDetail;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
44
68
|
var ArcaServiceError = class extends ArcaError {
|
|
45
69
|
name = "ArcaServiceError";
|
|
46
70
|
serviceCode;
|
|
@@ -125,12 +149,33 @@ function assertArcaClientConfig(config) {
|
|
|
125
149
|
if (normalized.logger?.log !== void 0 && typeof normalized.logger.log !== "function") {
|
|
126
150
|
invalidFields.push("logger.log");
|
|
127
151
|
}
|
|
152
|
+
invalidFields.push(...getInvalidWsaaSessionStoreFields(normalized));
|
|
128
153
|
if (invalidFields.length > 0) {
|
|
129
154
|
throw new ArcaConfigurationError(
|
|
130
155
|
`Missing or invalid ARCA client config fields: ${invalidFields.join(", ")}`
|
|
131
156
|
);
|
|
132
157
|
}
|
|
133
158
|
}
|
|
159
|
+
function getInvalidWsaaSessionStoreFields(config) {
|
|
160
|
+
const store = config.wsaaSessionStore;
|
|
161
|
+
if (store === void 0) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
const invalidFields = [];
|
|
165
|
+
if (typeof store.get !== "function") {
|
|
166
|
+
invalidFields.push("wsaaSessionStore.get");
|
|
167
|
+
}
|
|
168
|
+
if (typeof store.set !== "function") {
|
|
169
|
+
invalidFields.push("wsaaSessionStore.set");
|
|
170
|
+
}
|
|
171
|
+
if (store.delete !== void 0 && typeof store.delete !== "function") {
|
|
172
|
+
invalidFields.push("wsaaSessionStore.delete");
|
|
173
|
+
}
|
|
174
|
+
if (store.withLock !== void 0 && typeof store.withLock !== "function") {
|
|
175
|
+
invalidFields.push("wsaaSessionStore.withLock");
|
|
176
|
+
}
|
|
177
|
+
return invalidFields;
|
|
178
|
+
}
|
|
134
179
|
var ARCA_WSAA_CONFIG = {
|
|
135
180
|
namespace: "http://wsaa.view.sua.dvadac.desein.afip.gov",
|
|
136
181
|
endpoint: {
|
|
@@ -208,7 +253,8 @@ function normalizeArcaClientConfig(config) {
|
|
|
208
253
|
...config.logger,
|
|
209
254
|
...normalizedLoggerLevel === void 0 ? {} : { level: normalizedLoggerLevel }
|
|
210
255
|
}
|
|
211
|
-
}
|
|
256
|
+
},
|
|
257
|
+
...config.wsaaSessionStore === void 0 ? {} : { wsaaSessionStore: config.wsaaSessionStore }
|
|
212
258
|
};
|
|
213
259
|
}
|
|
214
260
|
function normalizeEnvironmentValue(value) {
|
|
@@ -389,7 +435,7 @@ function createWsfeService(options) {
|
|
|
389
435
|
async function executeWsfeAuthenticatedOperation(operation, input, body = {}) {
|
|
390
436
|
const auth = await options.auth.login("wsfe", {
|
|
391
437
|
representedTaxId: input.representedTaxId,
|
|
392
|
-
forceRefresh: input.
|
|
438
|
+
forceRefresh: input.forceRefresh
|
|
393
439
|
});
|
|
394
440
|
const response = await options.soap.execute({
|
|
395
441
|
service: "wsfe",
|
|
@@ -417,13 +463,13 @@ function createWsfeService(options) {
|
|
|
417
463
|
representedTaxId,
|
|
418
464
|
salesPoint,
|
|
419
465
|
voucherType,
|
|
420
|
-
|
|
466
|
+
forceRefresh
|
|
421
467
|
}) {
|
|
422
468
|
const result = await executeWsfeAuthenticatedOperation(
|
|
423
469
|
"FECompUltimoAutorizado",
|
|
424
470
|
{
|
|
425
471
|
representedTaxId,
|
|
426
|
-
|
|
472
|
+
forceRefresh
|
|
427
473
|
},
|
|
428
474
|
{
|
|
429
475
|
PtoVta: salesPoint,
|
|
@@ -435,29 +481,35 @@ function createWsfeService(options) {
|
|
|
435
481
|
async function getWsfeCatalog(operation, resultKey, input) {
|
|
436
482
|
const result = await executeWsfeAuthenticatedOperation(operation, {
|
|
437
483
|
representedTaxId: input.representedTaxId,
|
|
438
|
-
|
|
484
|
+
forceRefresh: input.forceRefresh
|
|
439
485
|
});
|
|
440
486
|
return getWsfeResultEntries(result, resultKey).map(mapWsfeCatalogEntry);
|
|
441
487
|
}
|
|
442
488
|
function authorizeVoucher({
|
|
443
489
|
representedTaxId,
|
|
444
490
|
data,
|
|
445
|
-
voucherNumber
|
|
491
|
+
voucherNumber,
|
|
492
|
+
forceRefresh
|
|
446
493
|
}) {
|
|
447
494
|
const normalizedInput = normalizeWsfeVoucherInput(data);
|
|
448
495
|
return authorizeNormalizedVoucher({
|
|
449
496
|
representedTaxId,
|
|
450
497
|
data: normalizedInput,
|
|
451
|
-
voucherNumber
|
|
498
|
+
voucherNumber,
|
|
499
|
+
forceRefresh
|
|
452
500
|
});
|
|
453
501
|
}
|
|
454
502
|
async function authorizeNormalizedVoucher({
|
|
455
503
|
representedTaxId,
|
|
456
504
|
data: normalizedInput,
|
|
457
|
-
voucherNumber
|
|
505
|
+
voucherNumber,
|
|
506
|
+
forceRefresh
|
|
458
507
|
}) {
|
|
459
508
|
const requestData = mapWsfeVoucherInput(normalizedInput, voucherNumber);
|
|
460
|
-
const auth = await options.auth.login("wsfe", {
|
|
509
|
+
const auth = await options.auth.login("wsfe", {
|
|
510
|
+
representedTaxId,
|
|
511
|
+
forceRefresh
|
|
512
|
+
});
|
|
461
513
|
const response = await options.soap.execute({
|
|
462
514
|
service: "wsfe",
|
|
463
515
|
operation: "FECAESolicitar",
|
|
@@ -497,12 +549,13 @@ function createWsfeService(options) {
|
|
|
497
549
|
}
|
|
498
550
|
return {
|
|
499
551
|
authorizeVoucher,
|
|
500
|
-
async createNextVoucher({ representedTaxId, data }) {
|
|
552
|
+
async createNextVoucher({ representedTaxId, data, forceRefresh }) {
|
|
501
553
|
const normalizedInput = normalizeWsfeVoucherInput(data);
|
|
502
554
|
const voucherNumber = await getNextVoucherNumber({
|
|
503
555
|
representedTaxId,
|
|
504
556
|
salesPoint: normalizedInput.salesPoint,
|
|
505
|
-
voucherType: normalizedInput.voucherType
|
|
557
|
+
voucherType: normalizedInput.voucherType,
|
|
558
|
+
forceRefresh
|
|
506
559
|
});
|
|
507
560
|
return authorizeNormalizedVoucher({
|
|
508
561
|
representedTaxId,
|
|
@@ -514,12 +567,12 @@ function createWsfeService(options) {
|
|
|
514
567
|
getLastVoucher(input) {
|
|
515
568
|
return getNextVoucherNumber(input);
|
|
516
569
|
},
|
|
517
|
-
async getSalesPoints({ representedTaxId,
|
|
570
|
+
async getSalesPoints({ representedTaxId, forceRefresh }) {
|
|
518
571
|
const result = await executeWsfeAuthenticatedOperation(
|
|
519
572
|
"FEParamGetPtosVenta",
|
|
520
573
|
{
|
|
521
574
|
representedTaxId,
|
|
522
|
-
|
|
575
|
+
forceRefresh
|
|
523
576
|
}
|
|
524
577
|
);
|
|
525
578
|
const rawPoints = result.ResultGet?.PtoVenta;
|
|
@@ -538,12 +591,12 @@ function createWsfeService(options) {
|
|
|
538
591
|
getConceptTypes(input) {
|
|
539
592
|
return getWsfeCatalog("FEParamGetTiposConcepto", "ConceptoTipo", input);
|
|
540
593
|
},
|
|
541
|
-
async getCurrencyTypes({ representedTaxId,
|
|
594
|
+
async getCurrencyTypes({ representedTaxId, forceRefresh }) {
|
|
542
595
|
const result = await executeWsfeAuthenticatedOperation(
|
|
543
596
|
"FEParamGetTiposMonedas",
|
|
544
597
|
{
|
|
545
598
|
representedTaxId,
|
|
546
|
-
|
|
599
|
+
forceRefresh
|
|
547
600
|
}
|
|
548
601
|
);
|
|
549
602
|
return getWsfeResultEntries(result, "Moneda").map(mapWsfeCurrencyType);
|
|
@@ -557,12 +610,12 @@ function createWsfeService(options) {
|
|
|
557
610
|
getOptionalTypes(input) {
|
|
558
611
|
return getWsfeCatalog("FEParamGetTiposOpcional", "OpcionalTipo", input);
|
|
559
612
|
},
|
|
560
|
-
async getActivities({ representedTaxId,
|
|
613
|
+
async getActivities({ representedTaxId, forceRefresh }) {
|
|
561
614
|
const result = await executeWsfeAuthenticatedOperation(
|
|
562
615
|
"FEParamGetActividades",
|
|
563
616
|
{
|
|
564
617
|
representedTaxId,
|
|
565
|
-
|
|
618
|
+
forceRefresh
|
|
566
619
|
}
|
|
567
620
|
);
|
|
568
621
|
return getWsfeResultEntries(result, "ActividadesTipo").map(
|
|
@@ -572,13 +625,13 @@ function createWsfeService(options) {
|
|
|
572
625
|
async getReceiverVatConditions({
|
|
573
626
|
representedTaxId,
|
|
574
627
|
voucherClass,
|
|
575
|
-
|
|
628
|
+
forceRefresh
|
|
576
629
|
}) {
|
|
577
630
|
const result = await executeWsfeAuthenticatedOperation(
|
|
578
631
|
"FEParamGetCondicionIvaReceptor",
|
|
579
632
|
{
|
|
580
633
|
representedTaxId,
|
|
581
|
-
|
|
634
|
+
forceRefresh
|
|
582
635
|
},
|
|
583
636
|
{
|
|
584
637
|
...voucherClass === void 0 ? {} : { ClaseCmp: voucherClass }
|
|
@@ -592,12 +645,12 @@ function createWsfeService(options) {
|
|
|
592
645
|
const result = await executeWsfeOperation("FEDummy");
|
|
593
646
|
return mapWsfeServerStatus(result);
|
|
594
647
|
},
|
|
595
|
-
async getQuotation({ currencyId, representedTaxId,
|
|
648
|
+
async getQuotation({ currencyId, representedTaxId, forceRefresh }) {
|
|
596
649
|
const result = await executeWsfeAuthenticatedOperation(
|
|
597
650
|
"FEParamGetCotizacion",
|
|
598
651
|
{
|
|
599
652
|
representedTaxId,
|
|
600
|
-
|
|
653
|
+
forceRefresh
|
|
601
654
|
},
|
|
602
655
|
{
|
|
603
656
|
MonId: currencyId
|
|
@@ -610,12 +663,14 @@ function createWsfeService(options) {
|
|
|
610
663
|
representedTaxId,
|
|
611
664
|
number,
|
|
612
665
|
salesPoint,
|
|
613
|
-
voucherType
|
|
666
|
+
voucherType,
|
|
667
|
+
forceRefresh
|
|
614
668
|
}) {
|
|
615
669
|
const result = await executeWsfeAuthenticatedOperation(
|
|
616
670
|
"FECompConsultar",
|
|
617
671
|
{
|
|
618
|
-
representedTaxId
|
|
672
|
+
representedTaxId,
|
|
673
|
+
forceRefresh
|
|
619
674
|
},
|
|
620
675
|
{
|
|
621
676
|
FeCompConsReq: {
|
|
@@ -989,8 +1044,11 @@ function getWsfeResultEntries(result, key) {
|
|
|
989
1044
|
// src/services/wsmtxca.ts
|
|
990
1045
|
function createWsmtxcaService(options) {
|
|
991
1046
|
return {
|
|
992
|
-
async authorizeVoucher({ representedTaxId, data }) {
|
|
993
|
-
const auth = await options.auth.login("wsmtxca", {
|
|
1047
|
+
async authorizeVoucher({ representedTaxId, data, forceRefresh }) {
|
|
1048
|
+
const auth = await options.auth.login("wsmtxca", {
|
|
1049
|
+
representedTaxId,
|
|
1050
|
+
forceRefresh
|
|
1051
|
+
});
|
|
994
1052
|
const response = await options.soap.execute({
|
|
995
1053
|
service: "wsmtxca",
|
|
996
1054
|
operation: "autorizarComprobante",
|
|
@@ -1036,9 +1094,13 @@ function createWsmtxcaService(options) {
|
|
|
1036
1094
|
async getLastAuthorizedVoucher({
|
|
1037
1095
|
representedTaxId,
|
|
1038
1096
|
voucherType,
|
|
1039
|
-
salesPoint
|
|
1097
|
+
salesPoint,
|
|
1098
|
+
forceRefresh
|
|
1040
1099
|
}) {
|
|
1041
|
-
const auth = await options.auth.login("wsmtxca", {
|
|
1100
|
+
const auth = await options.auth.login("wsmtxca", {
|
|
1101
|
+
representedTaxId,
|
|
1102
|
+
forceRefresh
|
|
1103
|
+
});
|
|
1042
1104
|
const response = await options.soap.execute({
|
|
1043
1105
|
service: "wsmtxca",
|
|
1044
1106
|
operation: "consultarUltimoComprobanteAutorizado",
|
|
@@ -1073,9 +1135,13 @@ function createWsmtxcaService(options) {
|
|
|
1073
1135
|
representedTaxId,
|
|
1074
1136
|
voucherType,
|
|
1075
1137
|
salesPoint,
|
|
1076
|
-
voucherNumber
|
|
1138
|
+
voucherNumber,
|
|
1139
|
+
forceRefresh
|
|
1077
1140
|
}) {
|
|
1078
|
-
const auth = await options.auth.login("wsmtxca", {
|
|
1141
|
+
const auth = await options.auth.login("wsmtxca", {
|
|
1142
|
+
representedTaxId,
|
|
1143
|
+
forceRefresh
|
|
1144
|
+
});
|
|
1079
1145
|
const response = await options.soap.execute({
|
|
1080
1146
|
service: "wsmtxca",
|
|
1081
1147
|
operation: "consultarComprobante",
|
|
@@ -1219,7 +1285,7 @@ var legacyTlsAgent = new https.Agent({
|
|
|
1219
1285
|
keepAlive: true,
|
|
1220
1286
|
ciphers: "DEFAULT@SECLEVEL=0"
|
|
1221
1287
|
});
|
|
1222
|
-
async function
|
|
1288
|
+
async function postXmlWithMetadata({
|
|
1223
1289
|
url,
|
|
1224
1290
|
body,
|
|
1225
1291
|
contentType,
|
|
@@ -1287,12 +1353,12 @@ async function postXmlOnce({
|
|
|
1287
1353
|
const requestBody = Buffer.from(body, "utf8");
|
|
1288
1354
|
return await new Promise((resolve, reject) => {
|
|
1289
1355
|
let settled = false;
|
|
1290
|
-
const settleResolve = (
|
|
1356
|
+
const settleResolve = (response) => {
|
|
1291
1357
|
if (settled) {
|
|
1292
1358
|
return;
|
|
1293
1359
|
}
|
|
1294
1360
|
settled = true;
|
|
1295
|
-
resolve(
|
|
1361
|
+
resolve(response);
|
|
1296
1362
|
};
|
|
1297
1363
|
const settleReject = (error) => {
|
|
1298
1364
|
if (settled) {
|
|
@@ -1351,11 +1417,19 @@ async function postXmlOnce({
|
|
|
1351
1417
|
response.headers["content-type"]
|
|
1352
1418
|
) ? response.headers["content-type"].join("; ") : response.headers["content-type"];
|
|
1353
1419
|
if (statusCode >= 200 && statusCode < 300) {
|
|
1354
|
-
settleResolve(
|
|
1420
|
+
settleResolve({
|
|
1421
|
+
body: responseBody,
|
|
1422
|
+
statusCode,
|
|
1423
|
+
contentType: responseContentType
|
|
1424
|
+
});
|
|
1355
1425
|
return;
|
|
1356
1426
|
}
|
|
1357
1427
|
if (isXmlLikeResponse(responseBody, responseContentType)) {
|
|
1358
|
-
settleResolve(
|
|
1428
|
+
settleResolve({
|
|
1429
|
+
body: responseBody,
|
|
1430
|
+
statusCode,
|
|
1431
|
+
contentType: responseContentType
|
|
1432
|
+
});
|
|
1359
1433
|
return;
|
|
1360
1434
|
}
|
|
1361
1435
|
settleReject(
|
|
@@ -1363,6 +1437,7 @@ async function postXmlOnce({
|
|
|
1363
1437
|
`ARCA HTTP request failed with status ${statusCode}`,
|
|
1364
1438
|
{
|
|
1365
1439
|
statusCode,
|
|
1440
|
+
contentType: responseContentType,
|
|
1366
1441
|
responseBody
|
|
1367
1442
|
}
|
|
1368
1443
|
)
|
|
@@ -1437,14 +1512,26 @@ function buildSoapEnvelope(soapVersion, operation, namespace, body, options) {
|
|
|
1437
1512
|
};
|
|
1438
1513
|
return `<?xml version="1.0" encoding="utf-8"?>${xmlBuilder.build(payload)}`;
|
|
1439
1514
|
}
|
|
1440
|
-
function parseSoapBody(xml) {
|
|
1441
|
-
|
|
1515
|
+
function parseSoapBody(xml, context = {}) {
|
|
1516
|
+
let parsed;
|
|
1517
|
+
try {
|
|
1518
|
+
parsed = xmlParser.parse(xml);
|
|
1519
|
+
} catch (error) {
|
|
1520
|
+
throw createInvalidSoapResponseError(
|
|
1521
|
+
"Invalid SOAP response: XML parse failed",
|
|
1522
|
+
context,
|
|
1523
|
+
void 0,
|
|
1524
|
+
error instanceof Error ? error : void 0
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1442
1527
|
const envelope = parsed.Envelope;
|
|
1443
1528
|
const body = envelope?.Body;
|
|
1444
1529
|
if (!body) {
|
|
1445
|
-
throw
|
|
1446
|
-
|
|
1447
|
-
|
|
1530
|
+
throw createInvalidSoapResponseError(
|
|
1531
|
+
"Invalid SOAP response: missing body",
|
|
1532
|
+
context,
|
|
1533
|
+
parsed
|
|
1534
|
+
);
|
|
1448
1535
|
}
|
|
1449
1536
|
const fault = body.Fault;
|
|
1450
1537
|
if (fault) {
|
|
@@ -1452,18 +1539,40 @@ function parseSoapBody(xml) {
|
|
|
1452
1539
|
}
|
|
1453
1540
|
return body;
|
|
1454
1541
|
}
|
|
1455
|
-
function getSingleBodyEntry(body) {
|
|
1542
|
+
function getSingleBodyEntry(body, context = {}) {
|
|
1456
1543
|
const entries = Object.entries(body).filter(([key]) => key !== "@_xmlns");
|
|
1457
1544
|
if (entries.length !== 1) {
|
|
1458
|
-
throw
|
|
1545
|
+
throw createInvalidSoapResponseError(
|
|
1459
1546
|
`Invalid SOAP response: expected a single body entry, got ${entries.length}`,
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
}
|
|
1547
|
+
context,
|
|
1548
|
+
body
|
|
1463
1549
|
);
|
|
1464
1550
|
}
|
|
1465
1551
|
return entries[0];
|
|
1466
1552
|
}
|
|
1553
|
+
function createInvalidSoapResponseError(message, context, parsedDetail, cause) {
|
|
1554
|
+
const responseBody = context.responseBody ?? "";
|
|
1555
|
+
return new ArcaInvalidSoapResponseError(message, {
|
|
1556
|
+
cause,
|
|
1557
|
+
service: context.service,
|
|
1558
|
+
operation: context.operation,
|
|
1559
|
+
endpointUrl: context.endpointUrl,
|
|
1560
|
+
statusCode: context.statusCode,
|
|
1561
|
+
contentType: context.contentType,
|
|
1562
|
+
responseBodyLength: responseBody.length,
|
|
1563
|
+
responseBodyPreview: sanitizeSoapResponsePreview(
|
|
1564
|
+
responseBody,
|
|
1565
|
+
context.responseBodyPreviewLength
|
|
1566
|
+
),
|
|
1567
|
+
parsedDetail
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
function sanitizeSoapResponsePreview(responseBody, maxLength = 4096) {
|
|
1571
|
+
return responseBody.replace(
|
|
1572
|
+
/<((?:[A-Za-z_][\w.-]*:)?(?:Token|Sign))\b([^>]*)>[\s\S]*?<\/\1>/gi,
|
|
1573
|
+
(_match, tagName, attributes) => `<${tagName}${attributes}>[REDACTED]</${tagName}>`
|
|
1574
|
+
).slice(0, maxLength);
|
|
1575
|
+
}
|
|
1467
1576
|
function parseXmlDocument(xml) {
|
|
1468
1577
|
return xmlParser.parse(xml);
|
|
1469
1578
|
}
|
|
@@ -1522,7 +1631,7 @@ function createSoapTransport(options) {
|
|
|
1522
1631
|
url
|
|
1523
1632
|
});
|
|
1524
1633
|
try {
|
|
1525
|
-
const
|
|
1634
|
+
const response = await postXmlWithMetadata({
|
|
1526
1635
|
url,
|
|
1527
1636
|
body: xml,
|
|
1528
1637
|
contentType,
|
|
@@ -1540,12 +1649,23 @@ function createSoapTransport(options) {
|
|
|
1540
1649
|
operation: request.operation,
|
|
1541
1650
|
durationMs: Date.now() - startedAt
|
|
1542
1651
|
});
|
|
1543
|
-
const
|
|
1544
|
-
|
|
1652
|
+
const parseContext = {
|
|
1653
|
+
service: request.service,
|
|
1654
|
+
operation: request.operation,
|
|
1655
|
+
endpointUrl: url,
|
|
1656
|
+
statusCode: response.statusCode,
|
|
1657
|
+
contentType: response.contentType,
|
|
1658
|
+
responseBody: response.body
|
|
1659
|
+
};
|
|
1660
|
+
const soapBody = parseSoapBody(response.body, parseContext);
|
|
1661
|
+
const [, result] = getSingleBodyEntry(
|
|
1662
|
+
soapBody,
|
|
1663
|
+
parseContext
|
|
1664
|
+
);
|
|
1545
1665
|
return {
|
|
1546
1666
|
service: request.service,
|
|
1547
1667
|
operation: request.operation,
|
|
1548
|
-
raw:
|
|
1668
|
+
raw: response.body,
|
|
1549
1669
|
result
|
|
1550
1670
|
};
|
|
1551
1671
|
} catch (error) {
|
|
@@ -1558,6 +1678,17 @@ function createSoapTransport(options) {
|
|
|
1558
1678
|
error
|
|
1559
1679
|
});
|
|
1560
1680
|
}
|
|
1681
|
+
if (error instanceof ArcaInvalidSoapResponseError) {
|
|
1682
|
+
options.logger?.error("ARCA invalid SOAP response", {
|
|
1683
|
+
service: request.service,
|
|
1684
|
+
operation: request.operation,
|
|
1685
|
+
url,
|
|
1686
|
+
statusCode: error.statusCode,
|
|
1687
|
+
contentType: error.contentType,
|
|
1688
|
+
responseBodyLength: error.responseBodyLength,
|
|
1689
|
+
error
|
|
1690
|
+
});
|
|
1691
|
+
}
|
|
1561
1692
|
throw error;
|
|
1562
1693
|
}
|
|
1563
1694
|
}
|
|
@@ -1567,48 +1698,117 @@ function createSoapTransport(options) {
|
|
|
1567
1698
|
// src/wsaa/index.ts
|
|
1568
1699
|
import { createHash } from "crypto";
|
|
1569
1700
|
import forge from "node-forge";
|
|
1701
|
+
|
|
1702
|
+
// src/wsaa/session-store.ts
|
|
1703
|
+
var CREDENTIAL_EXPIRY_SAFETY_MARGIN_MS = 6e4;
|
|
1704
|
+
function createMemoryWsaaSessionStore() {
|
|
1705
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
1706
|
+
const locks = /* @__PURE__ */ new Map();
|
|
1707
|
+
return {
|
|
1708
|
+
get(key) {
|
|
1709
|
+
const credentials = sessions.get(serializeWsaaSessionKey(key));
|
|
1710
|
+
if (!(credentials && isWsaaCredentialValid(credentials))) {
|
|
1711
|
+
return Promise.resolve(null);
|
|
1712
|
+
}
|
|
1713
|
+
return Promise.resolve({ ...credentials });
|
|
1714
|
+
},
|
|
1715
|
+
set(key, credentials) {
|
|
1716
|
+
sessions.set(serializeWsaaSessionKey(key), { ...credentials });
|
|
1717
|
+
return Promise.resolve();
|
|
1718
|
+
},
|
|
1719
|
+
delete(key) {
|
|
1720
|
+
sessions.delete(serializeWsaaSessionKey(key));
|
|
1721
|
+
return Promise.resolve();
|
|
1722
|
+
},
|
|
1723
|
+
async withLock(key, fn) {
|
|
1724
|
+
const lockKey = serializeWsaaSessionKey(key);
|
|
1725
|
+
const previous = locks.get(lockKey) ?? Promise.resolve();
|
|
1726
|
+
let release = () => void 0;
|
|
1727
|
+
const current = new Promise((resolve) => {
|
|
1728
|
+
release = resolve;
|
|
1729
|
+
});
|
|
1730
|
+
const queued = previous.catch(() => void 0).then(() => current);
|
|
1731
|
+
locks.set(lockKey, queued);
|
|
1732
|
+
await previous.catch(() => void 0);
|
|
1733
|
+
try {
|
|
1734
|
+
return await fn();
|
|
1735
|
+
} finally {
|
|
1736
|
+
release();
|
|
1737
|
+
if (locks.get(lockKey) === queued) {
|
|
1738
|
+
locks.delete(lockKey);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
function serializeWsaaSessionKey(key) {
|
|
1745
|
+
return [key.environment, key.service, key.certificateFingerprint].join(":");
|
|
1746
|
+
}
|
|
1747
|
+
function isWsaaCredentialValid(credentials) {
|
|
1748
|
+
return new Date(credentials.expiresAt).getTime() - Date.now() > CREDENTIAL_EXPIRY_SAFETY_MARGIN_MS;
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
// src/wsaa/index.ts
|
|
1570
1752
|
function createWsaaAuthModule(options) {
|
|
1571
1753
|
const cache = /* @__PURE__ */ new Map();
|
|
1572
1754
|
const inFlight = /* @__PURE__ */ new Map();
|
|
1573
1755
|
return {
|
|
1574
1756
|
async login(service, authOptions = {}) {
|
|
1575
|
-
const
|
|
1757
|
+
const sessionKey = buildWsaaSessionKey(options.config, service);
|
|
1758
|
+
const cacheKey = serializeWsaaSessionKey(sessionKey);
|
|
1576
1759
|
const running = inFlight.get(cacheKey);
|
|
1577
1760
|
if (running) {
|
|
1578
1761
|
return running;
|
|
1579
1762
|
}
|
|
1580
1763
|
const loginPromise = (async () => {
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
});
|
|
1588
|
-
return cached;
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
options.logger?.debug("Attempting WSAA login", {
|
|
1764
|
+
const reuse = await getReusableCredentials({
|
|
1765
|
+
config: options.config,
|
|
1766
|
+
cache,
|
|
1767
|
+
cacheKey,
|
|
1768
|
+
sessionKey,
|
|
1769
|
+
logger: options.logger,
|
|
1592
1770
|
service,
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
const credentials = await requestCredentials(options.config, service, {
|
|
1596
|
-
logger: options.logger
|
|
1771
|
+
allowStore: !authOptions.forceRefresh,
|
|
1772
|
+
allowCache: !authOptions.forceRefresh
|
|
1597
1773
|
});
|
|
1598
|
-
|
|
1774
|
+
if (reuse) {
|
|
1775
|
+
return reuse;
|
|
1776
|
+
}
|
|
1777
|
+
const refresh = () => refreshWsaaCredentials({
|
|
1778
|
+
config: options.config,
|
|
1779
|
+
cache,
|
|
1780
|
+
cacheKey,
|
|
1781
|
+
sessionKey,
|
|
1782
|
+
logger: options.logger,
|
|
1599
1783
|
service,
|
|
1600
|
-
|
|
1784
|
+
forceRefresh: authOptions.forceRefresh === true
|
|
1601
1785
|
});
|
|
1602
|
-
|
|
1603
|
-
|
|
1786
|
+
if (options.config.wsaaSessionStore?.withLock) {
|
|
1787
|
+
return await withWsaaSessionStoreLock(
|
|
1788
|
+
options.config,
|
|
1789
|
+
sessionKey,
|
|
1790
|
+
service,
|
|
1791
|
+
refresh
|
|
1792
|
+
);
|
|
1793
|
+
}
|
|
1794
|
+
return await refresh();
|
|
1604
1795
|
})();
|
|
1605
1796
|
inFlight.set(cacheKey, loginPromise);
|
|
1606
1797
|
try {
|
|
1607
1798
|
return await loginPromise;
|
|
1608
1799
|
} catch (error) {
|
|
1609
|
-
if (
|
|
1610
|
-
const
|
|
1611
|
-
|
|
1800
|
+
if (error instanceof ArcaSoapFaultError && error.faultCode === "ns1:coe.alreadyAuthenticated") {
|
|
1801
|
+
const recovered = await getReusableCredentials({
|
|
1802
|
+
config: options.config,
|
|
1803
|
+
cache,
|
|
1804
|
+
cacheKey,
|
|
1805
|
+
sessionKey,
|
|
1806
|
+
logger: options.logger,
|
|
1807
|
+
service,
|
|
1808
|
+
allowStore: true,
|
|
1809
|
+
allowCache: true
|
|
1810
|
+
});
|
|
1811
|
+
if (recovered) {
|
|
1612
1812
|
options.logger?.warn(
|
|
1613
1813
|
"Recovered WSAA coe.alreadyAuthenticated fault",
|
|
1614
1814
|
{
|
|
@@ -1616,7 +1816,13 @@ function createWsaaAuthModule(options) {
|
|
|
1616
1816
|
faultCode: error.faultCode
|
|
1617
1817
|
}
|
|
1618
1818
|
);
|
|
1619
|
-
return
|
|
1819
|
+
return recovered;
|
|
1820
|
+
}
|
|
1821
|
+
if (!options.config.wsaaSessionStore) {
|
|
1822
|
+
throw new ArcaConfigurationError(
|
|
1823
|
+
"WSAA login failed because another process likely owns a valid TA. Configure a durable wsaaSessionStore for multi-process or serverless deployments.",
|
|
1824
|
+
{ cause: error }
|
|
1825
|
+
);
|
|
1620
1826
|
}
|
|
1621
1827
|
}
|
|
1622
1828
|
if (error instanceof ArcaSoapFaultError) {
|
|
@@ -1647,7 +1853,8 @@ async function requestCredentials(config, service, options) {
|
|
|
1647
1853
|
ARCA_WSAA_CONFIG.namespace,
|
|
1648
1854
|
{ in0: signedCms }
|
|
1649
1855
|
);
|
|
1650
|
-
const
|
|
1856
|
+
const url = ARCA_WSAA_CONFIG.endpoint[config.environment];
|
|
1857
|
+
const response = await postXmlWithMetadata({
|
|
1651
1858
|
url: ARCA_WSAA_CONFIG.endpoint[config.environment],
|
|
1652
1859
|
body: requestXml,
|
|
1653
1860
|
contentType: 'text/xml; charset="utf-8"',
|
|
@@ -1659,9 +1866,20 @@ async function requestCredentials(config, service, options) {
|
|
|
1659
1866
|
service: "wsaa",
|
|
1660
1867
|
operation: "loginCms"
|
|
1661
1868
|
});
|
|
1662
|
-
const
|
|
1663
|
-
|
|
1664
|
-
|
|
1869
|
+
const parseContext = {
|
|
1870
|
+
service: "wsaa",
|
|
1871
|
+
operation: "loginCms",
|
|
1872
|
+
endpointUrl: url,
|
|
1873
|
+
statusCode: response.statusCode,
|
|
1874
|
+
contentType: response.contentType,
|
|
1875
|
+
responseBody: response.body
|
|
1876
|
+
};
|
|
1877
|
+
const soapBody = parseSoapBody(response.body, parseContext);
|
|
1878
|
+
const [, responseBody] = getSingleBodyEntry(
|
|
1879
|
+
soapBody,
|
|
1880
|
+
parseContext
|
|
1881
|
+
);
|
|
1882
|
+
const loginCmsReturn = responseBody.loginCmsReturn;
|
|
1665
1883
|
if (typeof loginCmsReturn !== "string" || loginCmsReturn.trim().length < 1) {
|
|
1666
1884
|
throw new ArcaTransportError(
|
|
1667
1885
|
"WSAA response did not include loginCmsReturn XML"
|
|
@@ -1669,24 +1887,141 @@ async function requestCredentials(config, service, options) {
|
|
|
1669
1887
|
}
|
|
1670
1888
|
return parseLoginTicketResponse(loginCmsReturn);
|
|
1671
1889
|
}
|
|
1672
|
-
function
|
|
1673
|
-
return
|
|
1674
|
-
|
|
1675
|
-
|
|
1890
|
+
function buildWsaaSessionKey(config, service) {
|
|
1891
|
+
return {
|
|
1892
|
+
environment: config.environment,
|
|
1893
|
+
service,
|
|
1894
|
+
certificateFingerprint: getCertificateFingerprint(config)
|
|
1895
|
+
};
|
|
1676
1896
|
}
|
|
1677
1897
|
function getCertificateFingerprint(config) {
|
|
1678
1898
|
return createHash("sha256").update(config.certificatePem).digest("hex");
|
|
1679
1899
|
}
|
|
1680
|
-
function isCredentialValid(credentials) {
|
|
1681
|
-
return new Date(credentials.expiresAt).getTime() - Date.now() > 6e4;
|
|
1682
|
-
}
|
|
1683
1900
|
function getCachedCredentials(cache, cacheKey) {
|
|
1684
1901
|
const localCached = cache.get(cacheKey);
|
|
1685
|
-
if (localCached &&
|
|
1902
|
+
if (localCached && isWsaaCredentialValid(localCached)) {
|
|
1686
1903
|
return localCached;
|
|
1687
1904
|
}
|
|
1688
1905
|
return null;
|
|
1689
1906
|
}
|
|
1907
|
+
async function getReusableCredentials(options) {
|
|
1908
|
+
if (options.allowCache) {
|
|
1909
|
+
const cached = getCachedCredentials(options.cache, options.cacheKey);
|
|
1910
|
+
if (cached) {
|
|
1911
|
+
options.logger?.debug("Attempting WSAA login", {
|
|
1912
|
+
service: options.service,
|
|
1913
|
+
source: "cached"
|
|
1914
|
+
});
|
|
1915
|
+
return cached;
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
if (!(options.allowStore && options.config.wsaaSessionStore)) {
|
|
1919
|
+
return null;
|
|
1920
|
+
}
|
|
1921
|
+
const stored = await getStoredCredentials(
|
|
1922
|
+
options.config,
|
|
1923
|
+
options.sessionKey,
|
|
1924
|
+
options.service
|
|
1925
|
+
);
|
|
1926
|
+
if (!stored) {
|
|
1927
|
+
return null;
|
|
1928
|
+
}
|
|
1929
|
+
options.cache.set(options.cacheKey, stored);
|
|
1930
|
+
options.logger?.debug("Attempting WSAA login", {
|
|
1931
|
+
service: options.service,
|
|
1932
|
+
source: "store"
|
|
1933
|
+
});
|
|
1934
|
+
return stored;
|
|
1935
|
+
}
|
|
1936
|
+
async function refreshWsaaCredentials(options) {
|
|
1937
|
+
if (!options.forceRefresh) {
|
|
1938
|
+
const reuse = await getReusableCredentials({
|
|
1939
|
+
config: options.config,
|
|
1940
|
+
cache: options.cache,
|
|
1941
|
+
cacheKey: options.cacheKey,
|
|
1942
|
+
sessionKey: options.sessionKey,
|
|
1943
|
+
logger: options.logger,
|
|
1944
|
+
service: options.service,
|
|
1945
|
+
allowStore: true,
|
|
1946
|
+
allowCache: true
|
|
1947
|
+
});
|
|
1948
|
+
if (reuse) {
|
|
1949
|
+
return reuse;
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
options.logger?.debug("Attempting WSAA login", {
|
|
1953
|
+
service: options.service,
|
|
1954
|
+
source: "fresh"
|
|
1955
|
+
});
|
|
1956
|
+
const credentials = await requestCredentials(
|
|
1957
|
+
options.config,
|
|
1958
|
+
options.service,
|
|
1959
|
+
{
|
|
1960
|
+
logger: options.logger
|
|
1961
|
+
}
|
|
1962
|
+
);
|
|
1963
|
+
options.logger?.info("WSAA login succeeded", {
|
|
1964
|
+
service: options.service,
|
|
1965
|
+
expiresAt: credentials.expiresAt
|
|
1966
|
+
});
|
|
1967
|
+
options.cache.set(options.cacheKey, credentials);
|
|
1968
|
+
await setStoredCredentials(options.config, options.sessionKey, credentials);
|
|
1969
|
+
return credentials;
|
|
1970
|
+
}
|
|
1971
|
+
async function getStoredCredentials(config, key, service) {
|
|
1972
|
+
if (!config.wsaaSessionStore) {
|
|
1973
|
+
return null;
|
|
1974
|
+
}
|
|
1975
|
+
try {
|
|
1976
|
+
const credentials = await config.wsaaSessionStore.get(key);
|
|
1977
|
+
if (!(credentials && isWsaaCredentialValid(credentials))) {
|
|
1978
|
+
return null;
|
|
1979
|
+
}
|
|
1980
|
+
return credentials;
|
|
1981
|
+
} catch (error) {
|
|
1982
|
+
throw new ArcaConfigurationError(
|
|
1983
|
+
`WSAA session store get failed for service ${service}`,
|
|
1984
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
1985
|
+
);
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
async function setStoredCredentials(config, key, credentials) {
|
|
1989
|
+
if (!config.wsaaSessionStore) {
|
|
1990
|
+
return;
|
|
1991
|
+
}
|
|
1992
|
+
try {
|
|
1993
|
+
await config.wsaaSessionStore.set(key, credentials);
|
|
1994
|
+
} catch (error) {
|
|
1995
|
+
throw new ArcaConfigurationError(
|
|
1996
|
+
`WSAA session store set failed for service ${key.service}`,
|
|
1997
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
1998
|
+
);
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
async function withWsaaSessionStoreLock(config, key, service, fn) {
|
|
2002
|
+
const store = config.wsaaSessionStore;
|
|
2003
|
+
if (!store?.withLock) {
|
|
2004
|
+
return await fn();
|
|
2005
|
+
}
|
|
2006
|
+
let entered = false;
|
|
2007
|
+
try {
|
|
2008
|
+
return await store.withLock(key, async () => {
|
|
2009
|
+
entered = true;
|
|
2010
|
+
return await fn();
|
|
2011
|
+
});
|
|
2012
|
+
} catch (error) {
|
|
2013
|
+
if (entered) {
|
|
2014
|
+
throw error;
|
|
2015
|
+
}
|
|
2016
|
+
if (error instanceof ArcaConfigurationError) {
|
|
2017
|
+
throw error;
|
|
2018
|
+
}
|
|
2019
|
+
throw new ArcaConfigurationError(
|
|
2020
|
+
`WSAA session store lock failed for service ${service}`,
|
|
2021
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
2022
|
+
);
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
1690
2025
|
function buildLoginTicketRequest(service) {
|
|
1691
2026
|
const uniqueId = Math.floor(Date.now() / 1e3);
|
|
1692
2027
|
const generationTime = new Date(Date.now() - 5 * 6e4).toISOString().replace(".000Z", "Z");
|
|
@@ -1771,12 +2106,14 @@ export {
|
|
|
1771
2106
|
ArcaConfigurationError,
|
|
1772
2107
|
ArcaError,
|
|
1773
2108
|
ArcaInputError,
|
|
2109
|
+
ArcaInvalidSoapResponseError,
|
|
1774
2110
|
ArcaServiceError,
|
|
1775
2111
|
ArcaSoapFaultError,
|
|
1776
2112
|
ArcaTransportError,
|
|
1777
2113
|
assertArcaClientConfig,
|
|
1778
2114
|
createArcaClient,
|
|
1779
2115
|
createArcaClientConfigFromEnv,
|
|
2116
|
+
createMemoryWsaaSessionStore,
|
|
1780
2117
|
createPadronService,
|
|
1781
2118
|
createWsfeService,
|
|
1782
2119
|
createWsmtxcaService,
|