react-native-iap 15.3.5 → 15.3.6

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.
@@ -16,12 +16,125 @@ export interface IapError {
16
16
  [key: string]: any; // Allow additional platform-specific fields
17
17
  }
18
18
 
19
+ const parseJsonPayload = (
20
+ payload: string,
21
+ fallbackMessage: string,
22
+ ): IapError | null => {
23
+ const trimmed = payload.trim();
24
+ const candidates = [trimmed];
25
+
26
+ if (trimmed.includes('\\"')) {
27
+ candidates.push(trimmed.replace(/\\"/g, '"'));
28
+ }
29
+
30
+ for (const candidate of candidates) {
31
+ try {
32
+ const parsed = JSON.parse(candidate);
33
+ if (typeof parsed === 'object' && parsed !== null) {
34
+ const parsedError = parsed as Partial<IapError>;
35
+ return {
36
+ code: parsedError.code || ErrorCode.Unknown,
37
+ message: parsedError.message || fallbackMessage,
38
+ ...parsedError,
39
+ };
40
+ }
41
+ } catch {
42
+ // Try the next candidate.
43
+ }
44
+ }
45
+
46
+ return null;
47
+ };
48
+
49
+ const extractBalancedJsonObject = (
50
+ value: string,
51
+ startIndex: number,
52
+ ): string | null => {
53
+ for (let index = startIndex; index < value.length; index += 1) {
54
+ if (value[index] !== '}') {
55
+ continue;
56
+ }
57
+
58
+ const candidate = value.substring(startIndex, index + 1);
59
+ if (parseJsonPayload(candidate, value)) {
60
+ return candidate;
61
+ }
62
+ }
63
+
64
+ return null;
65
+ };
66
+
67
+ const parseNSErrorJsonPayload = (rawString: string): IapError | null => {
68
+ const payloads: string[] = [];
69
+ const quotedPayloadMatch = /Code=-?\d+\s+"/.exec(rawString);
70
+
71
+ if (quotedPayloadMatch?.index !== undefined) {
72
+ const contentStart =
73
+ quotedPayloadMatch.index + quotedPayloadMatch[0].length;
74
+ const userInfoIndex = rawString.indexOf('UserInfo=', contentStart);
75
+ const contentEnd =
76
+ userInfoIndex > contentStart
77
+ ? rawString.lastIndexOf('"', userInfoIndex)
78
+ : rawString.lastIndexOf('"');
79
+
80
+ if (contentEnd > contentStart) {
81
+ payloads.push(rawString.substring(contentStart, contentEnd));
82
+ }
83
+ }
84
+
85
+ const localizedDescription = 'NSLocalizedDescription=';
86
+ const descriptionIndex = rawString.indexOf(localizedDescription);
87
+
88
+ if (descriptionIndex >= 0) {
89
+ let valueStart = descriptionIndex + localizedDescription.length;
90
+ while (/\s/.test(rawString[valueStart] ?? '')) {
91
+ valueStart += 1;
92
+ }
93
+
94
+ const firstBraceIndex = rawString.indexOf('{', valueStart);
95
+ if (firstBraceIndex >= 0 && firstBraceIndex <= valueStart + 2) {
96
+ const payload = extractBalancedJsonObject(rawString, firstBraceIndex);
97
+ if (payload) {
98
+ payloads.push(payload);
99
+ }
100
+ }
101
+ }
102
+
103
+ for (const payload of payloads) {
104
+ const parsed = parseJsonPayload(payload, rawString);
105
+ if (parsed?.code && parsed.code !== ErrorCode.Unknown) {
106
+ return parsed;
107
+ }
108
+ }
109
+
110
+ return null;
111
+ };
112
+
113
+ const parseStructuredError = (error: Error): IapError | null => {
114
+ const errorWithCode = error as Error & {code?: unknown} & Record<
115
+ string,
116
+ unknown
117
+ >;
118
+
119
+ if (typeof errorWithCode.code === 'string' && errorWithCode.code.length > 0) {
120
+ const {code, message, ...extraFields} = errorWithCode;
121
+
122
+ return {
123
+ ...extraFields,
124
+ code,
125
+ message: typeof message === 'string' ? message : error.message,
126
+ };
127
+ }
128
+
129
+ return null;
130
+ };
131
+
19
132
  /**
20
133
  * Parses error string from native modules into a structured error object
21
134
  *
22
135
  * Native modules return errors in different formats:
23
136
  * - Android: JSON string like '{"code":"E_USER_CANCELLED","message":"User cancelled the purchase","responseCode":1}'
24
- * - iOS: JSON string or plain message
137
+ * - iOS: JSON string, Nitro NSError string with embedded JSON, or plain message
25
138
  * - Legacy: "CODE: message" format
26
139
  *
27
140
  * @param errorString - The error string from native module
@@ -32,6 +145,11 @@ export function parseErrorStringToJsonObj(
32
145
  ): IapError {
33
146
  // Handle Error objects
34
147
  if (errorString instanceof Error) {
148
+ const structuredError = parseStructuredError(errorString);
149
+ if (structuredError) {
150
+ return structuredError;
151
+ }
152
+
35
153
  errorString = errorString.message;
36
154
  }
37
155
 
@@ -43,19 +161,14 @@ export function parseErrorStringToJsonObj(
43
161
  };
44
162
  }
45
163
 
46
- // Try to parse as JSON first
47
- try {
48
- const parsed = JSON.parse(errorString);
49
- if (typeof parsed === 'object' && parsed !== null) {
50
- // Ensure it has at least code and message
51
- return {
52
- code: parsed.code || ErrorCode.Unknown,
53
- message: parsed.message || errorString,
54
- ...parsed,
55
- };
56
- }
57
- } catch {
58
- // Not JSON, continue with other formats
164
+ const jsonPayload = parseJsonPayload(errorString, errorString);
165
+ if (jsonPayload) {
166
+ return jsonPayload;
167
+ }
168
+
169
+ const nsErrorPayload = parseNSErrorJsonPayload(errorString);
170
+ if (nsErrorPayload) {
171
+ return nsErrorPayload;
59
172
  }
60
173
 
61
174
  // Try to parse "CODE: message" format