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.js CHANGED
@@ -153,12 +153,14 @@ var init_ksef_unauthorized_error = __esm({
153
153
  detail;
154
154
  traceId;
155
155
  instance;
156
+ timestamp;
156
157
  constructor(problemDetails) {
157
158
  super(problemDetails.detail || "Unauthorized");
158
159
  this.name = "KSeFUnauthorizedError";
159
160
  this.detail = problemDetails.detail;
160
161
  this.traceId = problemDetails.traceId;
161
162
  this.instance = problemDetails.instance;
163
+ this.timestamp = problemDetails.timestamp;
162
164
  }
163
165
  };
164
166
  }
@@ -177,6 +179,7 @@ var init_ksef_forbidden_error = __esm({
177
179
  instance;
178
180
  security;
179
181
  traceId;
182
+ timestamp;
180
183
  constructor(problemDetails) {
181
184
  super(problemDetails.detail || "Forbidden");
182
185
  this.name = "KSeFForbiddenError";
@@ -185,6 +188,31 @@ var init_ksef_forbidden_error = __esm({
185
188
  this.instance = problemDetails.instance;
186
189
  this.security = problemDetails.security;
187
190
  this.traceId = problemDetails.traceId;
191
+ this.timestamp = problemDetails.timestamp;
192
+ }
193
+ };
194
+ }
195
+ });
196
+
197
+ // src/errors/ksef-gone-error.ts
198
+ var KSeFGoneError;
199
+ var init_ksef_gone_error = __esm({
200
+ "src/errors/ksef-gone-error.ts"() {
201
+ "use strict";
202
+ init_ksef_error();
203
+ KSeFGoneError = class extends KSeFError {
204
+ statusCode = 410;
205
+ detail;
206
+ instance;
207
+ traceId;
208
+ timestamp;
209
+ constructor(problemDetails) {
210
+ super(problemDetails.detail || "Operation status no longer available (retention expired)");
211
+ this.name = "KSeFGoneError";
212
+ this.detail = problemDetails.detail;
213
+ this.instance = problemDetails.instance;
214
+ this.traceId = problemDetails.traceId;
215
+ this.timestamp = problemDetails.timestamp;
188
216
  }
189
217
  };
190
218
  }
@@ -252,6 +280,45 @@ var init_ksef_validation_error = __esm({
252
280
  }
253
281
  });
254
282
 
283
+ // src/errors/error-codes.ts
284
+ function hasErrorCode(body, code) {
285
+ return !!body?.exception?.exceptionDetailList?.some((d) => d.exceptionCode === code);
286
+ }
287
+ var KSeFErrorCode;
288
+ var init_error_codes = __esm({
289
+ "src/errors/error-codes.ts"() {
290
+ "use strict";
291
+ KSeFErrorCode = {
292
+ BatchTimeout: 21208,
293
+ DuplicateInvoice: 440
294
+ };
295
+ }
296
+ });
297
+
298
+ // src/errors/ksef-batch-timeout-error.ts
299
+ var KSeFBatchTimeoutError;
300
+ var init_ksef_batch_timeout_error = __esm({
301
+ "src/errors/ksef-batch-timeout-error.ts"() {
302
+ "use strict";
303
+ init_ksef_api_error();
304
+ init_error_codes();
305
+ KSeFBatchTimeoutError = class _KSeFBatchTimeoutError extends KSeFApiError {
306
+ errorCode = KSeFErrorCode.BatchTimeout;
307
+ constructor(message, statusCode, errorResponse) {
308
+ super(message, statusCode, errorResponse);
309
+ this.name = "KSeFBatchTimeoutError";
310
+ }
311
+ static fromResponse(statusCode, body) {
312
+ const detail = body?.exception?.exceptionDetailList?.find(
313
+ (d) => d.exceptionCode === KSeFErrorCode.BatchTimeout
314
+ );
315
+ const message = detail?.exceptionDescription?.trim() || "Batch session timed out before the server completed processing (KSeF 21208).";
316
+ return new _KSeFBatchTimeoutError(message, statusCode, body);
317
+ }
318
+ };
319
+ }
320
+ });
321
+
255
322
  // src/errors/index.ts
256
323
  var init_errors = __esm({
257
324
  "src/errors/index.ts"() {
@@ -261,9 +328,12 @@ var init_errors = __esm({
261
328
  init_ksef_rate_limit_error();
262
329
  init_ksef_unauthorized_error();
263
330
  init_ksef_forbidden_error();
331
+ init_ksef_gone_error();
264
332
  init_ksef_auth_status_error();
265
333
  init_ksef_session_expired_error();
266
334
  init_ksef_validation_error();
335
+ init_ksef_batch_timeout_error();
336
+ init_error_codes();
267
337
  }
268
338
  });
269
339
 
@@ -520,6 +590,9 @@ var init_rest_client = __esm({
520
590
  init_ksef_rate_limit_error();
521
591
  init_ksef_unauthorized_error();
522
592
  init_ksef_forbidden_error();
593
+ init_ksef_gone_error();
594
+ init_ksef_batch_timeout_error();
595
+ init_error_codes();
523
596
  init_route_builder();
524
597
  init_transport();
525
598
  init_retry_policy();
@@ -660,18 +733,33 @@ var init_rest_client = __esm({
660
733
  );
661
734
  }
662
735
  if (response.status === 401) {
663
- const body = parseJson();
664
- if (body?.detail) {
665
- throw new KSeFUnauthorizedError(body);
736
+ const body2 = parseJson();
737
+ if (body2?.detail) {
738
+ throw new KSeFUnauthorizedError(body2);
666
739
  }
667
740
  }
668
741
  if (response.status === 403) {
669
- const body = parseJson();
670
- if (body?.reasonCode) {
671
- throw new KSeFForbiddenError(body);
742
+ const body2 = parseJson();
743
+ if (body2?.reasonCode) {
744
+ throw new KSeFForbiddenError(body2);
672
745
  }
673
746
  }
674
- throw KSeFApiError.fromResponse(response.status, parseJson());
747
+ if (response.status === 410) {
748
+ const body2 = parseJson();
749
+ throw new KSeFGoneError({
750
+ title: body2?.title || "Gone",
751
+ status: body2?.status || 410,
752
+ detail: body2?.detail || "Operation status no longer available (retention expired)",
753
+ instance: body2?.instance,
754
+ traceId: body2?.traceId,
755
+ timestamp: body2?.timestamp
756
+ });
757
+ }
758
+ const body = parseJson();
759
+ if (hasErrorCode(body, KSeFErrorCode.BatchTimeout)) {
760
+ throw KSeFBatchTimeoutError.fromResponse(response.status, body);
761
+ }
762
+ throw KSeFApiError.fromResponse(response.status, body);
675
763
  }
676
764
  };
677
765
  }
@@ -955,7 +1043,9 @@ function isValidVatUe(value) {
955
1043
  return VatUe.test(value);
956
1044
  }
957
1045
  function isValidNipVatUe(value) {
958
- return NipVatUe.test(value);
1046
+ if (!NipVatUe.test(value)) return false;
1047
+ const nip = value.split("-")[0];
1048
+ return isValidNip(nip);
959
1049
  }
960
1050
  function isValidInternalId(value) {
961
1051
  return InternalId.test(value);
@@ -2967,6 +3057,32 @@ var init_online_session = __esm({
2967
3057
  }
2968
3058
  });
2969
3059
 
3060
+ // src/utils/concurrency.ts
3061
+ async function runWithConcurrency(tasks, parallelism) {
3062
+ if (tasks.length === 0) return;
3063
+ const limit = Math.max(1, Math.min(parallelism, tasks.length));
3064
+ const controller = new AbortController();
3065
+ let index = 0;
3066
+ const workers = Array.from({ length: limit }, async () => {
3067
+ while (index < tasks.length && !controller.signal.aborted) {
3068
+ const current = index;
3069
+ index += 1;
3070
+ try {
3071
+ await tasks[current](controller.signal);
3072
+ } catch (err) {
3073
+ controller.abort();
3074
+ throw err;
3075
+ }
3076
+ }
3077
+ });
3078
+ await Promise.all(workers);
3079
+ }
3080
+ var init_concurrency = __esm({
3081
+ "src/utils/concurrency.ts"() {
3082
+ "use strict";
3083
+ }
3084
+ });
3085
+
2970
3086
  // src/services/batch-session.ts
2971
3087
  var BatchSessionService;
2972
3088
  var init_batch_session = __esm({
@@ -2975,6 +3091,7 @@ var init_batch_session = __esm({
2975
3091
  init_ksef_feature();
2976
3092
  init_rest_request();
2977
3093
  init_routes();
3094
+ init_concurrency();
2978
3095
  BatchSessionService = class {
2979
3096
  restClient;
2980
3097
  constructor(restClient) {
@@ -2988,12 +3105,10 @@ var init_batch_session = __esm({
2988
3105
  const response = await this.restClient.execute(req);
2989
3106
  return response.body;
2990
3107
  }
2991
- async sendParts(openResponse, parts) {
2992
- const uploadRequests = openResponse.partUploadRequests;
2993
- const tasks = parts.map(async (part) => {
2994
- const uploadReq = uploadRequests.find(
2995
- (r) => r.ordinalNumber === part.ordinalNumber
2996
- );
3108
+ async sendParts(openResponse, parts, parallelism) {
3109
+ const uploadMap = new Map(openResponse.partUploadRequests.map((r) => [r.ordinalNumber, r]));
3110
+ const tasks = parts.map((part) => async (signal) => {
3111
+ const uploadReq = uploadMap.get(part.ordinalNumber);
2997
3112
  if (!uploadReq) {
2998
3113
  throw new Error(`No upload request found for part ${part.ordinalNumber}`);
2999
3114
  }
@@ -3001,25 +3116,38 @@ var init_batch_session = __esm({
3001
3116
  for (const [k, v] of Object.entries(uploadReq.headers)) {
3002
3117
  if (v != null) headers[k] = v;
3003
3118
  }
3004
- await fetch(uploadReq.url, {
3119
+ const resp = await fetch(uploadReq.url, {
3005
3120
  method: uploadReq.method,
3006
3121
  headers,
3007
- body: part.data
3122
+ body: part.data,
3123
+ signal
3008
3124
  });
3125
+ if (!resp.ok) {
3126
+ const body = await resp.text().catch(() => "");
3127
+ throw new Error(
3128
+ `Upload failed for part ${part.ordinalNumber}: HTTP ${resp.status}${body ? ` \u2014 ${body}` : ""}`
3129
+ );
3130
+ }
3009
3131
  });
3010
- await Promise.all(tasks);
3132
+ if (parallelism !== void 0) {
3133
+ await runWithConcurrency(tasks, parallelism);
3134
+ } else {
3135
+ const ac = new AbortController();
3136
+ await Promise.all(tasks.map((t) => t(ac.signal).catch((err) => {
3137
+ ac.abort();
3138
+ throw err;
3139
+ })));
3140
+ }
3011
3141
  }
3012
3142
  /**
3013
- * Upload parts sequentially (not in parallel) because each part uses a
3014
- * streaming body (`duplex: 'half'`). Parallel streaming uploads can cause
3015
- * backpressure issues and exceed memory limits for large payloads.
3143
+ * Upload parts using streaming bodies (`duplex: 'half'`).
3144
+ * By default uploads sequentially to avoid backpressure issues.
3145
+ * Pass `parallelism` to enable bounded concurrent uploads.
3016
3146
  */
3017
- async sendPartsWithStream(openResponse, parts) {
3018
- const uploadRequests = openResponse.partUploadRequests;
3019
- for (const part of parts) {
3020
- const uploadReq = uploadRequests.find(
3021
- (r) => r.ordinalNumber === part.ordinalNumber
3022
- );
3147
+ async sendPartsWithStream(openResponse, parts, parallelism) {
3148
+ const uploadMap = new Map(openResponse.partUploadRequests.map((r) => [r.ordinalNumber, r]));
3149
+ const uploadPart = async (part, signal) => {
3150
+ const uploadReq = uploadMap.get(part.ordinalNumber);
3023
3151
  if (!uploadReq) {
3024
3152
  throw new Error(`No upload request found for part ${part.ordinalNumber}`);
3025
3153
  }
@@ -3031,11 +3159,23 @@ var init_batch_session = __esm({
3031
3159
  method: uploadReq.method,
3032
3160
  headers,
3033
3161
  body: part.dataStream,
3162
+ signal,
3034
3163
  // @ts-expect-error -- Node 18+ undici supports duplex for streaming body
3035
3164
  duplex: "half"
3036
3165
  });
3037
3166
  if (!resp.ok) {
3038
- throw new Error(`Upload failed for part ${part.ordinalNumber}: HTTP ${resp.status}`);
3167
+ const body = await resp.text().catch(() => "");
3168
+ throw new Error(
3169
+ `Upload failed for part ${part.ordinalNumber}: HTTP ${resp.status}${body ? ` \u2014 ${body}` : ""}`
3170
+ );
3171
+ }
3172
+ };
3173
+ if (parallelism !== void 0) {
3174
+ await runWithConcurrency(parts.map((p) => (signal) => uploadPart(p, signal)), parallelism);
3175
+ } else {
3176
+ const { signal } = new AbortController();
3177
+ for (const part of parts) {
3178
+ await uploadPart(part, signal);
3039
3179
  }
3040
3180
  }
3041
3181
  }
@@ -3148,7 +3288,10 @@ var init_invoice_download = __esm({
3148
3288
  async getInvoice(ksefNumber) {
3149
3289
  const req = RestRequest.get(Routes.Invoices.byKsefNumber(ksefNumber));
3150
3290
  const response = await this.restClient.executeRaw(req);
3151
- return new TextDecoder().decode(response.body);
3291
+ return {
3292
+ xml: new TextDecoder().decode(response.body),
3293
+ hash: response.headers.get("x-ms-meta-hash") ?? void 0
3294
+ };
3152
3295
  }
3153
3296
  async queryInvoiceMetadata(filters, pageOffset, pageSize, sortOrder) {
3154
3297
  const req = RestRequest.post(Routes.Invoices.queryMetadata).body(filters);
@@ -4410,6 +4553,72 @@ var init_zip = __esm({
4410
4553
  }
4411
4554
  });
4412
4555
 
4556
+ // src/offline/holidays.ts
4557
+ function toDateKey(d) {
4558
+ return d.toISOString().slice(0, 10);
4559
+ }
4560
+ function dateKey(year, month, day) {
4561
+ return toDateKey(new Date(Date.UTC(year, month - 1, day)));
4562
+ }
4563
+ function addDays(d, n) {
4564
+ const result = new Date(d);
4565
+ result.setUTCDate(result.getUTCDate() + n);
4566
+ return result;
4567
+ }
4568
+ function computeEasterSunday(year) {
4569
+ const a = year % 19;
4570
+ const b = Math.floor(year / 100);
4571
+ const c = year % 100;
4572
+ const d = Math.floor(b / 4);
4573
+ const e = b % 4;
4574
+ const f = Math.floor((b + 8) / 25);
4575
+ const g = Math.floor((b - f + 1) / 3);
4576
+ const h = (19 * a + b - d - g + 15) % 30;
4577
+ const i = Math.floor(c / 4);
4578
+ const k = c % 4;
4579
+ const l = (32 + 2 * e + 2 * i - h - k) % 7;
4580
+ const m = Math.floor((a + 11 * h + 22 * l) / 451);
4581
+ const month = Math.floor((h + l - 7 * m + 114) / 31);
4582
+ const day = (h + l - 7 * m + 114) % 31 + 1;
4583
+ return new Date(Date.UTC(year, month - 1, day));
4584
+ }
4585
+ function getPolishHolidays(year) {
4586
+ const cached = holidayCache.get(year);
4587
+ if (cached) return cached;
4588
+ const easter = computeEasterSunday(year);
4589
+ const holidays = /* @__PURE__ */ new Set([
4590
+ // Fixed
4591
+ dateKey(year, 1, 1),
4592
+ dateKey(year, 1, 6),
4593
+ dateKey(year, 5, 1),
4594
+ dateKey(year, 5, 3),
4595
+ dateKey(year, 8, 15),
4596
+ dateKey(year, 11, 1),
4597
+ dateKey(year, 11, 11),
4598
+ dateKey(year, 12, 25),
4599
+ dateKey(year, 12, 26),
4600
+ // Wigilia — added by Dz.U. 2024 poz. 1965, effective from 2025
4601
+ ...year >= 2025 ? [dateKey(year, 12, 24)] : [],
4602
+ // Moveable
4603
+ toDateKey(easter),
4604
+ toDateKey(addDays(easter, 1)),
4605
+ toDateKey(addDays(easter, 49)),
4606
+ toDateKey(addDays(easter, 60))
4607
+ ]);
4608
+ holidayCache.set(year, holidays);
4609
+ return holidays;
4610
+ }
4611
+ function isPolishHoliday(date) {
4612
+ return getPolishHolidays(date.getUTCFullYear()).has(toDateKey(date));
4613
+ }
4614
+ var holidayCache;
4615
+ var init_holidays = __esm({
4616
+ "src/offline/holidays.ts"() {
4617
+ "use strict";
4618
+ holidayCache = /* @__PURE__ */ new Map();
4619
+ }
4620
+ });
4621
+
4413
4622
  // src/offline/deadline.ts
4414
4623
  function getDefaultReason(mode) {
4415
4624
  switch (mode) {
@@ -4426,7 +4635,7 @@ function getDefaultReason(mode) {
4426
4635
  function nextBusinessDay(from) {
4427
4636
  const d = new Date(from);
4428
4637
  d.setUTCDate(d.getUTCDate() + 1);
4429
- while (d.getUTCDay() === 0 || d.getUTCDay() === 6) {
4638
+ while (d.getUTCDay() === 0 || d.getUTCDay() === 6 || isPolishHoliday(d)) {
4430
4639
  d.setUTCDate(d.getUTCDate() + 1);
4431
4640
  }
4432
4641
  return d;
@@ -4439,7 +4648,7 @@ function addBusinessDays(from, days) {
4439
4648
  let remaining = days;
4440
4649
  while (remaining > 0) {
4441
4650
  d.setUTCDate(d.getUTCDate() + 1);
4442
- if (d.getUTCDay() !== 0 && d.getUTCDay() !== 6) {
4651
+ if (d.getUTCDay() !== 0 && d.getUTCDay() !== 6 && !isPolishHoliday(d)) {
4443
4652
  remaining--;
4444
4653
  }
4445
4654
  }
@@ -4505,12 +4714,13 @@ var FAR_FUTURE;
4505
4714
  var init_deadline = __esm({
4506
4715
  "src/offline/deadline.ts"() {
4507
4716
  "use strict";
4717
+ init_holidays();
4508
4718
  FAR_FUTURE = /* @__PURE__ */ new Date("9999-12-31T23:59:59Z");
4509
4719
  }
4510
4720
  });
4511
4721
 
4512
4722
  // src/workflows/offline-invoice-workflow.ts
4513
- import crypto6 from "crypto";
4723
+ import crypto7 from "crypto";
4514
4724
  var OfflineInvoiceWorkflow;
4515
4725
  var init_offline_invoice_workflow = __esm({
4516
4726
  "src/workflows/offline-invoice-workflow.ts"() {
@@ -4534,7 +4744,7 @@ var init_offline_invoice_workflow = __esm({
4534
4744
  }
4535
4745
  const mode = options?.mode ?? "offline24";
4536
4746
  const reason = getDefaultReason(mode);
4537
- const invoiceHashBase64 = crypto6.createHash("sha256").update(input.invoiceXml).digest("base64");
4747
+ const invoiceHashBase64 = crypto7.createHash("sha256").update(input.invoiceXml).digest("base64");
4538
4748
  const kod1Url = this.qrService.buildInvoiceVerificationUrl(
4539
4749
  input.sellerNip,
4540
4750
  input.invoiceDate,
@@ -4553,7 +4763,7 @@ var init_offline_invoice_workflow = __esm({
4553
4763
  }
4554
4764
  const submitBy = options?.customDeadline ? typeof options.customDeadline === "string" ? options.customDeadline : options.customDeadline.toISOString() : calculateOfflineDeadline(mode, input.invoiceDate, options?.maintenanceWindow).toISOString();
4555
4765
  const metadata = {
4556
- id: crypto6.randomUUID(),
4766
+ id: crypto7.randomUUID(),
4557
4767
  mode,
4558
4768
  reason,
4559
4769
  status: "GENERATED",
@@ -4709,7 +4919,7 @@ var init_offline_invoice_workflow = __esm({
4709
4919
  original.status === "CORRECTED" ? `Invoice ${rejectedInvoiceId} has already been corrected (by ${original.correctedBy})` : `Only rejected invoices can be corrected (current status: ${original.status})`
4710
4920
  );
4711
4921
  }
4712
- const originalHash = crypto6.createHash("sha256").update(original.invoiceXml).digest("base64");
4922
+ const originalHash = crypto7.createHash("sha256").update(original.invoiceXml).digest("base64");
4713
4923
  await client.crypto.init();
4714
4924
  const encData = client.crypto.getEncryptionData();
4715
4925
  const formCode = options.formCode ?? DEFAULT_FORM_CODE;
@@ -4722,9 +4932,9 @@ var init_offline_invoice_workflow = __esm({
4722
4932
  const plainMeta = client.crypto.getFileMetadata(data);
4723
4933
  const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
4724
4934
  const encMeta = client.crypto.getFileMetadata(encrypted);
4725
- const correctionId = crypto6.randomUUID();
4935
+ const correctionId = crypto7.randomUUID();
4726
4936
  const submittedAt = (/* @__PURE__ */ new Date()).toISOString();
4727
- const correctedHashBase64 = crypto6.createHash("sha256").update(correctedInvoiceXml).digest("base64");
4937
+ const correctedHashBase64 = crypto7.createHash("sha256").update(correctedInvoiceXml).digest("base64");
4728
4938
  const kod1Url = this.qrService.buildInvoiceVerificationUrl(
4729
4939
  original.sellerNip,
4730
4940
  original.invoiceDate,
@@ -4879,6 +5089,7 @@ var init_client = __esm({
4879
5089
  qr;
4880
5090
  options;
4881
5091
  authManager;
5092
+ _baseRestClientConfig;
4882
5093
  _offline;
4883
5094
  constructor(options) {
4884
5095
  this.options = resolveOptions(options);
@@ -4890,6 +5101,8 @@ var init_client = __esm({
4890
5101
  });
4891
5102
  this.authManager = authManager;
4892
5103
  const restClientConfig = buildRestClientConfig(options, authManager);
5104
+ const { authManager: _am, ...baseConfig } = restClientConfig;
5105
+ this._baseRestClientConfig = baseConfig;
4893
5106
  const restClient = new RestClient(this.options, restClientConfig);
4894
5107
  const fetcher = new CertificateFetcher(restClient);
4895
5108
  this.crypto = new CryptographyService(fetcher);
@@ -4914,6 +5127,10 @@ var init_client = __esm({
4914
5127
  }
4915
5128
  return this._offline;
4916
5129
  }
5130
+ /** @internal Create a RestClient with a different AuthManager, preserving transport/retry/rateLimit config. */
5131
+ createScopedRestClient(authManager) {
5132
+ return new RestClient(this.options, { ...this._baseRestClientConfig, authManager });
5133
+ }
4917
5134
  async loginWithToken(token, nip) {
4918
5135
  const challenge = await this.auth.getChallenge();
4919
5136
  await this.crypto.init();
@@ -4928,6 +5145,7 @@ var init_client = __esm({
4928
5145
  const tokens = await this.auth.getAccessToken(authToken);
4929
5146
  this.authManager.setAccessToken(tokens.accessToken.token);
4930
5147
  this.authManager.setRefreshToken(tokens.refreshToken.token);
5148
+ return { clientIp: challenge.clientIp };
4931
5149
  }
4932
5150
  async loginWithCertificate(certPem, keyPem, nip, keyPassword) {
4933
5151
  const challenge = await this.auth.getChallenge();
@@ -4940,11 +5158,12 @@ var init_client = __esm({
4940
5158
  const tokens = await this.auth.getAccessToken(authToken);
4941
5159
  this.authManager.setAccessToken(tokens.accessToken.token);
4942
5160
  this.authManager.setRefreshToken(tokens.refreshToken.token);
5161
+ return { clientIp: challenge.clientIp };
4943
5162
  }
4944
5163
  async loginWithPkcs12(p12, password, nip) {
4945
5164
  const { Pkcs12Loader: Pkcs12Loader2 } = await Promise.resolve().then(() => (init_pkcs12_loader(), pkcs12_loader_exports));
4946
5165
  const { certificatePem, privateKeyPem } = Pkcs12Loader2.load(p12, password);
4947
- await this.loginWithCertificate(certificatePem, privateKeyPem, nip);
5166
+ return this.loginWithCertificate(certificatePem, privateKeyPem, nip);
4948
5167
  }
4949
5168
  async awaitAuthReady(referenceNumber, authToken) {
4950
5169
  for (let i = 0; i < 30; i++) {
@@ -5733,6 +5952,18 @@ function parseKSeFTokenContext(token) {
5733
5952
  };
5734
5953
  }
5735
5954
 
5955
+ // src/utils/hash.ts
5956
+ import crypto6 from "crypto";
5957
+ function sha256Base642(data) {
5958
+ return crypto6.createHash("sha256").update(data).digest("base64");
5959
+ }
5960
+ function verifyHash(data, expectedHash) {
5961
+ return sha256Base642(data) === expectedHash;
5962
+ }
5963
+
5964
+ // src/utils/index.ts
5965
+ init_concurrency();
5966
+
5736
5967
  // src/workflows/polling.ts
5737
5968
  async function pollUntil(action, condition, options) {
5738
5969
  const intervalMs = options?.intervalMs ?? 2e3;
@@ -5751,6 +5982,10 @@ async function pollUntil(action, condition, options) {
5751
5982
  }
5752
5983
 
5753
5984
  // src/workflows/online-session-workflow.ts
5985
+ init_ksef_session_expired_error();
5986
+ init_auth_manager();
5987
+ init_online_session();
5988
+ init_session_status();
5754
5989
  init_document_structures();
5755
5990
 
5756
5991
  // src/xml/upo-parser.ts
@@ -5914,18 +6149,11 @@ function nonEmptyString(value) {
5914
6149
  // src/workflows/online-session-workflow.ts
5915
6150
  init_invoice_validator();
5916
6151
  init_ksef_validation_error();
5917
- async function openOnlineSession(client, options) {
5918
- await client.crypto.init();
5919
- const encData = client.crypto.getEncryptionData();
5920
- const formCode = options?.formCode ?? DEFAULT_FORM_CODE;
5921
- const openResp = await client.onlineSession.openSession(
5922
- { formCode, encryption: encData.encryptionInfo },
5923
- options?.upoVersion
5924
- );
5925
- const sessionRef = openResp.referenceNumber;
6152
+ function buildSessionHandle(params) {
6153
+ const { deps, sessionRef, validUntil, cipherKey, cipherIv, formCode, validate: validate2 } = params;
5926
6154
  async function fetchUpo(pollOpts) {
5927
6155
  const result = await pollUntil(
5928
- () => client.sessionStatus.getSessionStatus(sessionRef),
6156
+ () => deps.sessionStatus.getSessionStatus(sessionRef),
5929
6157
  (s) => s.status.code === 200 || s.status.code >= 400,
5930
6158
  { ...pollOpts, description: `UPO for session ${sessionRef}` }
5931
6159
  );
@@ -5941,9 +6169,9 @@ async function openOnlineSession(client, options) {
5941
6169
  }
5942
6170
  return {
5943
6171
  sessionRef,
5944
- validUntil: openResp.validUntil,
6172
+ validUntil,
5945
6173
  async sendInvoice(invoiceXml) {
5946
- if (options?.validate) {
6174
+ if (validate2) {
5947
6175
  const xmlStr = typeof invoiceXml === "string" ? invoiceXml : new TextDecoder().decode(invoiceXml);
5948
6176
  const vResult = await validate(xmlStr);
5949
6177
  if (!vResult.valid) {
@@ -5954,10 +6182,10 @@ async function openOnlineSession(client, options) {
5954
6182
  }
5955
6183
  }
5956
6184
  const data = typeof invoiceXml === "string" ? new TextEncoder().encode(invoiceXml) : invoiceXml;
5957
- const plainMeta = client.crypto.getFileMetadata(data);
5958
- const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
5959
- const encMeta = client.crypto.getFileMetadata(encrypted);
5960
- const resp = await client.onlineSession.sendInvoice(sessionRef, {
6185
+ const plainMeta = deps.crypto.getFileMetadata(data);
6186
+ const encrypted = deps.crypto.encryptAES256(data, cipherKey, cipherIv);
6187
+ const encMeta = deps.crypto.getFileMetadata(encrypted);
6188
+ const resp = await deps.onlineSession.sendInvoice(sessionRef, {
5961
6189
  invoiceHash: plainMeta.hashSHA,
5962
6190
  invoiceSize: plainMeta.fileSize,
5963
6191
  encryptedInvoiceHash: encMeta.hashSHA,
@@ -5967,7 +6195,7 @@ async function openOnlineSession(client, options) {
5967
6195
  return resp.referenceNumber;
5968
6196
  },
5969
6197
  async close() {
5970
- await client.onlineSession.closeSession(sessionRef);
6198
+ await deps.onlineSession.closeSession(sessionRef);
5971
6199
  },
5972
6200
  async waitForUpo(pollOpts) {
5973
6201
  return fetchUpo(pollOpts);
@@ -5976,13 +6204,75 @@ async function openOnlineSession(client, options) {
5976
6204
  const upoInfo = await fetchUpo(pollOpts);
5977
6205
  const parsed = [];
5978
6206
  for (const page of upoInfo.pages) {
5979
- const result = await client.sessionStatus.getSessionUpo(sessionRef, page.referenceNumber);
6207
+ const result = await deps.sessionStatus.getSessionUpo(sessionRef, page.referenceNumber);
5980
6208
  parsed.push(parseUpoXml(result.upo));
5981
6209
  }
5982
6210
  return { ...upoInfo, parsed };
6211
+ },
6212
+ getState() {
6213
+ const token = deps.getAccessToken();
6214
+ if (!token) {
6215
+ throw new Error("Cannot serialize session state: no access token available");
6216
+ }
6217
+ return {
6218
+ referenceNumber: sessionRef,
6219
+ aesKey: Buffer.from(cipherKey).toString("base64"),
6220
+ iv: Buffer.from(cipherIv).toString("base64"),
6221
+ accessToken: token,
6222
+ formCode,
6223
+ validUntil,
6224
+ ...validate2 ? { validate: validate2 } : {}
6225
+ };
5983
6226
  }
5984
6227
  };
5985
6228
  }
6229
+ async function openOnlineSession(client, options) {
6230
+ await client.crypto.init();
6231
+ const encData = client.crypto.getEncryptionData();
6232
+ const formCode = options?.formCode ?? DEFAULT_FORM_CODE;
6233
+ const openResp = await client.onlineSession.openSession(
6234
+ { formCode, encryption: encData.encryptionInfo },
6235
+ options?.upoVersion
6236
+ );
6237
+ return buildSessionHandle({
6238
+ deps: {
6239
+ crypto: client.crypto,
6240
+ onlineSession: client.onlineSession,
6241
+ sessionStatus: client.sessionStatus,
6242
+ getAccessToken: () => client.authManager.getAccessToken()
6243
+ },
6244
+ sessionRef: openResp.referenceNumber,
6245
+ validUntil: openResp.validUntil,
6246
+ cipherKey: encData.cipherKey,
6247
+ cipherIv: encData.cipherIv,
6248
+ formCode,
6249
+ validate: options?.validate
6250
+ });
6251
+ }
6252
+ function resumeOnlineSession(client, state, options) {
6253
+ const expiry = new Date(state.validUntil);
6254
+ if (expiry.getTime() <= Date.now()) {
6255
+ throw new KSeFSessionExpiredError(
6256
+ `Cannot resume session: expired at ${state.validUntil}`
6257
+ );
6258
+ }
6259
+ const scopedAuth = new DefaultAuthManager(() => Promise.resolve(null), state.accessToken);
6260
+ const scopedRestClient = client.createScopedRestClient(scopedAuth);
6261
+ return buildSessionHandle({
6262
+ deps: {
6263
+ crypto: client.crypto,
6264
+ onlineSession: new OnlineSessionService(scopedRestClient),
6265
+ sessionStatus: new SessionStatusService(scopedRestClient),
6266
+ getAccessToken: () => scopedAuth.getAccessToken()
6267
+ },
6268
+ sessionRef: state.referenceNumber,
6269
+ validUntil: state.validUntil,
6270
+ cipherKey: new Uint8Array(Buffer.from(state.aesKey, "base64")),
6271
+ cipherIv: new Uint8Array(Buffer.from(state.iv, "base64")),
6272
+ formCode: state.formCode,
6273
+ validate: options?.validate ?? state.validate
6274
+ });
6275
+ }
5986
6276
  async function openSendAndClose(client, invoices, options) {
5987
6277
  const handle = await openOnlineSession(client, options);
5988
6278
  for (const inv of invoices) {
@@ -5995,6 +6285,9 @@ async function openSendAndClose(client, invoices, options) {
5995
6285
  // src/workflows/batch-session-workflow.ts
5996
6286
  init_document_structures();
5997
6287
  async function uploadBatch(client, zipData, options) {
6288
+ if (options?.parallelism !== void 0 && (!Number.isInteger(options.parallelism) || options.parallelism < 1)) {
6289
+ throw new Error("parallelism must be a positive integer");
6290
+ }
5998
6291
  await client.crypto.init();
5999
6292
  if (options?.validate) {
6000
6293
  const { unzip: unzip2 } = await Promise.resolve().then(() => (init_zip(), zip_exports));
@@ -6037,7 +6330,7 @@ async function uploadBatch(client, zipData, options) {
6037
6330
  },
6038
6331
  ordinalNumber: i + 1
6039
6332
  }));
6040
- await client.batchSession.sendParts(openResp, sendingParts);
6333
+ await client.batchSession.sendParts(openResp, sendingParts, options?.parallelism);
6041
6334
  await client.batchSession.closeSession(openResp.referenceNumber);
6042
6335
  const result = await pollUntil(
6043
6336
  () => client.sessionStatus.getSessionStatus(openResp.referenceNumber),
@@ -6058,6 +6351,9 @@ async function uploadBatch(client, zipData, options) {
6058
6351
  };
6059
6352
  }
6060
6353
  async function uploadBatchStream(client, zipStreamFactory, zipSize, options) {
6354
+ if (options?.parallelism !== void 0 && (!Number.isInteger(options.parallelism) || options.parallelism < 1)) {
6355
+ throw new Error("parallelism must be a positive integer");
6356
+ }
6061
6357
  await client.crypto.init();
6062
6358
  const encData = client.crypto.getEncryptionData();
6063
6359
  const formCode = options?.formCode ?? DEFAULT_FORM_CODE;
@@ -6079,7 +6375,7 @@ async function uploadBatchStream(client, zipStreamFactory, zipSize, options) {
6079
6375
  },
6080
6376
  options?.upoVersion
6081
6377
  );
6082
- await client.batchSession.sendPartsWithStream(openResp, streamParts);
6378
+ await client.batchSession.sendPartsWithStream(openResp, streamParts, options?.parallelism);
6083
6379
  await client.batchSession.closeSession(openResp.referenceNumber);
6084
6380
  const result = await pollUntil(
6085
6381
  () => client.sessionStatus.getSessionStatus(openResp.referenceNumber),
@@ -6154,6 +6450,7 @@ async function doExport(client, filters, options) {
6154
6450
  url: p.url,
6155
6451
  method: p.method,
6156
6452
  partSize: p.partSize,
6453
+ partHash: p.partHash,
6157
6454
  encryptedPartSize: p.encryptedPartSize,
6158
6455
  encryptedPartHash: p.encryptedPartHash,
6159
6456
  expirationDate: p.expirationDate
@@ -6179,6 +6476,9 @@ async function exportAndDownload(client, filters, options) {
6179
6476
  throw new Error(`Download failed for part ${part.ordinalNumber}: HTTP ${resp.status}`);
6180
6477
  }
6181
6478
  const encryptedData = new Uint8Array(await resp.arrayBuffer());
6479
+ if (options?.verifyHash !== false && !verifyHash(encryptedData, part.encryptedPartHash)) {
6480
+ throw new Error(`Hash mismatch for export part ${part.ordinalNumber}`);
6481
+ }
6182
6482
  const decrypted = client.crypto.decryptAES256(encryptedData, encData.cipherKey, encData.cipherIv);
6183
6483
  decryptedParts.push(decrypted);
6184
6484
  }
@@ -6251,6 +6551,9 @@ async function incrementalExportAndDownload(client, options) {
6251
6551
  throw new Error(`Download failed for part ${part.ordinalNumber}: HTTP ${resp.status}`);
6252
6552
  }
6253
6553
  const encryptedData = new Uint8Array(await resp.arrayBuffer());
6554
+ if (options.verifyHash !== false && !verifyHash(encryptedData, part.encryptedPartHash)) {
6555
+ throw new Error(`Hash mismatch for export part ${part.ordinalNumber}`);
6556
+ }
6254
6557
  const decrypted = client.crypto.decryptAES256(encryptedData, encData.cipherKey, encData.cipherIv);
6255
6558
  decryptedParts.push(decrypted);
6256
6559
  }
@@ -6416,6 +6719,7 @@ async function authenticateWithPkcs12(client, options) {
6416
6719
 
6417
6720
  // src/offline/index.ts
6418
6721
  init_deadline();
6722
+ init_holidays();
6419
6723
 
6420
6724
  // src/offline/storage.ts
6421
6725
  function matchesFilter(invoice, filter) {
@@ -6590,9 +6894,12 @@ export {
6590
6894
  KSEF_FEATURE_HEADER,
6591
6895
  KSeFApiError,
6592
6896
  KSeFAuthStatusError,
6897
+ KSeFBatchTimeoutError,
6593
6898
  KSeFClient,
6594
6899
  KSeFError,
6900
+ KSeFErrorCode,
6595
6901
  KSeFForbiddenError,
6902
+ KSeFGoneError,
6596
6903
  KSeFRateLimitError,
6597
6904
  KSeFSessionExpiredError,
6598
6905
  KSeFUnauthorizedError,
@@ -6657,9 +6964,11 @@ export {
6657
6964
  getDefaultReason,
6658
6965
  getEffectiveStartDate,
6659
6966
  getFormCode,
6967
+ getPolishHolidays,
6660
6968
  getTimeUntilDeadline,
6661
6969
  incrementalExportAndDownload,
6662
6970
  isExpired,
6971
+ isPolishHoliday,
6663
6972
  isRetryableError,
6664
6973
  isRetryableStatus,
6665
6974
  isValidBase64,
@@ -6686,6 +6995,9 @@ export {
6686
6995
  parseUpoXml,
6687
6996
  pollUntil,
6688
6997
  resolveOptions,
6998
+ resumeOnlineSession,
6999
+ runWithConcurrency,
7000
+ sha256Base642 as sha256Base64,
6689
7001
  sleep,
6690
7002
  unzip,
6691
7003
  updateContinuationPoint,
@@ -6700,6 +7012,7 @@ export {
6700
7012
  validatePresignedUrl,
6701
7013
  validateSchema,
6702
7014
  validateWellFormedness,
7015
+ verifyHash,
6703
7016
  xmlToObject
6704
7017
  };
6705
7018
  //# sourceMappingURL=index.js.map