react-native-iap 15.4.1 → 15.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -150,6 +150,129 @@ function createVegaError(
150
150
  return error;
151
151
  }
152
152
 
153
+ function isValidIpv4Address(address: string): boolean {
154
+ const octets = address.split('.');
155
+ return (
156
+ octets.length === 4 &&
157
+ octets.every(
158
+ (octet) =>
159
+ /^(?:0|[1-9]\d{0,2})$/.test(octet) && Number(octet) <= 255,
160
+ )
161
+ );
162
+ }
163
+
164
+ function isValidIpv6Address(address: string): boolean {
165
+ let ipv6Part = address;
166
+ let ipv4GroupCount = 0;
167
+
168
+ if (address.includes('.')) {
169
+ const lastColon = address.lastIndexOf(':');
170
+ if (lastColon < 0 || !isValidIpv4Address(address.slice(lastColon + 1))) {
171
+ return false;
172
+ }
173
+ const ipv6Prefix = address.slice(0, lastColon);
174
+ ipv6Part = ipv6Prefix.endsWith(':') ? `${ipv6Prefix}:` : ipv6Prefix;
175
+ ipv4GroupCount = 2;
176
+ }
177
+
178
+ if (
179
+ !ipv6Part.includes(':') ||
180
+ !/^[0-9a-f:]+$/i.test(ipv6Part) ||
181
+ ipv6Part.includes(':::')
182
+ ) {
183
+ return false;
184
+ }
185
+
186
+ const compressionIndex = ipv6Part.indexOf('::');
187
+ const hasCompression = compressionIndex >= 0;
188
+ if (
189
+ (hasCompression && ipv6Part.indexOf('::', compressionIndex + 2) >= 0) ||
190
+ (!hasCompression && (ipv6Part.startsWith(':') || ipv6Part.endsWith(':')))
191
+ ) {
192
+ return false;
193
+ }
194
+
195
+ const sections = hasCompression ? ipv6Part.split('::') : [ipv6Part];
196
+ const groups: string[] = [];
197
+ for (const section of sections) {
198
+ if (section.length > 0) groups.push(...section.split(':'));
199
+ }
200
+ if (!groups.every((group) => /^[0-9a-f]{1,4}$/i.test(group))) {
201
+ return false;
202
+ }
203
+
204
+ const groupCount = groups.length + ipv4GroupCount;
205
+ return hasCompression ? groupCount < 8 : groupCount === 8;
206
+ }
207
+
208
+ function isValidHostname(hostname: string): boolean {
209
+ if (/^[0-9.]+$/.test(hostname)) {
210
+ return isValidIpv4Address(hostname);
211
+ }
212
+
213
+ const normalizedHostname = hostname.endsWith('.')
214
+ ? hostname.slice(0, -1)
215
+ : hostname;
216
+ if (normalizedHostname.length === 0 || normalizedHostname.length > 253) {
217
+ return false;
218
+ }
219
+ const hostnameLabels = normalizedHostname.split('.');
220
+ const lastLabel = hostnameLabels[hostnameLabels.length - 1]!;
221
+ if (/^(?:[0-9]+|0x[0-9a-f]+)$/i.test(lastLabel)) {
222
+ return false;
223
+ }
224
+
225
+ return normalizedHostname
226
+ .split('.')
227
+ .every(
228
+ (label) =>
229
+ label.length <= 63 && /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i.test(label),
230
+ );
231
+ }
232
+
233
+ function getIapkitVerifyUrl(baseUrl?: string | null): string {
234
+ const requestedBaseUrl =
235
+ typeof baseUrl === 'string' && baseUrl.trim().length > 0
236
+ ? baseUrl.trim()
237
+ : IAPKIT_DEFAULT_BASE_URL;
238
+ const normalizedBaseUrl = requestedBaseUrl.replace(/\/+$/, '');
239
+ // Kepler's URL polyfill throws for standard getters such as protocol,
240
+ // host, and pathname. Parse the small origin-only contract directly.
241
+ const originMatch = /^(https?):\/\/([^/?#@\s\\]+)$/i.exec(normalizedBaseUrl);
242
+ if (!originMatch) {
243
+ throw createVegaError(
244
+ ErrorCode.DeveloperError,
245
+ 'IAPKit baseUrl must be a valid HTTP(S) origin',
246
+ );
247
+ }
248
+
249
+ const authority = originMatch[2]!;
250
+ const isBracketedIpv6 = authority.startsWith('[');
251
+ const authorityMatch = isBracketedIpv6
252
+ ? /^\[([^\]]+)\](?::([0-9]+))?$/.exec(authority)
253
+ : /^([^:]+)(?::([0-9]+))?$/.exec(authority);
254
+ const host = authorityMatch?.[1];
255
+ const requestedPort = authorityMatch?.[2];
256
+ const portNumber = requestedPort ? Number(requestedPort) : null;
257
+ const hasValidHost =
258
+ typeof host === 'string' &&
259
+ (isBracketedIpv6 ? isValidIpv6Address(host) : isValidHostname(host));
260
+ const hasValidPort =
261
+ portNumber === null ||
262
+ (Number.isInteger(portNumber) && portNumber >= 1 && portNumber <= 65535);
263
+ if (!authorityMatch || !hasValidHost || !hasValidPort) {
264
+ throw createVegaError(
265
+ ErrorCode.DeveloperError,
266
+ 'IAPKit baseUrl must be a valid HTTP(S) origin',
267
+ );
268
+ }
269
+
270
+ const scheme = originMatch[1]!.toLowerCase();
271
+ const serializedHost = isBracketedIpv6 ? `[${host!}]` : host!;
272
+ const port = requestedPort ? `:${requestedPort}` : '';
273
+ return `${scheme}://${serializedHost}${port}${IAPKIT_VERIFY_PATH}`;
274
+ }
275
+
153
276
  function parseVegaErrorPayload(error: unknown): Record<string, unknown> {
154
277
  if (!(error instanceof Error)) return {};
155
278
  const vegaError = error as VegaError;
@@ -1023,27 +1146,6 @@ export function createVegaIapModule(service: VegaPurchasingService): RnIap {
1023
1146
  const verifyWithIapkit = async (
1024
1147
  params: NitroVerifyPurchaseWithProviderProps,
1025
1148
  ): Promise<NitroVerifyPurchaseWithProviderResult> => {
1026
- type IapkitEndpointOptions = NonNullable<
1027
- NitroVerifyPurchaseWithProviderProps['iapkit']
1028
- > & {
1029
- baseUrl?: string | null;
1030
- };
1031
-
1032
- function iapkitVerifyUrl(
1033
- iapkit: NitroVerifyPurchaseWithProviderProps['iapkit'],
1034
- ): string {
1035
- const endpointOptions = iapkit as
1036
- | IapkitEndpointOptions
1037
- | null
1038
- | undefined;
1039
- const baseUrl =
1040
- typeof endpointOptions?.baseUrl === 'string' &&
1041
- endpointOptions.baseUrl.trim().length > 0
1042
- ? endpointOptions.baseUrl.trim()
1043
- : IAPKIT_DEFAULT_BASE_URL;
1044
- return `${baseUrl.replace(/\/+$/, '')}${IAPKIT_VERIFY_PATH}`;
1045
- }
1046
-
1047
1149
  function normalizeIapkitState(state: unknown): IapkitPurchaseState {
1048
1150
  const normalized =
1049
1151
  typeof state === 'string'
@@ -1198,6 +1300,7 @@ export function createVegaIapModule(service: VegaPurchasingService): RnIap {
1198
1300
 
1199
1301
  const apiKey =
1200
1302
  typeof iapkit?.apiKey === 'string' ? iapkit.apiKey.trim() : '';
1303
+ const verificationUrl = getIapkitVerifyUrl(iapkit?.baseUrl);
1201
1304
  let response: Response;
1202
1305
  try {
1203
1306
  const controller = new AbortController();
@@ -1205,7 +1308,7 @@ export function createVegaIapModule(service: VegaPurchasingService): RnIap {
1205
1308
  () => controller.abort(),
1206
1309
  IAPKIT_VERIFY_TIMEOUT_MS,
1207
1310
  );
1208
- response = await fetch(iapkitVerifyUrl(iapkit), {
1311
+ response = await fetch(verificationUrl, {
1209
1312
  method: 'POST',
1210
1313
  headers: {
1211
1314
  'Content-Type': 'application/json',