@tamtamchik/app-store-receipt-parser 2.3.0 → 2.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.3.1 - 2026-06-12
4
+
5
+ ### Fixed
6
+
7
+ - Decoded ASN.1 `INTEGER` field values to decimal strings instead of raw hex.
8
+ - Preserved `Environment` literal type hints and exported the `Environment` type from the package entrypoint.
9
+ - Shipped generated type declarations instead of hand-written ones.
10
+ - Allowed parsing receipts without in-app purchases.
11
+
12
+ ### Changed
13
+
14
+ - Documented last-wins semantics of top-level `IN_APP_*` fields.
15
+ - Added a warning that receipt signatures are not verified.
16
+ - Updated the README example to decoded integer values.
17
+
18
+ ### Maintenance
19
+
20
+ - Replaced `tsup` with `tsdown` for the build.
21
+ - Simplified the Dependabot configuration.
22
+ - Updated development dependencies.
23
+
3
24
  ## 2.3.0 - 2026-05-27
4
25
 
5
26
  ### Added
package/README.md CHANGED
@@ -14,6 +14,12 @@ A lightweight TypeScript library for extracting selected fields from Apple's ASN
14
14
  > It extracts supported fields from Apple's ASN.1 encoded Unified Receipts, including in-app purchase receipts.
15
15
  > It does not work with the old-style transaction receipts.
16
16
 
17
+ > [!CAUTION]
18
+ > This library does **not** verify the receipt's PKCS#7 signature — it only checks that the ASN.1
19
+ > structure has the expected shape. The extracted data is not cryptographically trustworthy on its own.
20
+ > Do not grant entitlements based on it without verifying the receipt signature (or using Apple's
21
+ > [App Store Server API](https://developer.apple.com/documentation/appstoreserverapi)) separately.
22
+
17
23
  > [!NOTE]
18
24
  > Documentation for the version 1.x of the library can be found [here](https://github.com/tamtamchik/app-store-receipt-parser/tree/1.x/README.md).
19
25
 
@@ -55,8 +61,8 @@ console.log(data);
55
61
  // ORIGINAL_PURCHASE_DATE: '2013-08-01T07:00:00Z',
56
62
  // IN_APP_EXPIRES_DATE: '2015-08-10T07:19:32Z',
57
63
  // IN_APP_CANCELLATION_DATE: '',
58
- // IN_APP_QUANTITY: '020101',
59
- // IN_APP_WEB_ORDER_LINE_ITEM_ID: '0207038d7ea69472c9',
64
+ // IN_APP_QUANTITY: '1',
65
+ // IN_APP_WEB_ORDER_LINE_ITEM_ID: '1000000030274249',
60
66
  // IN_APP_PRODUCT_ID: 'monthly',
61
67
  // IN_APP_TRANSACTION_ID: '1000000166967782',
62
68
  // IN_APP_TRANSACTION_IDS: [
@@ -73,8 +79,8 @@ console.log(data);
73
79
  // {
74
80
  // IN_APP_EXPIRES_DATE: '2015-08-10T07:19:32Z',
75
81
  // IN_APP_CANCELLATION_DATE: '',
76
- // IN_APP_QUANTITY: '020101',
77
- // IN_APP_WEB_ORDER_LINE_ITEM_ID: '0207038d7ea69472c9',
82
+ // IN_APP_QUANTITY: '1',
83
+ // IN_APP_WEB_ORDER_LINE_ITEM_ID: '1000000030274249',
78
84
  // IN_APP_PRODUCT_ID: 'monthly',
79
85
  // IN_APP_TRANSACTION_ID: '1000000166967782',
80
86
  // IN_APP_ORIGINAL_TRANSACTION_ID: '1000000166965150',
@@ -92,6 +98,12 @@ console.log(data);
92
98
  // }
93
99
  ```
94
100
 
101
+ > [!WARNING]
102
+ > Top-level scalar `IN_APP_*` fields (e.g. `IN_APP_PRODUCT_ID`, `IN_APP_TRANSACTION_ID`) hold the value
103
+ > from the **last** in-app purchase block encountered in the receipt, and Apple does not guarantee the
104
+ > order of those blocks. They are kept for backward compatibility — for reliable per-purchase data use
105
+ > `IN_APP_RECEIPTS`.
106
+
95
107
  ## Special Thanks
96
108
 
97
109
  - [@Jurajzovinec](https://github.com/Jurajzovinec) for his superb contribution to the project.
@@ -0,0 +1,15 @@
1
+ //#region src/constants.d.ts
2
+ type ReceiptFieldsKeyNames = 'ENVIRONMENT' | 'BUNDLE_ID' | 'APP_VERSION' | 'OPAQUE_VALUE' | 'SHA1_HASH' | 'RECEIPT_CREATION_DATE' | 'ORIGINAL_PURCHASE_DATE' | 'ORIGINAL_APP_VERSION' | 'IN_APP_QUANTITY' | 'IN_APP_PRODUCT_ID' | 'IN_APP_TRANSACTION_ID' | 'IN_APP_PURCHASE_DATE' | 'IN_APP_ORIGINAL_TRANSACTION_ID' | 'IN_APP_ORIGINAL_PURCHASE_DATE' | 'IN_APP_EXPIRES_DATE' | 'IN_APP_WEB_ORDER_LINE_ITEM_ID' | 'IN_APP_CANCELLATION_DATE';
3
+ //#endregion
4
+ //#region src/ReceiptParser.d.ts
5
+ type Environment = 'Production' | 'ProductionSandbox' | (string & {});
6
+ type InAppReceipt = Partial<Record<ReceiptFieldsKeyNames, string>>;
7
+ type ParsedReceipt = Partial<Record<ReceiptFieldsKeyNames, string>> & {
8
+ ENVIRONMENT: Environment;
9
+ IN_APP_RECEIPTS: InAppReceipt[];
10
+ IN_APP_ORIGINAL_TRANSACTION_IDS: string[];
11
+ IN_APP_TRANSACTION_IDS: string[];
12
+ };
13
+ declare function parseReceipt(receipt: string): ParsedReceipt;
14
+ //#endregion
15
+ export { type Environment, type InAppReceipt, type ParsedReceipt, parseReceipt };
@@ -0,0 +1,15 @@
1
+ //#region src/constants.d.ts
2
+ type ReceiptFieldsKeyNames = 'ENVIRONMENT' | 'BUNDLE_ID' | 'APP_VERSION' | 'OPAQUE_VALUE' | 'SHA1_HASH' | 'RECEIPT_CREATION_DATE' | 'ORIGINAL_PURCHASE_DATE' | 'ORIGINAL_APP_VERSION' | 'IN_APP_QUANTITY' | 'IN_APP_PRODUCT_ID' | 'IN_APP_TRANSACTION_ID' | 'IN_APP_PURCHASE_DATE' | 'IN_APP_ORIGINAL_TRANSACTION_ID' | 'IN_APP_ORIGINAL_PURCHASE_DATE' | 'IN_APP_EXPIRES_DATE' | 'IN_APP_WEB_ORDER_LINE_ITEM_ID' | 'IN_APP_CANCELLATION_DATE';
3
+ //#endregion
4
+ //#region src/ReceiptParser.d.ts
5
+ type Environment = 'Production' | 'ProductionSandbox' | (string & {});
6
+ type InAppReceipt = Partial<Record<ReceiptFieldsKeyNames, string>>;
7
+ type ParsedReceipt = Partial<Record<ReceiptFieldsKeyNames, string>> & {
8
+ ENVIRONMENT: Environment;
9
+ IN_APP_RECEIPTS: InAppReceipt[];
10
+ IN_APP_ORIGINAL_TRANSACTION_IDS: string[];
11
+ IN_APP_TRANSACTION_IDS: string[];
12
+ };
13
+ declare function parseReceipt(receipt: string): ParsedReceipt;
14
+ //#endregion
15
+ export { type Environment, type InAppReceipt, type ParsedReceipt, parseReceipt };
package/dist/index.js CHANGED
@@ -1,237 +1,188 @@
1
- "use strict";
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region \0rolldown/runtime.js
2
3
  var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
7
  var __getProtoOf = Object.getPrototypeOf;
7
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
9
  var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
19
18
  };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- parseReceipt: () => parseReceipt
34
- });
35
- module.exports = __toCommonJS(index_exports);
36
-
37
- // src/ReceiptParser.ts
38
- var ASN1 = __toESM(require("asn1js"));
39
-
40
- // src/constants.ts
41
- var IN_APP = 17;
42
- var CONTENT_ID = "pkcs7_content";
43
- var FIELD_TYPE_ID = "FieldType";
44
- var FIELD_VALUE_ID = "FieldTypeOctetString";
45
- var RECEIPT_FIELDS_MAP = /* @__PURE__ */ new Map([
46
- [0, "ENVIRONMENT"],
47
- [2, "BUNDLE_ID"],
48
- [3, "APP_VERSION"],
49
- [4, "OPAQUE_VALUE"],
50
- [5, "SHA1_HASH"],
51
- [12, "RECEIPT_CREATION_DATE"],
52
- [18, "ORIGINAL_PURCHASE_DATE"],
53
- [19, "ORIGINAL_APP_VERSION"],
54
- [1701, "IN_APP_QUANTITY"],
55
- [1702, "IN_APP_PRODUCT_ID"],
56
- [1703, "IN_APP_TRANSACTION_ID"],
57
- [1704, "IN_APP_PURCHASE_DATE"],
58
- [1705, "IN_APP_ORIGINAL_TRANSACTION_ID"],
59
- [1706, "IN_APP_ORIGINAL_PURCHASE_DATE"],
60
- [1708, "IN_APP_EXPIRES_DATE"],
61
- [1711, "IN_APP_WEB_ORDER_LINE_ITEM_ID"],
62
- [1712, "IN_APP_CANCELLATION_DATE"]
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ //#endregion
24
+ let asn1js = require("asn1js");
25
+ asn1js = __toESM(asn1js);
26
+ //#region src/constants.ts
27
+ /** Identifies pkcs7 content information encoded as Octet string */
28
+ const CONTENT_ID = "pkcs7_content";
29
+ /** Identifies field type id information */
30
+ const FIELD_TYPE_ID = "FieldType";
31
+ /** Identifies field value information encoded as Octet string */
32
+ const FIELD_VALUE_ID = "FieldTypeOctetString";
33
+ /**
34
+ * Receipt fields
35
+ * @see https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html
36
+ */
37
+ const RECEIPT_FIELDS_MAP = new Map([
38
+ [0, "ENVIRONMENT"],
39
+ [2, "BUNDLE_ID"],
40
+ [3, "APP_VERSION"],
41
+ [4, "OPAQUE_VALUE"],
42
+ [5, "SHA1_HASH"],
43
+ [12, "RECEIPT_CREATION_DATE"],
44
+ [18, "ORIGINAL_PURCHASE_DATE"],
45
+ [19, "ORIGINAL_APP_VERSION"],
46
+ [1701, "IN_APP_QUANTITY"],
47
+ [1702, "IN_APP_PRODUCT_ID"],
48
+ [1703, "IN_APP_TRANSACTION_ID"],
49
+ [1704, "IN_APP_PURCHASE_DATE"],
50
+ [1705, "IN_APP_ORIGINAL_TRANSACTION_ID"],
51
+ [1706, "IN_APP_ORIGINAL_PURCHASE_DATE"],
52
+ [1708, "IN_APP_EXPIRES_DATE"],
53
+ [1711, "IN_APP_WEB_ORDER_LINE_ITEM_ID"],
54
+ [1712, "IN_APP_CANCELLATION_DATE"]
63
55
  ]);
64
-
65
- // src/ReceiptVerifier.ts
66
- var import_asn1js = require("asn1js");
56
+ //#endregion
57
+ //#region src/ReceiptVerifier.ts
67
58
  var ReceiptVerifier = class {
68
- receiptSchema;
69
- fieldSchema;
70
- constructor() {
71
- this.receiptSchema = new import_asn1js.Sequence({
72
- value: [
73
- new import_asn1js.ObjectIdentifier(),
74
- new import_asn1js.Constructed({
75
- idBlock: { tagClass: 3, tagNumber: 0 },
76
- value: [
77
- new import_asn1js.Sequence({
78
- value: [
79
- new import_asn1js.Integer(),
80
- new import_asn1js.Set({
81
- value: [
82
- new import_asn1js.Sequence({
83
- value: [new import_asn1js.ObjectIdentifier(), new import_asn1js.Any()]
84
- })
85
- ]
86
- }),
87
- new import_asn1js.Sequence({
88
- value: [
89
- new import_asn1js.ObjectIdentifier(),
90
- new import_asn1js.Constructed({
91
- idBlock: { tagClass: 3, tagNumber: 0 },
92
- value: [new import_asn1js.OctetString({ name: CONTENT_ID })]
93
- })
94
- ]
95
- })
96
- ]
97
- })
98
- ]
99
- })
100
- ]
101
- });
102
- this.fieldSchema = new import_asn1js.Sequence({
103
- value: [
104
- new import_asn1js.Integer({ name: FIELD_TYPE_ID }),
105
- new import_asn1js.Integer(),
106
- new import_asn1js.OctetString({ name: FIELD_VALUE_ID })
107
- ]
108
- });
109
- }
110
- verifyReceiptSchema(receipt) {
111
- const receiptVerification = (0, import_asn1js.verifySchema)(Buffer.from(receipt, "base64"), this.receiptSchema);
112
- if (!receiptVerification.verified) {
113
- throw new Error("Receipt verification failed.");
114
- }
115
- return receiptVerification;
116
- }
117
- verifyFieldSchema(sequence) {
118
- const fieldVerification = (0, import_asn1js.verifySchema)(sequence.toBER(), this.fieldSchema);
119
- if (!fieldVerification.verified) {
120
- return null;
121
- }
122
- return fieldVerification;
123
- }
59
+ receiptSchema;
60
+ fieldSchema;
61
+ constructor() {
62
+ this.receiptSchema = new asn1js.Sequence({ value: [new asn1js.ObjectIdentifier(), new asn1js.Constructed({
63
+ idBlock: {
64
+ tagClass: 3,
65
+ tagNumber: 0
66
+ },
67
+ value: [new asn1js.Sequence({ value: [
68
+ new asn1js.Integer(),
69
+ new asn1js.Set({ value: [new asn1js.Sequence({ value: [new asn1js.ObjectIdentifier(), new asn1js.Any()] })] }),
70
+ new asn1js.Sequence({ value: [new asn1js.ObjectIdentifier(), new asn1js.Constructed({
71
+ idBlock: {
72
+ tagClass: 3,
73
+ tagNumber: 0
74
+ },
75
+ value: [new asn1js.OctetString({ name: CONTENT_ID })]
76
+ })] })
77
+ ] })]
78
+ })] });
79
+ this.fieldSchema = new asn1js.Sequence({ value: [
80
+ new asn1js.Integer({ name: FIELD_TYPE_ID }),
81
+ new asn1js.Integer(),
82
+ new asn1js.OctetString({ name: FIELD_VALUE_ID })
83
+ ] });
84
+ }
85
+ verifyReceiptSchema(receipt) {
86
+ const receiptVerification = (0, asn1js.verifySchema)(Buffer.from(receipt, "base64"), this.receiptSchema);
87
+ if (!receiptVerification.verified) throw new Error("Receipt verification failed.");
88
+ return receiptVerification;
89
+ }
90
+ verifyFieldSchema(sequence) {
91
+ const fieldVerification = (0, asn1js.verifySchema)(sequence.toBER(), this.fieldSchema);
92
+ if (!fieldVerification.verified) return null;
93
+ return fieldVerification;
94
+ }
124
95
  };
125
-
126
- // src/ReceiptParser.ts
96
+ //#endregion
97
+ //#region src/ReceiptParser.ts
127
98
  var ReceiptParser = class {
128
- parsed;
129
- receiptVerifier;
130
- constructor() {
131
- this.receiptVerifier = new ReceiptVerifier();
132
- this.parsed = this.createInitialParsedReceipt();
133
- }
134
- parseReceipt(receipt) {
135
- if (receipt.trim() === "") {
136
- throw new Error("Receipt must be a non-empty string.");
137
- }
138
- const rootSchemaVerification = this.receiptVerifier.verifyReceiptSchema(receipt);
139
- const content = rootSchemaVerification.result[CONTENT_ID];
140
- this.parseReceiptContent(content);
141
- this.validateParsedFields();
142
- this.deduplicateArrayFields();
143
- return this.parsed;
144
- }
145
- createInitialParsedReceipt() {
146
- return {
147
- ENVIRONMENT: "Production",
148
- IN_APP_RECEIPTS: [],
149
- IN_APP_ORIGINAL_TRANSACTION_IDS: [],
150
- IN_APP_TRANSACTION_IDS: []
151
- };
152
- }
153
- parseReceiptContent(content, inAppReceipt) {
154
- const sequences = this.extractSequencesFromContent(content);
155
- sequences.forEach((sequence) => this.processSequence(sequence, inAppReceipt));
156
- }
157
- extractSequencesFromContent(content) {
158
- const [contentSet] = content.valueBlock.value;
159
- return contentSet.valueBlock.value.filter((v) => v instanceof ASN1.Sequence);
160
- }
161
- processSequence(sequence, inAppReceipt) {
162
- const verifiedSequence = this.receiptVerifier.verifyFieldSchema(sequence);
163
- if (verifiedSequence) {
164
- this.handleVerifiedSequence(verifiedSequence, inAppReceipt);
165
- }
166
- }
167
- handleVerifiedSequence(verifiedSequence, inAppReceipt) {
168
- const fieldKey = verifiedSequence.result[FIELD_TYPE_ID].valueBlock.valueDec;
169
- const fieldValue = verifiedSequence.result[FIELD_VALUE_ID];
170
- const handler = this.getFieldHandler(fieldKey, inAppReceipt);
171
- handler(fieldValue);
172
- }
173
- getFieldHandler(fieldKey, inAppReceipt) {
174
- if (fieldKey === IN_APP) {
175
- return (fieldValue) => {
176
- const parsedInAppReceipt = {};
177
- this.parseReceiptContent(fieldValue, parsedInAppReceipt);
178
- this.parsed.IN_APP_RECEIPTS.push(parsedInAppReceipt);
179
- };
180
- }
181
- if (this.isValidReceiptFieldKey(fieldKey)) {
182
- const name = RECEIPT_FIELDS_MAP.get(fieldKey);
183
- return (fieldValue) => {
184
- this.addFieldToReceipt(name, this.extractStringValue(fieldValue), inAppReceipt);
185
- };
186
- }
187
- return () => {
188
- };
189
- }
190
- isValidReceiptFieldKey(value) {
191
- return typeof value === "number" && RECEIPT_FIELDS_MAP.has(value);
192
- }
193
- extractStringValue(field) {
194
- const [fieldValue] = field.valueBlock.value;
195
- if (fieldValue instanceof ASN1.IA5String || fieldValue instanceof ASN1.Utf8String) {
196
- return fieldValue.valueBlock.value;
197
- }
198
- return field.toJSON().valueBlock.valueHex;
199
- }
200
- addFieldToReceipt(name, value, inAppReceipt) {
201
- this.addToArrayFieldIfApplicable(name, value);
202
- this.parsed[name] = value;
203
- if (inAppReceipt) {
204
- inAppReceipt[name] = value;
205
- }
206
- }
207
- addToArrayFieldIfApplicable(name, value) {
208
- const arrayFields = {
209
- "IN_APP_ORIGINAL_TRANSACTION_ID": "IN_APP_ORIGINAL_TRANSACTION_IDS",
210
- "IN_APP_TRANSACTION_ID": "IN_APP_TRANSACTION_IDS"
211
- };
212
- const arrayFieldName = arrayFields[name];
213
- if (arrayFieldName) {
214
- this.parsed[arrayFieldName].push(value);
215
- }
216
- }
217
- validateParsedFields() {
218
- const missingFields = Array.from(RECEIPT_FIELDS_MAP.values()).filter((fieldKey) => !(fieldKey in this.parsed));
219
- if (missingFields.length > 0) {
220
- throw new Error(`Missing required fields: ${missingFields.join(", ")}`);
221
- }
222
- }
223
- deduplicateArrayFields() {
224
- this.parsed.IN_APP_ORIGINAL_TRANSACTION_IDS = this.removeDuplicates(this.parsed.IN_APP_ORIGINAL_TRANSACTION_IDS);
225
- this.parsed.IN_APP_TRANSACTION_IDS = this.removeDuplicates(this.parsed.IN_APP_TRANSACTION_IDS);
226
- }
227
- removeDuplicates(array) {
228
- return [...new Set(array)];
229
- }
99
+ parsed;
100
+ receiptVerifier;
101
+ constructor() {
102
+ this.receiptVerifier = new ReceiptVerifier();
103
+ this.parsed = this.createInitialParsedReceipt();
104
+ }
105
+ parseReceipt(receipt) {
106
+ if (receipt.trim() === "") throw new Error("Receipt must be a non-empty string.");
107
+ const content = this.receiptVerifier.verifyReceiptSchema(receipt).result[CONTENT_ID];
108
+ this.parseReceiptContent(content);
109
+ this.validateParsedFields();
110
+ this.deduplicateArrayFields();
111
+ return this.parsed;
112
+ }
113
+ createInitialParsedReceipt() {
114
+ return {
115
+ ENVIRONMENT: "Production",
116
+ IN_APP_RECEIPTS: [],
117
+ IN_APP_ORIGINAL_TRANSACTION_IDS: [],
118
+ IN_APP_TRANSACTION_IDS: []
119
+ };
120
+ }
121
+ parseReceiptContent(content, inAppReceipt) {
122
+ this.extractSequencesFromContent(content).forEach((sequence) => this.processSequence(sequence, inAppReceipt));
123
+ }
124
+ extractSequencesFromContent(content) {
125
+ const [contentSet] = content.valueBlock.value;
126
+ return contentSet.valueBlock.value.filter((v) => v instanceof asn1js.Sequence);
127
+ }
128
+ processSequence(sequence, inAppReceipt) {
129
+ const verifiedSequence = this.receiptVerifier.verifyFieldSchema(sequence);
130
+ if (verifiedSequence) this.handleVerifiedSequence(verifiedSequence, inAppReceipt);
131
+ }
132
+ handleVerifiedSequence(verifiedSequence, inAppReceipt) {
133
+ const fieldKey = verifiedSequence.result[FIELD_TYPE_ID].valueBlock.valueDec;
134
+ const fieldValue = verifiedSequence.result[FIELD_VALUE_ID];
135
+ this.getFieldHandler(fieldKey, inAppReceipt)(fieldValue);
136
+ }
137
+ getFieldHandler(fieldKey, inAppReceipt) {
138
+ if (fieldKey === 17) return (fieldValue) => {
139
+ const parsedInAppReceipt = {};
140
+ this.parseReceiptContent(fieldValue, parsedInAppReceipt);
141
+ this.parsed.IN_APP_RECEIPTS.push(parsedInAppReceipt);
142
+ };
143
+ if (this.isValidReceiptFieldKey(fieldKey)) {
144
+ const name = RECEIPT_FIELDS_MAP.get(fieldKey);
145
+ return (fieldValue) => {
146
+ this.addFieldToReceipt(name, this.extractStringValue(fieldValue), inAppReceipt);
147
+ };
148
+ }
149
+ return () => {};
150
+ }
151
+ isValidReceiptFieldKey(value) {
152
+ return typeof value === "number" && RECEIPT_FIELDS_MAP.has(value);
153
+ }
154
+ extractStringValue(field) {
155
+ const [fieldValue] = field.valueBlock.value;
156
+ if (fieldValue instanceof asn1js.IA5String || fieldValue instanceof asn1js.Utf8String) return fieldValue.valueBlock.value;
157
+ if (fieldValue instanceof asn1js.Integer) return fieldValue.toBigInt().toString();
158
+ return field.toJSON().valueBlock.valueHex;
159
+ }
160
+ addFieldToReceipt(name, value, inAppReceipt) {
161
+ this.addToArrayFieldIfApplicable(name, value);
162
+ this.parsed[name] = value;
163
+ if (inAppReceipt) inAppReceipt[name] = value;
164
+ }
165
+ addToArrayFieldIfApplicable(name, value) {
166
+ const arrayFieldName = {
167
+ "IN_APP_ORIGINAL_TRANSACTION_ID": "IN_APP_ORIGINAL_TRANSACTION_IDS",
168
+ "IN_APP_TRANSACTION_ID": "IN_APP_TRANSACTION_IDS"
169
+ }[name];
170
+ if (arrayFieldName) this.parsed[arrayFieldName].push(value);
171
+ }
172
+ validateParsedFields() {
173
+ const missingFields = Array.from(RECEIPT_FIELDS_MAP.values()).filter((fieldKey) => !fieldKey.startsWith("IN_APP_")).filter((fieldKey) => !(fieldKey in this.parsed));
174
+ if (missingFields.length > 0) throw new Error(`Missing required fields: ${missingFields.join(", ")}`);
175
+ }
176
+ deduplicateArrayFields() {
177
+ this.parsed.IN_APP_ORIGINAL_TRANSACTION_IDS = this.removeDuplicates(this.parsed.IN_APP_ORIGINAL_TRANSACTION_IDS);
178
+ this.parsed.IN_APP_TRANSACTION_IDS = this.removeDuplicates(this.parsed.IN_APP_TRANSACTION_IDS);
179
+ }
180
+ removeDuplicates(array) {
181
+ return [...new Set(array)];
182
+ }
230
183
  };
231
184
  function parseReceipt(receipt) {
232
- return new ReceiptParser().parseReceipt(receipt);
185
+ return new ReceiptParser().parseReceipt(receipt);
233
186
  }
234
- // Annotate the CommonJS export names for ESM import in node:
235
- 0 && (module.exports = {
236
- parseReceipt
237
- });
187
+ //#endregion
188
+ exports.parseReceipt = parseReceipt;
package/dist/index.mjs CHANGED
@@ -1,200 +1,165 @@
1
- // src/ReceiptParser.ts
2
1
  import * as ASN1 from "asn1js";
3
-
4
- // src/constants.ts
5
- var IN_APP = 17;
6
- var CONTENT_ID = "pkcs7_content";
7
- var FIELD_TYPE_ID = "FieldType";
8
- var FIELD_VALUE_ID = "FieldTypeOctetString";
9
- var RECEIPT_FIELDS_MAP = /* @__PURE__ */ new Map([
10
- [0, "ENVIRONMENT"],
11
- [2, "BUNDLE_ID"],
12
- [3, "APP_VERSION"],
13
- [4, "OPAQUE_VALUE"],
14
- [5, "SHA1_HASH"],
15
- [12, "RECEIPT_CREATION_DATE"],
16
- [18, "ORIGINAL_PURCHASE_DATE"],
17
- [19, "ORIGINAL_APP_VERSION"],
18
- [1701, "IN_APP_QUANTITY"],
19
- [1702, "IN_APP_PRODUCT_ID"],
20
- [1703, "IN_APP_TRANSACTION_ID"],
21
- [1704, "IN_APP_PURCHASE_DATE"],
22
- [1705, "IN_APP_ORIGINAL_TRANSACTION_ID"],
23
- [1706, "IN_APP_ORIGINAL_PURCHASE_DATE"],
24
- [1708, "IN_APP_EXPIRES_DATE"],
25
- [1711, "IN_APP_WEB_ORDER_LINE_ITEM_ID"],
26
- [1712, "IN_APP_CANCELLATION_DATE"]
2
+ import { Any, Constructed, Integer, ObjectIdentifier, OctetString, Sequence, Set as Set$1, verifySchema } from "asn1js";
3
+ //#region src/constants.ts
4
+ /** Identifies pkcs7 content information encoded as Octet string */
5
+ const CONTENT_ID = "pkcs7_content";
6
+ /** Identifies field type id information */
7
+ const FIELD_TYPE_ID = "FieldType";
8
+ /** Identifies field value information encoded as Octet string */
9
+ const FIELD_VALUE_ID = "FieldTypeOctetString";
10
+ /**
11
+ * Receipt fields
12
+ * @see https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ReceiptFields.html
13
+ */
14
+ const RECEIPT_FIELDS_MAP = new Map([
15
+ [0, "ENVIRONMENT"],
16
+ [2, "BUNDLE_ID"],
17
+ [3, "APP_VERSION"],
18
+ [4, "OPAQUE_VALUE"],
19
+ [5, "SHA1_HASH"],
20
+ [12, "RECEIPT_CREATION_DATE"],
21
+ [18, "ORIGINAL_PURCHASE_DATE"],
22
+ [19, "ORIGINAL_APP_VERSION"],
23
+ [1701, "IN_APP_QUANTITY"],
24
+ [1702, "IN_APP_PRODUCT_ID"],
25
+ [1703, "IN_APP_TRANSACTION_ID"],
26
+ [1704, "IN_APP_PURCHASE_DATE"],
27
+ [1705, "IN_APP_ORIGINAL_TRANSACTION_ID"],
28
+ [1706, "IN_APP_ORIGINAL_PURCHASE_DATE"],
29
+ [1708, "IN_APP_EXPIRES_DATE"],
30
+ [1711, "IN_APP_WEB_ORDER_LINE_ITEM_ID"],
31
+ [1712, "IN_APP_CANCELLATION_DATE"]
27
32
  ]);
28
-
29
- // src/ReceiptVerifier.ts
30
- import { Any, Constructed, Integer, ObjectIdentifier, OctetString, Sequence, Set as Set2, verifySchema } from "asn1js";
33
+ //#endregion
34
+ //#region src/ReceiptVerifier.ts
31
35
  var ReceiptVerifier = class {
32
- receiptSchema;
33
- fieldSchema;
34
- constructor() {
35
- this.receiptSchema = new Sequence({
36
- value: [
37
- new ObjectIdentifier(),
38
- new Constructed({
39
- idBlock: { tagClass: 3, tagNumber: 0 },
40
- value: [
41
- new Sequence({
42
- value: [
43
- new Integer(),
44
- new Set2({
45
- value: [
46
- new Sequence({
47
- value: [new ObjectIdentifier(), new Any()]
48
- })
49
- ]
50
- }),
51
- new Sequence({
52
- value: [
53
- new ObjectIdentifier(),
54
- new Constructed({
55
- idBlock: { tagClass: 3, tagNumber: 0 },
56
- value: [new OctetString({ name: CONTENT_ID })]
57
- })
58
- ]
59
- })
60
- ]
61
- })
62
- ]
63
- })
64
- ]
65
- });
66
- this.fieldSchema = new Sequence({
67
- value: [
68
- new Integer({ name: FIELD_TYPE_ID }),
69
- new Integer(),
70
- new OctetString({ name: FIELD_VALUE_ID })
71
- ]
72
- });
73
- }
74
- verifyReceiptSchema(receipt) {
75
- const receiptVerification = verifySchema(Buffer.from(receipt, "base64"), this.receiptSchema);
76
- if (!receiptVerification.verified) {
77
- throw new Error("Receipt verification failed.");
78
- }
79
- return receiptVerification;
80
- }
81
- verifyFieldSchema(sequence) {
82
- const fieldVerification = verifySchema(sequence.toBER(), this.fieldSchema);
83
- if (!fieldVerification.verified) {
84
- return null;
85
- }
86
- return fieldVerification;
87
- }
36
+ receiptSchema;
37
+ fieldSchema;
38
+ constructor() {
39
+ this.receiptSchema = new Sequence({ value: [new ObjectIdentifier(), new Constructed({
40
+ idBlock: {
41
+ tagClass: 3,
42
+ tagNumber: 0
43
+ },
44
+ value: [new Sequence({ value: [
45
+ new Integer(),
46
+ new Set$1({ value: [new Sequence({ value: [new ObjectIdentifier(), new Any()] })] }),
47
+ new Sequence({ value: [new ObjectIdentifier(), new Constructed({
48
+ idBlock: {
49
+ tagClass: 3,
50
+ tagNumber: 0
51
+ },
52
+ value: [new OctetString({ name: CONTENT_ID })]
53
+ })] })
54
+ ] })]
55
+ })] });
56
+ this.fieldSchema = new Sequence({ value: [
57
+ new Integer({ name: FIELD_TYPE_ID }),
58
+ new Integer(),
59
+ new OctetString({ name: FIELD_VALUE_ID })
60
+ ] });
61
+ }
62
+ verifyReceiptSchema(receipt) {
63
+ const receiptVerification = verifySchema(Buffer.from(receipt, "base64"), this.receiptSchema);
64
+ if (!receiptVerification.verified) throw new Error("Receipt verification failed.");
65
+ return receiptVerification;
66
+ }
67
+ verifyFieldSchema(sequence) {
68
+ const fieldVerification = verifySchema(sequence.toBER(), this.fieldSchema);
69
+ if (!fieldVerification.verified) return null;
70
+ return fieldVerification;
71
+ }
88
72
  };
89
-
90
- // src/ReceiptParser.ts
73
+ //#endregion
74
+ //#region src/ReceiptParser.ts
91
75
  var ReceiptParser = class {
92
- parsed;
93
- receiptVerifier;
94
- constructor() {
95
- this.receiptVerifier = new ReceiptVerifier();
96
- this.parsed = this.createInitialParsedReceipt();
97
- }
98
- parseReceipt(receipt) {
99
- if (receipt.trim() === "") {
100
- throw new Error("Receipt must be a non-empty string.");
101
- }
102
- const rootSchemaVerification = this.receiptVerifier.verifyReceiptSchema(receipt);
103
- const content = rootSchemaVerification.result[CONTENT_ID];
104
- this.parseReceiptContent(content);
105
- this.validateParsedFields();
106
- this.deduplicateArrayFields();
107
- return this.parsed;
108
- }
109
- createInitialParsedReceipt() {
110
- return {
111
- ENVIRONMENT: "Production",
112
- IN_APP_RECEIPTS: [],
113
- IN_APP_ORIGINAL_TRANSACTION_IDS: [],
114
- IN_APP_TRANSACTION_IDS: []
115
- };
116
- }
117
- parseReceiptContent(content, inAppReceipt) {
118
- const sequences = this.extractSequencesFromContent(content);
119
- sequences.forEach((sequence) => this.processSequence(sequence, inAppReceipt));
120
- }
121
- extractSequencesFromContent(content) {
122
- const [contentSet] = content.valueBlock.value;
123
- return contentSet.valueBlock.value.filter((v) => v instanceof ASN1.Sequence);
124
- }
125
- processSequence(sequence, inAppReceipt) {
126
- const verifiedSequence = this.receiptVerifier.verifyFieldSchema(sequence);
127
- if (verifiedSequence) {
128
- this.handleVerifiedSequence(verifiedSequence, inAppReceipt);
129
- }
130
- }
131
- handleVerifiedSequence(verifiedSequence, inAppReceipt) {
132
- const fieldKey = verifiedSequence.result[FIELD_TYPE_ID].valueBlock.valueDec;
133
- const fieldValue = verifiedSequence.result[FIELD_VALUE_ID];
134
- const handler = this.getFieldHandler(fieldKey, inAppReceipt);
135
- handler(fieldValue);
136
- }
137
- getFieldHandler(fieldKey, inAppReceipt) {
138
- if (fieldKey === IN_APP) {
139
- return (fieldValue) => {
140
- const parsedInAppReceipt = {};
141
- this.parseReceiptContent(fieldValue, parsedInAppReceipt);
142
- this.parsed.IN_APP_RECEIPTS.push(parsedInAppReceipt);
143
- };
144
- }
145
- if (this.isValidReceiptFieldKey(fieldKey)) {
146
- const name = RECEIPT_FIELDS_MAP.get(fieldKey);
147
- return (fieldValue) => {
148
- this.addFieldToReceipt(name, this.extractStringValue(fieldValue), inAppReceipt);
149
- };
150
- }
151
- return () => {
152
- };
153
- }
154
- isValidReceiptFieldKey(value) {
155
- return typeof value === "number" && RECEIPT_FIELDS_MAP.has(value);
156
- }
157
- extractStringValue(field) {
158
- const [fieldValue] = field.valueBlock.value;
159
- if (fieldValue instanceof ASN1.IA5String || fieldValue instanceof ASN1.Utf8String) {
160
- return fieldValue.valueBlock.value;
161
- }
162
- return field.toJSON().valueBlock.valueHex;
163
- }
164
- addFieldToReceipt(name, value, inAppReceipt) {
165
- this.addToArrayFieldIfApplicable(name, value);
166
- this.parsed[name] = value;
167
- if (inAppReceipt) {
168
- inAppReceipt[name] = value;
169
- }
170
- }
171
- addToArrayFieldIfApplicable(name, value) {
172
- const arrayFields = {
173
- "IN_APP_ORIGINAL_TRANSACTION_ID": "IN_APP_ORIGINAL_TRANSACTION_IDS",
174
- "IN_APP_TRANSACTION_ID": "IN_APP_TRANSACTION_IDS"
175
- };
176
- const arrayFieldName = arrayFields[name];
177
- if (arrayFieldName) {
178
- this.parsed[arrayFieldName].push(value);
179
- }
180
- }
181
- validateParsedFields() {
182
- const missingFields = Array.from(RECEIPT_FIELDS_MAP.values()).filter((fieldKey) => !(fieldKey in this.parsed));
183
- if (missingFields.length > 0) {
184
- throw new Error(`Missing required fields: ${missingFields.join(", ")}`);
185
- }
186
- }
187
- deduplicateArrayFields() {
188
- this.parsed.IN_APP_ORIGINAL_TRANSACTION_IDS = this.removeDuplicates(this.parsed.IN_APP_ORIGINAL_TRANSACTION_IDS);
189
- this.parsed.IN_APP_TRANSACTION_IDS = this.removeDuplicates(this.parsed.IN_APP_TRANSACTION_IDS);
190
- }
191
- removeDuplicates(array) {
192
- return [...new Set(array)];
193
- }
76
+ parsed;
77
+ receiptVerifier;
78
+ constructor() {
79
+ this.receiptVerifier = new ReceiptVerifier();
80
+ this.parsed = this.createInitialParsedReceipt();
81
+ }
82
+ parseReceipt(receipt) {
83
+ if (receipt.trim() === "") throw new Error("Receipt must be a non-empty string.");
84
+ const content = this.receiptVerifier.verifyReceiptSchema(receipt).result[CONTENT_ID];
85
+ this.parseReceiptContent(content);
86
+ this.validateParsedFields();
87
+ this.deduplicateArrayFields();
88
+ return this.parsed;
89
+ }
90
+ createInitialParsedReceipt() {
91
+ return {
92
+ ENVIRONMENT: "Production",
93
+ IN_APP_RECEIPTS: [],
94
+ IN_APP_ORIGINAL_TRANSACTION_IDS: [],
95
+ IN_APP_TRANSACTION_IDS: []
96
+ };
97
+ }
98
+ parseReceiptContent(content, inAppReceipt) {
99
+ this.extractSequencesFromContent(content).forEach((sequence) => this.processSequence(sequence, inAppReceipt));
100
+ }
101
+ extractSequencesFromContent(content) {
102
+ const [contentSet] = content.valueBlock.value;
103
+ return contentSet.valueBlock.value.filter((v) => v instanceof ASN1.Sequence);
104
+ }
105
+ processSequence(sequence, inAppReceipt) {
106
+ const verifiedSequence = this.receiptVerifier.verifyFieldSchema(sequence);
107
+ if (verifiedSequence) this.handleVerifiedSequence(verifiedSequence, inAppReceipt);
108
+ }
109
+ handleVerifiedSequence(verifiedSequence, inAppReceipt) {
110
+ const fieldKey = verifiedSequence.result[FIELD_TYPE_ID].valueBlock.valueDec;
111
+ const fieldValue = verifiedSequence.result[FIELD_VALUE_ID];
112
+ this.getFieldHandler(fieldKey, inAppReceipt)(fieldValue);
113
+ }
114
+ getFieldHandler(fieldKey, inAppReceipt) {
115
+ if (fieldKey === 17) return (fieldValue) => {
116
+ const parsedInAppReceipt = {};
117
+ this.parseReceiptContent(fieldValue, parsedInAppReceipt);
118
+ this.parsed.IN_APP_RECEIPTS.push(parsedInAppReceipt);
119
+ };
120
+ if (this.isValidReceiptFieldKey(fieldKey)) {
121
+ const name = RECEIPT_FIELDS_MAP.get(fieldKey);
122
+ return (fieldValue) => {
123
+ this.addFieldToReceipt(name, this.extractStringValue(fieldValue), inAppReceipt);
124
+ };
125
+ }
126
+ return () => {};
127
+ }
128
+ isValidReceiptFieldKey(value) {
129
+ return typeof value === "number" && RECEIPT_FIELDS_MAP.has(value);
130
+ }
131
+ extractStringValue(field) {
132
+ const [fieldValue] = field.valueBlock.value;
133
+ if (fieldValue instanceof ASN1.IA5String || fieldValue instanceof ASN1.Utf8String) return fieldValue.valueBlock.value;
134
+ if (fieldValue instanceof ASN1.Integer) return fieldValue.toBigInt().toString();
135
+ return field.toJSON().valueBlock.valueHex;
136
+ }
137
+ addFieldToReceipt(name, value, inAppReceipt) {
138
+ this.addToArrayFieldIfApplicable(name, value);
139
+ this.parsed[name] = value;
140
+ if (inAppReceipt) inAppReceipt[name] = value;
141
+ }
142
+ addToArrayFieldIfApplicable(name, value) {
143
+ const arrayFieldName = {
144
+ "IN_APP_ORIGINAL_TRANSACTION_ID": "IN_APP_ORIGINAL_TRANSACTION_IDS",
145
+ "IN_APP_TRANSACTION_ID": "IN_APP_TRANSACTION_IDS"
146
+ }[name];
147
+ if (arrayFieldName) this.parsed[arrayFieldName].push(value);
148
+ }
149
+ validateParsedFields() {
150
+ const missingFields = Array.from(RECEIPT_FIELDS_MAP.values()).filter((fieldKey) => !fieldKey.startsWith("IN_APP_")).filter((fieldKey) => !(fieldKey in this.parsed));
151
+ if (missingFields.length > 0) throw new Error(`Missing required fields: ${missingFields.join(", ")}`);
152
+ }
153
+ deduplicateArrayFields() {
154
+ this.parsed.IN_APP_ORIGINAL_TRANSACTION_IDS = this.removeDuplicates(this.parsed.IN_APP_ORIGINAL_TRANSACTION_IDS);
155
+ this.parsed.IN_APP_TRANSACTION_IDS = this.removeDuplicates(this.parsed.IN_APP_TRANSACTION_IDS);
156
+ }
157
+ removeDuplicates(array) {
158
+ return [...new Set(array)];
159
+ }
194
160
  };
195
161
  function parseReceipt(receipt) {
196
- return new ReceiptParser().parseReceipt(receipt);
162
+ return new ReceiptParser().parseReceipt(receipt);
197
163
  }
198
- export {
199
- parseReceipt
200
- };
164
+ //#endregion
165
+ export { parseReceipt };
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@tamtamchik/app-store-receipt-parser",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "description": "A lightweight TypeScript library for extracting selected fields from Apple's ASN.1 encoded receipts.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
7
- "types": "./src/index.ts",
7
+ "types": "./dist/index.d.ts",
8
8
  "author": "Yuri Tkachenko <yuri.tam.tkachenko@gmail.com>",
9
9
  "license": "MIT",
10
10
  "homepage": "https://github.com/tamtamchik/app-store-receipt-parser#readme",
@@ -26,7 +26,7 @@
26
26
  ],
27
27
  "exports": {
28
28
  ".": {
29
- "types": "./src/index.ts",
29
+ "types": "./dist/index.d.ts",
30
30
  "require": "./dist/index.js",
31
31
  "import": "./dist/index.mjs"
32
32
  }
@@ -41,13 +41,13 @@
41
41
  "c8": "^11.0.0",
42
42
  "eslint": "^10.2.0",
43
43
  "globals": "^17.5.0",
44
- "tsup": "^8.0.2",
44
+ "tsdown": "^0.22.2",
45
45
  "tsx": "^4.21.0",
46
46
  "typescript": "^6.0.2",
47
47
  "typescript-eslint": "^8.58.2"
48
48
  },
49
49
  "scripts": {
50
- "build": "tsup src/index.ts --format cjs,esm --clean",
50
+ "build": "tsdown",
51
51
  "dev": "npm run build -- --watch src",
52
52
  "lint": "eslint src",
53
53
  "lint:fix": "eslint src --fix",
@@ -11,7 +11,7 @@ import {
11
11
 
12
12
  import { ReceiptVerifier } from './ReceiptVerifier'
13
13
 
14
- export type Environment = 'Production' | 'ProductionSandbox' | string
14
+ export type Environment = 'Production' | 'ProductionSandbox' | (string & {})
15
15
 
16
16
  export type InAppReceipt = Partial<Record<ReceiptFieldsKeyNames, string>>
17
17
 
@@ -115,6 +115,10 @@ class ReceiptParser {
115
115
  return fieldValue.valueBlock.value
116
116
  }
117
117
 
118
+ if (fieldValue instanceof ASN1.Integer) {
119
+ return fieldValue.toBigInt().toString()
120
+ }
121
+
118
122
  return field.toJSON().valueBlock.valueHex
119
123
  }
120
124
 
@@ -139,7 +143,9 @@ class ReceiptParser {
139
143
  }
140
144
 
141
145
  private validateParsedFields(): void {
146
+ // In-app fields are optional: a valid receipt may contain no in-app purchases.
142
147
  const missingFields = Array.from(RECEIPT_FIELDS_MAP.values())
148
+ .filter(fieldKey => !fieldKey.startsWith('IN_APP_'))
143
149
  .filter(fieldKey => !(fieldKey in this.parsed))
144
150
 
145
151
  if (missingFields.length > 0) {
package/src/index.ts CHANGED
@@ -1,2 +1,2 @@
1
- export type { InAppReceipt, ParsedReceipt } from './ReceiptParser'
1
+ export type { Environment, InAppReceipt, ParsedReceipt } from './ReceiptParser'
2
2
  export { parseReceipt } from './ReceiptParser'
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'tsdown'
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ format: ['cjs', 'esm'],
6
+ dts: true,
7
+ clean: true,
8
+ // Keep the file names the package has always shipped (index.js / index.mjs / index.d.ts)
9
+ outExtensions: ({ format }) => ({
10
+ js: format === 'cjs' ? '.js' : '.mjs',
11
+ dts: format === 'cjs' ? '.d.ts' : '.d.mts',
12
+ }),
13
+ })