ksef-client-ts 0.6.0 → 0.6.2

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/dist/index.cjs CHANGED
@@ -175,12 +175,14 @@ var init_ksef_unauthorized_error = __esm({
175
175
  detail;
176
176
  traceId;
177
177
  instance;
178
+ timestamp;
178
179
  constructor(problemDetails) {
179
180
  super(problemDetails.detail || "Unauthorized");
180
181
  this.name = "KSeFUnauthorizedError";
181
182
  this.detail = problemDetails.detail;
182
183
  this.traceId = problemDetails.traceId;
183
184
  this.instance = problemDetails.instance;
185
+ this.timestamp = problemDetails.timestamp;
184
186
  }
185
187
  };
186
188
  }
@@ -199,6 +201,7 @@ var init_ksef_forbidden_error = __esm({
199
201
  instance;
200
202
  security;
201
203
  traceId;
204
+ timestamp;
202
205
  constructor(problemDetails) {
203
206
  super(problemDetails.detail || "Forbidden");
204
207
  this.name = "KSeFForbiddenError";
@@ -207,6 +210,31 @@ var init_ksef_forbidden_error = __esm({
207
210
  this.instance = problemDetails.instance;
208
211
  this.security = problemDetails.security;
209
212
  this.traceId = problemDetails.traceId;
213
+ this.timestamp = problemDetails.timestamp;
214
+ }
215
+ };
216
+ }
217
+ });
218
+
219
+ // src/errors/ksef-gone-error.ts
220
+ var KSeFGoneError;
221
+ var init_ksef_gone_error = __esm({
222
+ "src/errors/ksef-gone-error.ts"() {
223
+ "use strict";
224
+ init_ksef_error();
225
+ KSeFGoneError = class extends KSeFError {
226
+ statusCode = 410;
227
+ detail;
228
+ instance;
229
+ traceId;
230
+ timestamp;
231
+ constructor(problemDetails) {
232
+ super(problemDetails.detail || "Operation status no longer available (retention expired)");
233
+ this.name = "KSeFGoneError";
234
+ this.detail = problemDetails.detail;
235
+ this.instance = problemDetails.instance;
236
+ this.traceId = problemDetails.traceId;
237
+ this.timestamp = problemDetails.timestamp;
210
238
  }
211
239
  };
212
240
  }
@@ -274,6 +302,45 @@ var init_ksef_validation_error = __esm({
274
302
  }
275
303
  });
276
304
 
305
+ // src/errors/error-codes.ts
306
+ function hasErrorCode(body, code) {
307
+ return !!body?.exception?.exceptionDetailList?.some((d) => d.exceptionCode === code);
308
+ }
309
+ var KSeFErrorCode;
310
+ var init_error_codes = __esm({
311
+ "src/errors/error-codes.ts"() {
312
+ "use strict";
313
+ KSeFErrorCode = {
314
+ BatchTimeout: 21208,
315
+ DuplicateInvoice: 440
316
+ };
317
+ }
318
+ });
319
+
320
+ // src/errors/ksef-batch-timeout-error.ts
321
+ var KSeFBatchTimeoutError;
322
+ var init_ksef_batch_timeout_error = __esm({
323
+ "src/errors/ksef-batch-timeout-error.ts"() {
324
+ "use strict";
325
+ init_ksef_api_error();
326
+ init_error_codes();
327
+ KSeFBatchTimeoutError = class _KSeFBatchTimeoutError extends KSeFApiError {
328
+ errorCode = KSeFErrorCode.BatchTimeout;
329
+ constructor(message, statusCode, errorResponse) {
330
+ super(message, statusCode, errorResponse);
331
+ this.name = "KSeFBatchTimeoutError";
332
+ }
333
+ static fromResponse(statusCode, body) {
334
+ const detail = body?.exception?.exceptionDetailList?.find(
335
+ (d) => d.exceptionCode === KSeFErrorCode.BatchTimeout
336
+ );
337
+ const message = detail?.exceptionDescription?.trim() || "Batch session timed out before the server completed processing (KSeF 21208).";
338
+ return new _KSeFBatchTimeoutError(message, statusCode, body);
339
+ }
340
+ };
341
+ }
342
+ });
343
+
277
344
  // src/errors/index.ts
278
345
  var init_errors = __esm({
279
346
  "src/errors/index.ts"() {
@@ -283,9 +350,12 @@ var init_errors = __esm({
283
350
  init_ksef_rate_limit_error();
284
351
  init_ksef_unauthorized_error();
285
352
  init_ksef_forbidden_error();
353
+ init_ksef_gone_error();
286
354
  init_ksef_auth_status_error();
287
355
  init_ksef_session_expired_error();
288
356
  init_ksef_validation_error();
357
+ init_ksef_batch_timeout_error();
358
+ init_error_codes();
289
359
  }
290
360
  });
291
361
 
@@ -542,6 +612,9 @@ var init_rest_client = __esm({
542
612
  init_ksef_rate_limit_error();
543
613
  init_ksef_unauthorized_error();
544
614
  init_ksef_forbidden_error();
615
+ init_ksef_gone_error();
616
+ init_ksef_batch_timeout_error();
617
+ init_error_codes();
545
618
  init_route_builder();
546
619
  init_transport();
547
620
  init_retry_policy();
@@ -682,18 +755,33 @@ var init_rest_client = __esm({
682
755
  );
683
756
  }
684
757
  if (response.status === 401) {
685
- const body = parseJson();
686
- if (body?.detail) {
687
- throw new KSeFUnauthorizedError(body);
758
+ const body2 = parseJson();
759
+ if (body2?.detail) {
760
+ throw new KSeFUnauthorizedError(body2);
688
761
  }
689
762
  }
690
763
  if (response.status === 403) {
691
- const body = parseJson();
692
- if (body?.reasonCode) {
693
- throw new KSeFForbiddenError(body);
764
+ const body2 = parseJson();
765
+ if (body2?.reasonCode) {
766
+ throw new KSeFForbiddenError(body2);
694
767
  }
695
768
  }
696
- throw KSeFApiError.fromResponse(response.status, parseJson());
769
+ if (response.status === 410) {
770
+ const body2 = parseJson();
771
+ throw new KSeFGoneError({
772
+ title: body2?.title || "Gone",
773
+ status: body2?.status || 410,
774
+ detail: body2?.detail || "Operation status no longer available (retention expired)",
775
+ instance: body2?.instance,
776
+ traceId: body2?.traceId,
777
+ timestamp: body2?.timestamp
778
+ });
779
+ }
780
+ const body = parseJson();
781
+ if (hasErrorCode(body, KSeFErrorCode.BatchTimeout)) {
782
+ throw KSeFBatchTimeoutError.fromResponse(response.status, body);
783
+ }
784
+ throw KSeFApiError.fromResponse(response.status, body);
697
785
  }
698
786
  };
699
787
  }
@@ -977,7 +1065,9 @@ function isValidVatUe(value) {
977
1065
  return VatUe.test(value);
978
1066
  }
979
1067
  function isValidNipVatUe(value) {
980
- return NipVatUe.test(value);
1068
+ if (!NipVatUe.test(value)) return false;
1069
+ const nip = value.split("-")[0];
1070
+ return isValidNip(nip);
981
1071
  }
982
1072
  function isValidInternalId(value) {
983
1073
  return InternalId.test(value);
@@ -2990,6 +3080,32 @@ var init_online_session = __esm({
2990
3080
  }
2991
3081
  });
2992
3082
 
3083
+ // src/utils/concurrency.ts
3084
+ async function runWithConcurrency(tasks, parallelism) {
3085
+ if (tasks.length === 0) return;
3086
+ const limit = Math.max(1, Math.min(parallelism, tasks.length));
3087
+ const controller = new AbortController();
3088
+ let index = 0;
3089
+ const workers = Array.from({ length: limit }, async () => {
3090
+ while (index < tasks.length && !controller.signal.aborted) {
3091
+ const current = index;
3092
+ index += 1;
3093
+ try {
3094
+ await tasks[current](controller.signal);
3095
+ } catch (err) {
3096
+ controller.abort();
3097
+ throw err;
3098
+ }
3099
+ }
3100
+ });
3101
+ await Promise.all(workers);
3102
+ }
3103
+ var init_concurrency = __esm({
3104
+ "src/utils/concurrency.ts"() {
3105
+ "use strict";
3106
+ }
3107
+ });
3108
+
2993
3109
  // src/services/batch-session.ts
2994
3110
  var BatchSessionService;
2995
3111
  var init_batch_session = __esm({
@@ -2998,6 +3114,7 @@ var init_batch_session = __esm({
2998
3114
  init_ksef_feature();
2999
3115
  init_rest_request();
3000
3116
  init_routes();
3117
+ init_concurrency();
3001
3118
  BatchSessionService = class {
3002
3119
  restClient;
3003
3120
  constructor(restClient) {
@@ -3011,12 +3128,10 @@ var init_batch_session = __esm({
3011
3128
  const response = await this.restClient.execute(req);
3012
3129
  return response.body;
3013
3130
  }
3014
- async sendParts(openResponse, parts) {
3015
- const uploadRequests = openResponse.partUploadRequests;
3016
- const tasks = parts.map(async (part) => {
3017
- const uploadReq = uploadRequests.find(
3018
- (r) => r.ordinalNumber === part.ordinalNumber
3019
- );
3131
+ async sendParts(openResponse, parts, parallelism) {
3132
+ const uploadMap = new Map(openResponse.partUploadRequests.map((r) => [r.ordinalNumber, r]));
3133
+ const tasks = parts.map((part) => async (signal) => {
3134
+ const uploadReq = uploadMap.get(part.ordinalNumber);
3020
3135
  if (!uploadReq) {
3021
3136
  throw new Error(`No upload request found for part ${part.ordinalNumber}`);
3022
3137
  }
@@ -3024,25 +3139,38 @@ var init_batch_session = __esm({
3024
3139
  for (const [k, v] of Object.entries(uploadReq.headers)) {
3025
3140
  if (v != null) headers[k] = v;
3026
3141
  }
3027
- await fetch(uploadReq.url, {
3142
+ const resp = await fetch(uploadReq.url, {
3028
3143
  method: uploadReq.method,
3029
3144
  headers,
3030
- body: part.data
3145
+ body: part.data,
3146
+ signal
3031
3147
  });
3148
+ if (!resp.ok) {
3149
+ const body = await resp.text().catch(() => "");
3150
+ throw new Error(
3151
+ `Upload failed for part ${part.ordinalNumber}: HTTP ${resp.status}${body ? ` \u2014 ${body}` : ""}`
3152
+ );
3153
+ }
3032
3154
  });
3033
- await Promise.all(tasks);
3155
+ if (parallelism !== void 0) {
3156
+ await runWithConcurrency(tasks, parallelism);
3157
+ } else {
3158
+ const ac = new AbortController();
3159
+ await Promise.all(tasks.map((t) => t(ac.signal).catch((err) => {
3160
+ ac.abort();
3161
+ throw err;
3162
+ })));
3163
+ }
3034
3164
  }
3035
3165
  /**
3036
- * Upload parts sequentially (not in parallel) because each part uses a
3037
- * streaming body (`duplex: 'half'`). Parallel streaming uploads can cause
3038
- * backpressure issues and exceed memory limits for large payloads.
3166
+ * Upload parts using streaming bodies (`duplex: 'half'`).
3167
+ * By default uploads sequentially to avoid backpressure issues.
3168
+ * Pass `parallelism` to enable bounded concurrent uploads.
3039
3169
  */
3040
- async sendPartsWithStream(openResponse, parts) {
3041
- const uploadRequests = openResponse.partUploadRequests;
3042
- for (const part of parts) {
3043
- const uploadReq = uploadRequests.find(
3044
- (r) => r.ordinalNumber === part.ordinalNumber
3045
- );
3170
+ async sendPartsWithStream(openResponse, parts, parallelism) {
3171
+ const uploadMap = new Map(openResponse.partUploadRequests.map((r) => [r.ordinalNumber, r]));
3172
+ const uploadPart = async (part, signal) => {
3173
+ const uploadReq = uploadMap.get(part.ordinalNumber);
3046
3174
  if (!uploadReq) {
3047
3175
  throw new Error(`No upload request found for part ${part.ordinalNumber}`);
3048
3176
  }
@@ -3054,11 +3182,23 @@ var init_batch_session = __esm({
3054
3182
  method: uploadReq.method,
3055
3183
  headers,
3056
3184
  body: part.dataStream,
3185
+ signal,
3057
3186
  // @ts-expect-error -- Node 18+ undici supports duplex for streaming body
3058
3187
  duplex: "half"
3059
3188
  });
3060
3189
  if (!resp.ok) {
3061
- throw new Error(`Upload failed for part ${part.ordinalNumber}: HTTP ${resp.status}`);
3190
+ const body = await resp.text().catch(() => "");
3191
+ throw new Error(
3192
+ `Upload failed for part ${part.ordinalNumber}: HTTP ${resp.status}${body ? ` \u2014 ${body}` : ""}`
3193
+ );
3194
+ }
3195
+ };
3196
+ if (parallelism !== void 0) {
3197
+ await runWithConcurrency(parts.map((p) => (signal) => uploadPart(p, signal)), parallelism);
3198
+ } else {
3199
+ const { signal } = new AbortController();
3200
+ for (const part of parts) {
3201
+ await uploadPart(part, signal);
3062
3202
  }
3063
3203
  }
3064
3204
  }
@@ -3171,7 +3311,10 @@ var init_invoice_download = __esm({
3171
3311
  async getInvoice(ksefNumber) {
3172
3312
  const req = RestRequest.get(Routes.Invoices.byKsefNumber(ksefNumber));
3173
3313
  const response = await this.restClient.executeRaw(req);
3174
- return new TextDecoder().decode(response.body);
3314
+ return {
3315
+ xml: new TextDecoder().decode(response.body),
3316
+ hash: response.headers.get("x-ms-meta-hash") ?? void 0
3317
+ };
3175
3318
  }
3176
3319
  async queryInvoiceMetadata(filters, pageOffset, pageSize, sortOrder) {
3177
3320
  const req = RestRequest.post(Routes.Invoices.queryMetadata).body(filters);
@@ -4433,6 +4576,72 @@ var init_zip = __esm({
4433
4576
  }
4434
4577
  });
4435
4578
 
4579
+ // src/offline/holidays.ts
4580
+ function toDateKey(d) {
4581
+ return d.toISOString().slice(0, 10);
4582
+ }
4583
+ function dateKey(year, month, day) {
4584
+ return toDateKey(new Date(Date.UTC(year, month - 1, day)));
4585
+ }
4586
+ function addDays(d, n) {
4587
+ const result = new Date(d);
4588
+ result.setUTCDate(result.getUTCDate() + n);
4589
+ return result;
4590
+ }
4591
+ function computeEasterSunday(year) {
4592
+ const a = year % 19;
4593
+ const b = Math.floor(year / 100);
4594
+ const c = year % 100;
4595
+ const d = Math.floor(b / 4);
4596
+ const e = b % 4;
4597
+ const f = Math.floor((b + 8) / 25);
4598
+ const g = Math.floor((b - f + 1) / 3);
4599
+ const h = (19 * a + b - d - g + 15) % 30;
4600
+ const i = Math.floor(c / 4);
4601
+ const k = c % 4;
4602
+ const l = (32 + 2 * e + 2 * i - h - k) % 7;
4603
+ const m = Math.floor((a + 11 * h + 22 * l) / 451);
4604
+ const month = Math.floor((h + l - 7 * m + 114) / 31);
4605
+ const day = (h + l - 7 * m + 114) % 31 + 1;
4606
+ return new Date(Date.UTC(year, month - 1, day));
4607
+ }
4608
+ function getPolishHolidays(year) {
4609
+ const cached = holidayCache.get(year);
4610
+ if (cached) return cached;
4611
+ const easter = computeEasterSunday(year);
4612
+ const holidays = /* @__PURE__ */ new Set([
4613
+ // Fixed
4614
+ dateKey(year, 1, 1),
4615
+ dateKey(year, 1, 6),
4616
+ dateKey(year, 5, 1),
4617
+ dateKey(year, 5, 3),
4618
+ dateKey(year, 8, 15),
4619
+ dateKey(year, 11, 1),
4620
+ dateKey(year, 11, 11),
4621
+ dateKey(year, 12, 25),
4622
+ dateKey(year, 12, 26),
4623
+ // Wigilia — added by Dz.U. 2024 poz. 1965, effective from 2025
4624
+ ...year >= 2025 ? [dateKey(year, 12, 24)] : [],
4625
+ // Moveable
4626
+ toDateKey(easter),
4627
+ toDateKey(addDays(easter, 1)),
4628
+ toDateKey(addDays(easter, 49)),
4629
+ toDateKey(addDays(easter, 60))
4630
+ ]);
4631
+ holidayCache.set(year, holidays);
4632
+ return holidays;
4633
+ }
4634
+ function isPolishHoliday(date) {
4635
+ return getPolishHolidays(date.getUTCFullYear()).has(toDateKey(date));
4636
+ }
4637
+ var holidayCache;
4638
+ var init_holidays = __esm({
4639
+ "src/offline/holidays.ts"() {
4640
+ "use strict";
4641
+ holidayCache = /* @__PURE__ */ new Map();
4642
+ }
4643
+ });
4644
+
4436
4645
  // src/offline/deadline.ts
4437
4646
  function getDefaultReason(mode) {
4438
4647
  switch (mode) {
@@ -4449,7 +4658,7 @@ function getDefaultReason(mode) {
4449
4658
  function nextBusinessDay(from) {
4450
4659
  const d = new Date(from);
4451
4660
  d.setUTCDate(d.getUTCDate() + 1);
4452
- while (d.getUTCDay() === 0 || d.getUTCDay() === 6) {
4661
+ while (d.getUTCDay() === 0 || d.getUTCDay() === 6 || isPolishHoliday(d)) {
4453
4662
  d.setUTCDate(d.getUTCDate() + 1);
4454
4663
  }
4455
4664
  return d;
@@ -4462,7 +4671,7 @@ function addBusinessDays(from, days) {
4462
4671
  let remaining = days;
4463
4672
  while (remaining > 0) {
4464
4673
  d.setUTCDate(d.getUTCDate() + 1);
4465
- if (d.getUTCDay() !== 0 && d.getUTCDay() !== 6) {
4674
+ if (d.getUTCDay() !== 0 && d.getUTCDay() !== 6 && !isPolishHoliday(d)) {
4466
4675
  remaining--;
4467
4676
  }
4468
4677
  }
@@ -4528,16 +4737,17 @@ var FAR_FUTURE;
4528
4737
  var init_deadline = __esm({
4529
4738
  "src/offline/deadline.ts"() {
4530
4739
  "use strict";
4740
+ init_holidays();
4531
4741
  FAR_FUTURE = /* @__PURE__ */ new Date("9999-12-31T23:59:59Z");
4532
4742
  }
4533
4743
  });
4534
4744
 
4535
4745
  // src/workflows/offline-invoice-workflow.ts
4536
- var import_node_crypto2, OfflineInvoiceWorkflow;
4746
+ var import_node_crypto3, OfflineInvoiceWorkflow;
4537
4747
  var init_offline_invoice_workflow = __esm({
4538
4748
  "src/workflows/offline-invoice-workflow.ts"() {
4539
4749
  "use strict";
4540
- import_node_crypto2 = __toESM(require("crypto"), 1);
4750
+ import_node_crypto3 = __toESM(require("crypto"), 1);
4541
4751
  init_ksef_api_error();
4542
4752
  init_deadline();
4543
4753
  init_document_structures();
@@ -4557,7 +4767,7 @@ var init_offline_invoice_workflow = __esm({
4557
4767
  }
4558
4768
  const mode = options?.mode ?? "offline24";
4559
4769
  const reason = getDefaultReason(mode);
4560
- const invoiceHashBase64 = import_node_crypto2.default.createHash("sha256").update(input.invoiceXml).digest("base64");
4770
+ const invoiceHashBase64 = import_node_crypto3.default.createHash("sha256").update(input.invoiceXml).digest("base64");
4561
4771
  const kod1Url = this.qrService.buildInvoiceVerificationUrl(
4562
4772
  input.sellerNip,
4563
4773
  input.invoiceDate,
@@ -4576,7 +4786,7 @@ var init_offline_invoice_workflow = __esm({
4576
4786
  }
4577
4787
  const submitBy = options?.customDeadline ? typeof options.customDeadline === "string" ? options.customDeadline : options.customDeadline.toISOString() : calculateOfflineDeadline(mode, input.invoiceDate, options?.maintenanceWindow).toISOString();
4578
4788
  const metadata = {
4579
- id: import_node_crypto2.default.randomUUID(),
4789
+ id: import_node_crypto3.default.randomUUID(),
4580
4790
  mode,
4581
4791
  reason,
4582
4792
  status: "GENERATED",
@@ -4732,7 +4942,7 @@ var init_offline_invoice_workflow = __esm({
4732
4942
  original.status === "CORRECTED" ? `Invoice ${rejectedInvoiceId} has already been corrected (by ${original.correctedBy})` : `Only rejected invoices can be corrected (current status: ${original.status})`
4733
4943
  );
4734
4944
  }
4735
- const originalHash = import_node_crypto2.default.createHash("sha256").update(original.invoiceXml).digest("base64");
4945
+ const originalHash = import_node_crypto3.default.createHash("sha256").update(original.invoiceXml).digest("base64");
4736
4946
  await client.crypto.init();
4737
4947
  const encData = client.crypto.getEncryptionData();
4738
4948
  const formCode = options.formCode ?? DEFAULT_FORM_CODE;
@@ -4745,9 +4955,9 @@ var init_offline_invoice_workflow = __esm({
4745
4955
  const plainMeta = client.crypto.getFileMetadata(data);
4746
4956
  const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
4747
4957
  const encMeta = client.crypto.getFileMetadata(encrypted);
4748
- const correctionId = import_node_crypto2.default.randomUUID();
4958
+ const correctionId = import_node_crypto3.default.randomUUID();
4749
4959
  const submittedAt = (/* @__PURE__ */ new Date()).toISOString();
4750
- const correctedHashBase64 = import_node_crypto2.default.createHash("sha256").update(correctedInvoiceXml).digest("base64");
4960
+ const correctedHashBase64 = import_node_crypto3.default.createHash("sha256").update(correctedInvoiceXml).digest("base64");
4751
4961
  const kod1Url = this.qrService.buildInvoiceVerificationUrl(
4752
4962
  original.sellerNip,
4753
4963
  original.invoiceDate,
@@ -4902,6 +5112,7 @@ var init_client = __esm({
4902
5112
  qr;
4903
5113
  options;
4904
5114
  authManager;
5115
+ _baseRestClientConfig;
4905
5116
  _offline;
4906
5117
  constructor(options) {
4907
5118
  this.options = resolveOptions(options);
@@ -4913,6 +5124,8 @@ var init_client = __esm({
4913
5124
  });
4914
5125
  this.authManager = authManager;
4915
5126
  const restClientConfig = buildRestClientConfig(options, authManager);
5127
+ const { authManager: _am, ...baseConfig } = restClientConfig;
5128
+ this._baseRestClientConfig = baseConfig;
4916
5129
  const restClient = new RestClient(this.options, restClientConfig);
4917
5130
  const fetcher = new CertificateFetcher(restClient);
4918
5131
  this.crypto = new CryptographyService(fetcher);
@@ -4937,6 +5150,10 @@ var init_client = __esm({
4937
5150
  }
4938
5151
  return this._offline;
4939
5152
  }
5153
+ /** @internal Create a RestClient with a different AuthManager, preserving transport/retry/rateLimit config. */
5154
+ createScopedRestClient(authManager) {
5155
+ return new RestClient(this.options, { ...this._baseRestClientConfig, authManager });
5156
+ }
4940
5157
  async loginWithToken(token, nip) {
4941
5158
  const challenge = await this.auth.getChallenge();
4942
5159
  await this.crypto.init();
@@ -4951,6 +5168,7 @@ var init_client = __esm({
4951
5168
  const tokens = await this.auth.getAccessToken(authToken);
4952
5169
  this.authManager.setAccessToken(tokens.accessToken.token);
4953
5170
  this.authManager.setRefreshToken(tokens.refreshToken.token);
5171
+ return { clientIp: challenge.clientIp };
4954
5172
  }
4955
5173
  async loginWithCertificate(certPem, keyPem, nip, keyPassword) {
4956
5174
  const challenge = await this.auth.getChallenge();
@@ -4963,11 +5181,12 @@ var init_client = __esm({
4963
5181
  const tokens = await this.auth.getAccessToken(authToken);
4964
5182
  this.authManager.setAccessToken(tokens.accessToken.token);
4965
5183
  this.authManager.setRefreshToken(tokens.refreshToken.token);
5184
+ return { clientIp: challenge.clientIp };
4966
5185
  }
4967
5186
  async loginWithPkcs12(p12, password, nip) {
4968
5187
  const { Pkcs12Loader: Pkcs12Loader2 } = await Promise.resolve().then(() => (init_pkcs12_loader(), pkcs12_loader_exports));
4969
5188
  const { certificatePem, privateKeyPem } = Pkcs12Loader2.load(p12, password);
4970
- await this.loginWithCertificate(certificatePem, privateKeyPem, nip);
5189
+ return this.loginWithCertificate(certificatePem, privateKeyPem, nip);
4971
5190
  }
4972
5191
  async awaitAuthReady(referenceNumber, authToken) {
4973
5192
  for (let i = 0; i < 30; i++) {
@@ -5030,9 +5249,12 @@ __export(index_exports, {
5030
5249
  KSEF_FEATURE_HEADER: () => KSEF_FEATURE_HEADER,
5031
5250
  KSeFApiError: () => KSeFApiError,
5032
5251
  KSeFAuthStatusError: () => KSeFAuthStatusError,
5252
+ KSeFBatchTimeoutError: () => KSeFBatchTimeoutError,
5033
5253
  KSeFClient: () => KSeFClient,
5034
5254
  KSeFError: () => KSeFError,
5255
+ KSeFErrorCode: () => KSeFErrorCode,
5035
5256
  KSeFForbiddenError: () => KSeFForbiddenError,
5257
+ KSeFGoneError: () => KSeFGoneError,
5036
5258
  KSeFRateLimitError: () => KSeFRateLimitError,
5037
5259
  KSeFSessionExpiredError: () => KSeFSessionExpiredError,
5038
5260
  KSeFUnauthorizedError: () => KSeFUnauthorizedError,
@@ -5097,9 +5319,11 @@ __export(index_exports, {
5097
5319
  getDefaultReason: () => getDefaultReason,
5098
5320
  getEffectiveStartDate: () => getEffectiveStartDate,
5099
5321
  getFormCode: () => getFormCode,
5322
+ getPolishHolidays: () => getPolishHolidays,
5100
5323
  getTimeUntilDeadline: () => getTimeUntilDeadline,
5101
5324
  incrementalExportAndDownload: () => incrementalExportAndDownload,
5102
5325
  isExpired: () => isExpired,
5326
+ isPolishHoliday: () => isPolishHoliday,
5103
5327
  isRetryableError: () => isRetryableError,
5104
5328
  isRetryableStatus: () => isRetryableStatus,
5105
5329
  isValidBase64: () => isValidBase64,
@@ -5126,6 +5350,9 @@ __export(index_exports, {
5126
5350
  parseUpoXml: () => parseUpoXml,
5127
5351
  pollUntil: () => pollUntil,
5128
5352
  resolveOptions: () => resolveOptions,
5353
+ resumeOnlineSession: () => resumeOnlineSession,
5354
+ runWithConcurrency: () => runWithConcurrency,
5355
+ sha256Base64: () => sha256Base642,
5129
5356
  sleep: () => sleep,
5130
5357
  unzip: () => unzip,
5131
5358
  updateContinuationPoint: () => updateContinuationPoint,
@@ -5140,6 +5367,7 @@ __export(index_exports, {
5140
5367
  validatePresignedUrl: () => validatePresignedUrl,
5141
5368
  validateSchema: () => validateSchema,
5142
5369
  validateWellFormedness: () => validateWellFormedness,
5370
+ verifyHash: () => verifyHash,
5143
5371
  xmlToObject: () => xmlToObject
5144
5372
  });
5145
5373
  module.exports = __toCommonJS(index_exports);
@@ -5911,6 +6139,18 @@ function parseKSeFTokenContext(token) {
5911
6139
  };
5912
6140
  }
5913
6141
 
6142
+ // src/utils/hash.ts
6143
+ var import_node_crypto2 = __toESM(require("crypto"), 1);
6144
+ function sha256Base642(data) {
6145
+ return import_node_crypto2.default.createHash("sha256").update(data).digest("base64");
6146
+ }
6147
+ function verifyHash(data, expectedHash) {
6148
+ return sha256Base642(data) === expectedHash;
6149
+ }
6150
+
6151
+ // src/utils/index.ts
6152
+ init_concurrency();
6153
+
5914
6154
  // src/workflows/polling.ts
5915
6155
  async function pollUntil(action, condition, options) {
5916
6156
  const intervalMs = options?.intervalMs ?? 2e3;
@@ -5929,6 +6169,10 @@ async function pollUntil(action, condition, options) {
5929
6169
  }
5930
6170
 
5931
6171
  // src/workflows/online-session-workflow.ts
6172
+ init_ksef_session_expired_error();
6173
+ init_auth_manager();
6174
+ init_online_session();
6175
+ init_session_status();
5932
6176
  init_document_structures();
5933
6177
 
5934
6178
  // src/xml/upo-parser.ts
@@ -6092,18 +6336,11 @@ function nonEmptyString(value) {
6092
6336
  // src/workflows/online-session-workflow.ts
6093
6337
  init_invoice_validator();
6094
6338
  init_ksef_validation_error();
6095
- async function openOnlineSession(client, options) {
6096
- await client.crypto.init();
6097
- const encData = client.crypto.getEncryptionData();
6098
- const formCode = options?.formCode ?? DEFAULT_FORM_CODE;
6099
- const openResp = await client.onlineSession.openSession(
6100
- { formCode, encryption: encData.encryptionInfo },
6101
- options?.upoVersion
6102
- );
6103
- const sessionRef = openResp.referenceNumber;
6339
+ function buildSessionHandle(params) {
6340
+ const { deps, sessionRef, validUntil, cipherKey, cipherIv, formCode, validate: validate2 } = params;
6104
6341
  async function fetchUpo(pollOpts) {
6105
6342
  const result = await pollUntil(
6106
- () => client.sessionStatus.getSessionStatus(sessionRef),
6343
+ () => deps.sessionStatus.getSessionStatus(sessionRef),
6107
6344
  (s) => s.status.code === 200 || s.status.code >= 400,
6108
6345
  { ...pollOpts, description: `UPO for session ${sessionRef}` }
6109
6346
  );
@@ -6119,9 +6356,9 @@ async function openOnlineSession(client, options) {
6119
6356
  }
6120
6357
  return {
6121
6358
  sessionRef,
6122
- validUntil: openResp.validUntil,
6359
+ validUntil,
6123
6360
  async sendInvoice(invoiceXml) {
6124
- if (options?.validate) {
6361
+ if (validate2) {
6125
6362
  const xmlStr = typeof invoiceXml === "string" ? invoiceXml : new TextDecoder().decode(invoiceXml);
6126
6363
  const vResult = await validate(xmlStr);
6127
6364
  if (!vResult.valid) {
@@ -6132,10 +6369,10 @@ async function openOnlineSession(client, options) {
6132
6369
  }
6133
6370
  }
6134
6371
  const data = typeof invoiceXml === "string" ? new TextEncoder().encode(invoiceXml) : invoiceXml;
6135
- const plainMeta = client.crypto.getFileMetadata(data);
6136
- const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
6137
- const encMeta = client.crypto.getFileMetadata(encrypted);
6138
- const resp = await client.onlineSession.sendInvoice(sessionRef, {
6372
+ const plainMeta = deps.crypto.getFileMetadata(data);
6373
+ const encrypted = deps.crypto.encryptAES256(data, cipherKey, cipherIv);
6374
+ const encMeta = deps.crypto.getFileMetadata(encrypted);
6375
+ const resp = await deps.onlineSession.sendInvoice(sessionRef, {
6139
6376
  invoiceHash: plainMeta.hashSHA,
6140
6377
  invoiceSize: plainMeta.fileSize,
6141
6378
  encryptedInvoiceHash: encMeta.hashSHA,
@@ -6145,7 +6382,7 @@ async function openOnlineSession(client, options) {
6145
6382
  return resp.referenceNumber;
6146
6383
  },
6147
6384
  async close() {
6148
- await client.onlineSession.closeSession(sessionRef);
6385
+ await deps.onlineSession.closeSession(sessionRef);
6149
6386
  },
6150
6387
  async waitForUpo(pollOpts) {
6151
6388
  return fetchUpo(pollOpts);
@@ -6154,13 +6391,75 @@ async function openOnlineSession(client, options) {
6154
6391
  const upoInfo = await fetchUpo(pollOpts);
6155
6392
  const parsed = [];
6156
6393
  for (const page of upoInfo.pages) {
6157
- const result = await client.sessionStatus.getSessionUpo(sessionRef, page.referenceNumber);
6394
+ const result = await deps.sessionStatus.getSessionUpo(sessionRef, page.referenceNumber);
6158
6395
  parsed.push(parseUpoXml(result.upo));
6159
6396
  }
6160
6397
  return { ...upoInfo, parsed };
6398
+ },
6399
+ getState() {
6400
+ const token = deps.getAccessToken();
6401
+ if (!token) {
6402
+ throw new Error("Cannot serialize session state: no access token available");
6403
+ }
6404
+ return {
6405
+ referenceNumber: sessionRef,
6406
+ aesKey: Buffer.from(cipherKey).toString("base64"),
6407
+ iv: Buffer.from(cipherIv).toString("base64"),
6408
+ accessToken: token,
6409
+ formCode,
6410
+ validUntil,
6411
+ ...validate2 ? { validate: validate2 } : {}
6412
+ };
6161
6413
  }
6162
6414
  };
6163
6415
  }
6416
+ async function openOnlineSession(client, options) {
6417
+ await client.crypto.init();
6418
+ const encData = client.crypto.getEncryptionData();
6419
+ const formCode = options?.formCode ?? DEFAULT_FORM_CODE;
6420
+ const openResp = await client.onlineSession.openSession(
6421
+ { formCode, encryption: encData.encryptionInfo },
6422
+ options?.upoVersion
6423
+ );
6424
+ return buildSessionHandle({
6425
+ deps: {
6426
+ crypto: client.crypto,
6427
+ onlineSession: client.onlineSession,
6428
+ sessionStatus: client.sessionStatus,
6429
+ getAccessToken: () => client.authManager.getAccessToken()
6430
+ },
6431
+ sessionRef: openResp.referenceNumber,
6432
+ validUntil: openResp.validUntil,
6433
+ cipherKey: encData.cipherKey,
6434
+ cipherIv: encData.cipherIv,
6435
+ formCode,
6436
+ validate: options?.validate
6437
+ });
6438
+ }
6439
+ function resumeOnlineSession(client, state, options) {
6440
+ const expiry = new Date(state.validUntil);
6441
+ if (expiry.getTime() <= Date.now()) {
6442
+ throw new KSeFSessionExpiredError(
6443
+ `Cannot resume session: expired at ${state.validUntil}`
6444
+ );
6445
+ }
6446
+ const scopedAuth = new DefaultAuthManager(() => Promise.resolve(null), state.accessToken);
6447
+ const scopedRestClient = client.createScopedRestClient(scopedAuth);
6448
+ return buildSessionHandle({
6449
+ deps: {
6450
+ crypto: client.crypto,
6451
+ onlineSession: new OnlineSessionService(scopedRestClient),
6452
+ sessionStatus: new SessionStatusService(scopedRestClient),
6453
+ getAccessToken: () => scopedAuth.getAccessToken()
6454
+ },
6455
+ sessionRef: state.referenceNumber,
6456
+ validUntil: state.validUntil,
6457
+ cipherKey: new Uint8Array(Buffer.from(state.aesKey, "base64")),
6458
+ cipherIv: new Uint8Array(Buffer.from(state.iv, "base64")),
6459
+ formCode: state.formCode,
6460
+ validate: options?.validate ?? state.validate
6461
+ });
6462
+ }
6164
6463
  async function openSendAndClose(client, invoices, options) {
6165
6464
  const handle = await openOnlineSession(client, options);
6166
6465
  for (const inv of invoices) {
@@ -6173,6 +6472,9 @@ async function openSendAndClose(client, invoices, options) {
6173
6472
  // src/workflows/batch-session-workflow.ts
6174
6473
  init_document_structures();
6175
6474
  async function uploadBatch(client, zipData, options) {
6475
+ if (options?.parallelism !== void 0 && (!Number.isInteger(options.parallelism) || options.parallelism < 1)) {
6476
+ throw new Error("parallelism must be a positive integer");
6477
+ }
6176
6478
  await client.crypto.init();
6177
6479
  if (options?.validate) {
6178
6480
  const { unzip: unzip2 } = await Promise.resolve().then(() => (init_zip(), zip_exports));
@@ -6215,7 +6517,7 @@ async function uploadBatch(client, zipData, options) {
6215
6517
  },
6216
6518
  ordinalNumber: i + 1
6217
6519
  }));
6218
- await client.batchSession.sendParts(openResp, sendingParts);
6520
+ await client.batchSession.sendParts(openResp, sendingParts, options?.parallelism);
6219
6521
  await client.batchSession.closeSession(openResp.referenceNumber);
6220
6522
  const result = await pollUntil(
6221
6523
  () => client.sessionStatus.getSessionStatus(openResp.referenceNumber),
@@ -6236,6 +6538,9 @@ async function uploadBatch(client, zipData, options) {
6236
6538
  };
6237
6539
  }
6238
6540
  async function uploadBatchStream(client, zipStreamFactory, zipSize, options) {
6541
+ if (options?.parallelism !== void 0 && (!Number.isInteger(options.parallelism) || options.parallelism < 1)) {
6542
+ throw new Error("parallelism must be a positive integer");
6543
+ }
6239
6544
  await client.crypto.init();
6240
6545
  const encData = client.crypto.getEncryptionData();
6241
6546
  const formCode = options?.formCode ?? DEFAULT_FORM_CODE;
@@ -6257,7 +6562,7 @@ async function uploadBatchStream(client, zipStreamFactory, zipSize, options) {
6257
6562
  },
6258
6563
  options?.upoVersion
6259
6564
  );
6260
- await client.batchSession.sendPartsWithStream(openResp, streamParts);
6565
+ await client.batchSession.sendPartsWithStream(openResp, streamParts, options?.parallelism);
6261
6566
  await client.batchSession.closeSession(openResp.referenceNumber);
6262
6567
  const result = await pollUntil(
6263
6568
  () => client.sessionStatus.getSessionStatus(openResp.referenceNumber),
@@ -6332,6 +6637,7 @@ async function doExport(client, filters, options) {
6332
6637
  url: p.url,
6333
6638
  method: p.method,
6334
6639
  partSize: p.partSize,
6640
+ partHash: p.partHash,
6335
6641
  encryptedPartSize: p.encryptedPartSize,
6336
6642
  encryptedPartHash: p.encryptedPartHash,
6337
6643
  expirationDate: p.expirationDate
@@ -6357,6 +6663,9 @@ async function exportAndDownload(client, filters, options) {
6357
6663
  throw new Error(`Download failed for part ${part.ordinalNumber}: HTTP ${resp.status}`);
6358
6664
  }
6359
6665
  const encryptedData = new Uint8Array(await resp.arrayBuffer());
6666
+ if (options?.verifyHash !== false && !verifyHash(encryptedData, part.encryptedPartHash)) {
6667
+ throw new Error(`Hash mismatch for export part ${part.ordinalNumber}`);
6668
+ }
6360
6669
  const decrypted = client.crypto.decryptAES256(encryptedData, encData.cipherKey, encData.cipherIv);
6361
6670
  decryptedParts.push(decrypted);
6362
6671
  }
@@ -6429,6 +6738,9 @@ async function incrementalExportAndDownload(client, options) {
6429
6738
  throw new Error(`Download failed for part ${part.ordinalNumber}: HTTP ${resp.status}`);
6430
6739
  }
6431
6740
  const encryptedData = new Uint8Array(await resp.arrayBuffer());
6741
+ if (options.verifyHash !== false && !verifyHash(encryptedData, part.encryptedPartHash)) {
6742
+ throw new Error(`Hash mismatch for export part ${part.ordinalNumber}`);
6743
+ }
6432
6744
  const decrypted = client.crypto.decryptAES256(encryptedData, encData.cipherKey, encData.cipherIv);
6433
6745
  decryptedParts.push(decrypted);
6434
6746
  }
@@ -6594,6 +6906,7 @@ async function authenticateWithPkcs12(client, options) {
6594
6906
 
6595
6907
  // src/offline/index.ts
6596
6908
  init_deadline();
6909
+ init_holidays();
6597
6910
 
6598
6911
  // src/offline/storage.ts
6599
6912
  function matchesFilter(invoice, filter) {
@@ -6769,9 +7082,12 @@ init_client();
6769
7082
  KSEF_FEATURE_HEADER,
6770
7083
  KSeFApiError,
6771
7084
  KSeFAuthStatusError,
7085
+ KSeFBatchTimeoutError,
6772
7086
  KSeFClient,
6773
7087
  KSeFError,
7088
+ KSeFErrorCode,
6774
7089
  KSeFForbiddenError,
7090
+ KSeFGoneError,
6775
7091
  KSeFRateLimitError,
6776
7092
  KSeFSessionExpiredError,
6777
7093
  KSeFUnauthorizedError,
@@ -6836,9 +7152,11 @@ init_client();
6836
7152
  getDefaultReason,
6837
7153
  getEffectiveStartDate,
6838
7154
  getFormCode,
7155
+ getPolishHolidays,
6839
7156
  getTimeUntilDeadline,
6840
7157
  incrementalExportAndDownload,
6841
7158
  isExpired,
7159
+ isPolishHoliday,
6842
7160
  isRetryableError,
6843
7161
  isRetryableStatus,
6844
7162
  isValidBase64,
@@ -6865,6 +7183,9 @@ init_client();
6865
7183
  parseUpoXml,
6866
7184
  pollUntil,
6867
7185
  resolveOptions,
7186
+ resumeOnlineSession,
7187
+ runWithConcurrency,
7188
+ sha256Base64,
6868
7189
  sleep,
6869
7190
  unzip,
6870
7191
  updateContinuationPoint,
@@ -6879,6 +7200,7 @@ init_client();
6879
7200
  validatePresignedUrl,
6880
7201
  validateSchema,
6881
7202
  validateWellFormedness,
7203
+ verifyHash,
6882
7204
  xmlToObject
6883
7205
  });
6884
7206
  //# sourceMappingURL=index.cjs.map