@mohasinac/appkit 3.2.11 → 3.2.14
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/_internal/server/features/checkout/actions.js +11 -9
- package/dist/_internal/server/features/grouped/data.js +11 -3
- package/dist/_internal/server/features/products/data.js +5 -1
- package/dist/_internal/server/jobs/core/listingProcessor.js +2 -1
- package/dist/_internal/server/jobs/core/wrapJobHandler.js +2 -2
- package/dist/_internal/shared/listing-types/action-tracker.js +2 -2
- package/dist/features/admin/actions/notification-actions.js +3 -2
- package/dist/features/admin/repository/chat.repository.js +4 -4
- package/dist/features/admin/repository/site-settings.repository.js +8 -4
- package/dist/features/cron/jobs/auction-expiry.job.js +5 -1
- package/dist/features/cron/jobs/preorder-reminder.job.js +5 -1
- package/dist/features/promotions/repository/coupons.repository.js +5 -2
- package/dist/features/scams/repository/scammer.repository.js +10 -3
- package/dist/utils/media-url.js +3 -1
- package/dist/validation/url.validator.js +7 -3
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeError } from "../../../../errors/normalize";
|
|
2
|
+
import { safeFireAndForget } from "../../../../utils/safe-fire-forget";
|
|
2
3
|
/**
|
|
3
4
|
* Checkout server actions (appkit).
|
|
4
5
|
*
|
|
@@ -29,6 +30,7 @@ import { formatShippingAddress } from "./data";
|
|
|
29
30
|
import { enforceMaxPerUserForCart, computePrizeRevealDeadline, } from "./prize-bundle-gates";
|
|
30
31
|
import { getListingRule, runSyncPreflight, } from "../../../shared/checkout/rules";
|
|
31
32
|
import { cartIsDigitalOnly } from "../../../shared/listing-types/cart-shipping";
|
|
33
|
+
const AUDIT_LOG_FAIL_MSG = "checkout: audit log failed (non-critical)";
|
|
32
34
|
/**
|
|
33
35
|
* SB-UNI-N — Atomically claim the next available digital code for a confirmed
|
|
34
36
|
* order. Pre-fetches a candidate code outside the transaction then locks and
|
|
@@ -264,7 +266,7 @@ export async function createCheckoutOrderAction(input) {
|
|
|
264
266
|
if (!addressId) {
|
|
265
267
|
failedCheckoutRepository
|
|
266
268
|
.logCheckout(uid, "address_not_found", "Address required for physical cart", { addressId: "", paymentMethod })
|
|
267
|
-
.catch(
|
|
269
|
+
.catch((err) => { void normalizeError(err); serverLogger.warn(AUDIT_LOG_FAIL_MSG, { error: err instanceof Error ? err.message : String(err) }); });
|
|
268
270
|
throw new NotFoundError(ERROR_MESSAGES.CHECKOUT.ADDRESS_REQUIRED);
|
|
269
271
|
}
|
|
270
272
|
const addressDoc = await unitOfWork.addresses.findById(addressId);
|
|
@@ -275,7 +277,7 @@ export async function createCheckoutOrderAction(input) {
|
|
|
275
277
|
if (!resolvedAddress) {
|
|
276
278
|
failedCheckoutRepository
|
|
277
279
|
.logCheckout(uid, "address_not_found", "Address not found", { addressId, paymentMethod })
|
|
278
|
-
.catch(
|
|
280
|
+
.catch((err) => { void normalizeError(err); serverLogger.warn(AUDIT_LOG_FAIL_MSG, { error: err instanceof Error ? err.message : String(err) }); });
|
|
279
281
|
throw new NotFoundError(ERROR_MESSAGES.CHECKOUT.ADDRESS_REQUIRED);
|
|
280
282
|
}
|
|
281
283
|
shippingAddress = formatShippingAddress(resolvedAddress);
|
|
@@ -414,7 +416,7 @@ export async function createCheckoutOrderAction(input) {
|
|
|
414
416
|
addressId,
|
|
415
417
|
paymentMethod,
|
|
416
418
|
})
|
|
417
|
-
.catch(
|
|
419
|
+
.catch((logErr) => { void normalizeError(logErr); serverLogger.warn(AUDIT_LOG_FAIL_MSG, { error: logErr instanceof Error ? logErr.message : String(logErr) }); });
|
|
418
420
|
throw err;
|
|
419
421
|
}
|
|
420
422
|
const { available, unavailable, emailOtpUsed } = stockResult;
|
|
@@ -425,7 +427,7 @@ export async function createCheckoutOrderAction(input) {
|
|
|
425
427
|
paymentMethod,
|
|
426
428
|
cartItemCount: cartItems.length,
|
|
427
429
|
})
|
|
428
|
-
.catch(
|
|
430
|
+
.catch((logErr) => { void normalizeError(logErr); serverLogger.warn(AUDIT_LOG_FAIL_MSG, { error: logErr instanceof Error ? logErr.message : String(logErr) }); });
|
|
429
431
|
throw new ValidationError(ERROR_MESSAGES.CHECKOUT.INSUFFICIENT_STOCK);
|
|
430
432
|
}
|
|
431
433
|
const appliedCoupons = cart.appliedCoupons ?? [];
|
|
@@ -669,7 +671,7 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
|
|
|
669
671
|
gatewayPaymentId: razorpay_payment_id,
|
|
670
672
|
addressId,
|
|
671
673
|
})
|
|
672
|
-
.catch(
|
|
674
|
+
.catch((logErr) => { void normalizeError(logErr); serverLogger.warn(AUDIT_LOG_FAIL_MSG, { error: logErr instanceof Error ? logErr.message : String(logErr) }); });
|
|
673
675
|
throw new ValidationError(ERROR_MESSAGES.CHECKOUT.PAYMENT_FAILED);
|
|
674
676
|
}
|
|
675
677
|
const cart = await unitOfWork.carts.getOrCreate(uid);
|
|
@@ -711,7 +713,7 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
|
|
|
711
713
|
gatewayPaymentId: razorpay_payment_id,
|
|
712
714
|
addressId,
|
|
713
715
|
})
|
|
714
|
-
.catch(
|
|
716
|
+
.catch((err) => { void normalizeError(err); serverLogger.warn(AUDIT_LOG_FAIL_MSG, { error: err instanceof Error ? err.message : String(err) }); });
|
|
715
717
|
throw new ApiError(403, "Order verification required. Please complete OTP verification and retry.");
|
|
716
718
|
}
|
|
717
719
|
}
|
|
@@ -762,7 +764,7 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
|
|
|
762
764
|
gatewayPaymentId: razorpay_payment_id,
|
|
763
765
|
addressId,
|
|
764
766
|
})
|
|
765
|
-
.catch(
|
|
767
|
+
.catch((err) => { void normalizeError(err); serverLogger.warn(AUDIT_LOG_FAIL_MSG, { error: err instanceof Error ? err.message : String(err) }); });
|
|
766
768
|
throw new ValidationError(exists
|
|
767
769
|
? ERROR_MESSAGES.CHECKOUT.INSUFFICIENT_STOCK
|
|
768
770
|
: ERROR_MESSAGES.CHECKOUT.PRODUCT_UNAVAILABLE);
|
|
@@ -790,7 +792,7 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
|
|
|
790
792
|
amountRs: paidAmountRs,
|
|
791
793
|
addressId,
|
|
792
794
|
})
|
|
793
|
-
.catch(
|
|
795
|
+
.catch((err) => { void normalizeError(err); serverLogger.warn(AUDIT_LOG_FAIL_MSG, { error: err instanceof Error ? err.message : String(err) }); });
|
|
794
796
|
throw new ValidationError(ERROR_MESSAGES.CHECKOUT.PAYMENT_FAILED);
|
|
795
797
|
}
|
|
796
798
|
}
|
|
@@ -996,7 +998,7 @@ export async function verifyAndPlaceRazorpayOrderAction(input) {
|
|
|
996
998
|
});
|
|
997
999
|
if (!isDigitalCartRazorpay && addressId) {
|
|
998
1000
|
const otpRefForDelete = consentOtpRef(db, uid, addressId);
|
|
999
|
-
otpRefForDelete.delete()
|
|
1001
|
+
safeFireAndForget(otpRefForDelete.delete(), "checkout: delete OTP consent ref after Razorpay payment");
|
|
1000
1002
|
}
|
|
1001
1003
|
if (emailsToSend.length > 0) {
|
|
1002
1004
|
Promise.all(emailsToSend.map((e) => sendOrderConfirmationEmail(e))).catch((err) => serverLogger.error("Order confirmation email error:", err));
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { cache } from "react";
|
|
2
2
|
import { getAdminDb } from "../../../../providers/db-firebase";
|
|
3
|
+
import { normalizeError } from "../../../../errors/normalize";
|
|
4
|
+
import { serverLogger } from "../../../../monitoring/server-logger";
|
|
3
5
|
import { GROUPED_LISTINGS_COLLECTION, } from "../../../../features/grouped/schemas/firestore";
|
|
4
6
|
import { productRepository } from "../../../../repositories";
|
|
5
7
|
import { PRODUCT_FIELDS, CATEGORY_FIELDS } from "../../../../constants/field-names";
|
|
@@ -55,7 +57,9 @@ export const getGroupedListingForDetail = cache(async (slug) => {
|
|
|
55
57
|
}
|
|
56
58
|
return mapDoc(snap.docs[0]);
|
|
57
59
|
}
|
|
58
|
-
catch {
|
|
60
|
+
catch (err) {
|
|
61
|
+
void normalizeError(err);
|
|
62
|
+
serverLogger.warn("grouped-data: getGroupedListingForDetail failed — returning null", { slug, error: err instanceof Error ? err.message : String(err) });
|
|
59
63
|
return null;
|
|
60
64
|
}
|
|
61
65
|
});
|
|
@@ -93,7 +97,9 @@ export async function listGroupedListings(params = {}) {
|
|
|
93
97
|
.get();
|
|
94
98
|
return snap.docs.map(mapDoc);
|
|
95
99
|
}
|
|
96
|
-
catch {
|
|
100
|
+
catch (err) {
|
|
101
|
+
void normalizeError(err);
|
|
102
|
+
serverLogger.warn("grouped-data: listGroupedListings failed — returning empty", { params, error: err instanceof Error ? err.message : String(err) });
|
|
97
103
|
return [];
|
|
98
104
|
}
|
|
99
105
|
}
|
|
@@ -118,7 +124,9 @@ export async function listSitemapGroupedListings() {
|
|
|
118
124
|
};
|
|
119
125
|
});
|
|
120
126
|
}
|
|
121
|
-
catch {
|
|
127
|
+
catch (err) {
|
|
128
|
+
void normalizeError(err);
|
|
129
|
+
serverLogger.warn("grouped-data: listSitemapGroupedListings failed — returning empty", { error: err instanceof Error ? err.message : String(err) });
|
|
122
130
|
return [];
|
|
123
131
|
}
|
|
124
132
|
}
|
|
@@ -2,6 +2,8 @@ import { cache } from "react";
|
|
|
2
2
|
import { productRepository } from "../../../../repositories";
|
|
3
3
|
import { reviewRepository } from "../../../../repositories";
|
|
4
4
|
import { getAdminDb } from "../../../../providers/db-firebase";
|
|
5
|
+
import { normalizeError } from "../../../../errors/normalize";
|
|
6
|
+
import { serverLogger } from "../../../../monitoring/server-logger";
|
|
5
7
|
import { PRODUCT_COLLECTION } from "../../../../features/products/schemas/firestore";
|
|
6
8
|
import { PRODUCTS_SITEMAP_LIMIT } from "../../../shared/features/products/config";
|
|
7
9
|
import { PRODUCT_FIELDS } from "../../../../constants/field-names";
|
|
@@ -46,7 +48,9 @@ export async function listSitemapProducts() {
|
|
|
46
48
|
};
|
|
47
49
|
});
|
|
48
50
|
}
|
|
49
|
-
catch {
|
|
51
|
+
catch (err) {
|
|
52
|
+
void normalizeError(err);
|
|
53
|
+
serverLogger.warn("products-data: listSitemapProducts failed — returning empty", { error: err instanceof Error ? err.message : String(err) });
|
|
50
54
|
return [];
|
|
51
55
|
}
|
|
52
56
|
}
|
|
@@ -83,7 +83,8 @@ function decodeCursor(cursor) {
|
|
|
83
83
|
const page = Number(parsed.page);
|
|
84
84
|
return Number.isFinite(page) && page > 0 ? page : null;
|
|
85
85
|
}
|
|
86
|
-
catch {
|
|
86
|
+
catch (_err) {
|
|
87
|
+
// Malformed base64 or JSON — treat as no cursor (start from first page)
|
|
87
88
|
return null;
|
|
88
89
|
}
|
|
89
90
|
}
|
|
@@ -21,8 +21,8 @@ async function persist(name, err, ctxLike) {
|
|
|
21
21
|
occurredAt: (ctxLike?.now ?? new Date()).getTime(),
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
|
-
catch {
|
|
25
|
-
|
|
24
|
+
catch (_err) {
|
|
25
|
+
// Logging the logging failure would cause infinite recursion — intentionally swallowed
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
export function wrapScheduleHandler(name, handler) {
|
|
@@ -36,8 +36,8 @@ export const actionTracker = {
|
|
|
36
36
|
result.catch((err) => { void normalizeError(err); });
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
catch {
|
|
40
|
-
//
|
|
39
|
+
catch (_err) {
|
|
40
|
+
// Telemetry sink threw synchronously — swallowed so the caller action always completes
|
|
41
41
|
}
|
|
42
42
|
},
|
|
43
43
|
};
|
|
@@ -62,8 +62,9 @@ export async function sendNotification(input) {
|
|
|
62
62
|
userChannelPrefs = fetchedUserDoc?.notificationPreferences?.channels ?? {};
|
|
63
63
|
userTypePrefs = fetchedUserDoc?.notificationPreferences?.types ?? {};
|
|
64
64
|
}
|
|
65
|
-
catch {
|
|
66
|
-
|
|
65
|
+
catch (err) {
|
|
66
|
+
void normalizeError(err);
|
|
67
|
+
serverLogger.warn("notification-actions: user prefs lookup failed — defaulting to all channels enabled", { userId: input.userId, error: err instanceof Error ? err.message : String(err) });
|
|
67
68
|
}
|
|
68
69
|
// If caller didn't supply userEmail, try to decrypt it from the already-fetched user doc.
|
|
69
70
|
const resolvedEmail = userEmail ??
|
|
@@ -181,8 +181,8 @@ class ChatRepository extends BaseRepository {
|
|
|
181
181
|
try {
|
|
182
182
|
await getAdminRealtimeDb().ref(`/chat/${chatId}`).remove();
|
|
183
183
|
}
|
|
184
|
-
catch {
|
|
185
|
-
//
|
|
184
|
+
catch (_err) {
|
|
185
|
+
// RTDB cleanup is best-effort — Firestore doc already deleted, messages will be orphaned until TTL cleanup
|
|
186
186
|
}
|
|
187
187
|
return "deleted";
|
|
188
188
|
}
|
|
@@ -210,8 +210,8 @@ class ChatRepository extends BaseRepository {
|
|
|
210
210
|
try {
|
|
211
211
|
await getAdminRealtimeDb().ref(`/chat/${chatId}`).remove();
|
|
212
212
|
}
|
|
213
|
-
catch {
|
|
214
|
-
//
|
|
213
|
+
catch (_err) {
|
|
214
|
+
// RTDB cleanup is best-effort — adminDeleted flag in Firestore already revokes access
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
catch (error) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeError } from "../../../errors/normalize";
|
|
2
|
+
import { serverLogger } from "../../../monitoring/server-logger";
|
|
2
3
|
/**
|
|
3
4
|
* Site Settings Repository
|
|
4
5
|
*
|
|
@@ -131,8 +132,9 @@ export class SiteSettingsRepository extends BaseRepository {
|
|
|
131
132
|
try {
|
|
132
133
|
result[key] = decryptSecret(value);
|
|
133
134
|
}
|
|
134
|
-
catch {
|
|
135
|
-
|
|
135
|
+
catch (_err) {
|
|
136
|
+
// Auth-tag mismatch or malformed ciphertext (e.g., key rotation) — treat as missing credential
|
|
137
|
+
result[key] = "";
|
|
136
138
|
}
|
|
137
139
|
}
|
|
138
140
|
}
|
|
@@ -210,8 +212,10 @@ export class SiteSettingsRepository extends BaseRepository {
|
|
|
210
212
|
.get();
|
|
211
213
|
return doc.exists;
|
|
212
214
|
}
|
|
213
|
-
catch {
|
|
214
|
-
|
|
215
|
+
catch (err) {
|
|
216
|
+
void normalizeError(err);
|
|
217
|
+
serverLogger.error("site-settings-repo: exists() Firestore read failed — rethrowing to avoid masking missing settings", { error: err instanceof Error ? err.message : String(err) });
|
|
218
|
+
throw err;
|
|
215
219
|
}
|
|
216
220
|
}
|
|
217
221
|
/**
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { sieveFilter, sieveAnd, SIEVE_OP } from "@mohasinac/appkit";
|
|
2
2
|
import { createCronJob } from "../registry";
|
|
3
|
+
import { normalizeError } from "../../../errors/normalize";
|
|
4
|
+
import { serverLogger } from "../../../monitoring/server-logger";
|
|
3
5
|
export function createAuctionExpiryJob(auctionRepo) {
|
|
4
6
|
return createCronJob({
|
|
5
7
|
name: "auctions_expire",
|
|
@@ -26,7 +28,9 @@ export function createAuctionExpiryJob(auctionRepo) {
|
|
|
26
28
|
});
|
|
27
29
|
processed++;
|
|
28
30
|
}
|
|
29
|
-
catch {
|
|
31
|
+
catch (err) {
|
|
32
|
+
void normalizeError(err);
|
|
33
|
+
serverLogger.warn("auction-expiry: per-auction close failed", { auctionId: auction.id, error: err instanceof Error ? err.message : String(err) });
|
|
30
34
|
errors++;
|
|
31
35
|
}
|
|
32
36
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { sieveFilter, sieveAnd, SIEVE_OP } from "@mohasinac/appkit";
|
|
2
2
|
import { createCronJob } from "../registry";
|
|
3
|
+
import { normalizeError } from "../../../errors/normalize";
|
|
4
|
+
import { serverLogger } from "../../../monitoring/server-logger";
|
|
3
5
|
export function createPreOrderReminderJob(preOrderRepo) {
|
|
4
6
|
const sevenDaysFromNow = () => {
|
|
5
7
|
const d = new Date();
|
|
@@ -28,7 +30,9 @@ export function createPreOrderReminderJob(preOrderRepo) {
|
|
|
28
30
|
});
|
|
29
31
|
processed++;
|
|
30
32
|
}
|
|
31
|
-
catch {
|
|
33
|
+
catch (err) {
|
|
34
|
+
void normalizeError(err);
|
|
35
|
+
serverLogger.warn("preorder-reminder: per-order update failed", { orderId: order.id, error: err instanceof Error ? err.message : String(err) });
|
|
32
36
|
errors++;
|
|
33
37
|
}
|
|
34
38
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeError } from "../../../errors/normalize";
|
|
2
|
+
import { serverLogger } from "../../../monitoring/server-logger";
|
|
2
3
|
/**
|
|
3
4
|
* Coupons Repository
|
|
4
5
|
*
|
|
@@ -270,8 +271,10 @@ export class CouponsRepository extends BaseRepository {
|
|
|
270
271
|
const data = doc.data();
|
|
271
272
|
return data.usageCount ?? 1;
|
|
272
273
|
}
|
|
273
|
-
catch {
|
|
274
|
-
|
|
274
|
+
catch (err) {
|
|
275
|
+
void normalizeError(err);
|
|
276
|
+
serverLogger.error("coupons-repo: getUserCouponUsageCount failed — rethrowing to prevent coupon limit bypass", { userId, couponId, error: err instanceof Error ? err.message : String(err) });
|
|
277
|
+
throw err;
|
|
275
278
|
}
|
|
276
279
|
}
|
|
277
280
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DatabaseError } from "../../../errors";
|
|
2
2
|
import { BaseRepository, prepareForFirestore, } from "../../../providers/db-firebase";
|
|
3
3
|
import { normalizeError } from "../../../errors/normalize";
|
|
4
|
+
import { serverLogger } from "../../../monitoring/server-logger";
|
|
4
5
|
import { increment } from "../../../contracts/field-ops";
|
|
5
6
|
import { SCAMMER_COLLECTION, SCAMMER_FIELDS, SCAMMER_INCIDENTS_SUBCOLLECTION, SCAMMER_COMMENTS_SUBCOLLECTION, DEFAULT_SCAMMER_DATA, } from "../schemas/firestore";
|
|
6
7
|
class ScammerRepository extends BaseRepository {
|
|
@@ -116,7 +117,9 @@ class ScammerRepository extends BaseRepository {
|
|
|
116
117
|
.get();
|
|
117
118
|
return snap.docs.map((d) => this.mapDoc(d));
|
|
118
119
|
}
|
|
119
|
-
catch {
|
|
120
|
+
catch (err) {
|
|
121
|
+
void normalizeError(err);
|
|
122
|
+
serverLogger.warn("scammer-repo: listPublicIncidents failed — returning empty", { scammerId, error: err instanceof Error ? err.message : String(err) });
|
|
120
123
|
return [];
|
|
121
124
|
}
|
|
122
125
|
}
|
|
@@ -132,7 +135,9 @@ class ScammerRepository extends BaseRepository {
|
|
|
132
135
|
.get();
|
|
133
136
|
return snap.docs.map((d) => this.mapDoc(d));
|
|
134
137
|
}
|
|
135
|
-
catch {
|
|
138
|
+
catch (err) {
|
|
139
|
+
void normalizeError(err);
|
|
140
|
+
serverLogger.warn("scammer-repo: listPublicComments failed — returning empty", { scammerId, error: err instanceof Error ? err.message : String(err) });
|
|
136
141
|
return [];
|
|
137
142
|
}
|
|
138
143
|
}
|
|
@@ -143,7 +148,9 @@ class ScammerRepository extends BaseRepository {
|
|
|
143
148
|
const results = await Promise.all(ids.slice(0, 5).map((id) => this.findById(id).catch(() => null)));
|
|
144
149
|
return results.filter((d) => d !== null && d.status === "verified");
|
|
145
150
|
}
|
|
146
|
-
catch {
|
|
151
|
+
catch (err) {
|
|
152
|
+
void normalizeError(err);
|
|
153
|
+
serverLogger.warn("scammer-repo: findManyById failed — returning empty", { ids, error: err instanceof Error ? err.message : String(err) });
|
|
147
154
|
return [];
|
|
148
155
|
}
|
|
149
156
|
}
|
package/dist/utils/media-url.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { MEDIA_ENDPOINTS } from "../constants/api-endpoints";
|
|
2
|
+
import { normalizeError } from "../errors/normalize";
|
|
2
3
|
/**
|
|
3
4
|
* Single source of truth for the Firebase Storage host literal. Every other
|
|
4
5
|
* appkit + consumer module that needs to detect or allowlist this host
|
|
@@ -31,7 +32,8 @@ export function resolveMediaUrl(url) {
|
|
|
31
32
|
}
|
|
32
33
|
return MEDIA_ENDPOINTS.EXT_URL(url);
|
|
33
34
|
}
|
|
34
|
-
catch {
|
|
35
|
+
catch (_err) {
|
|
36
|
+
void normalizeError(_err); // URL constructor throws for non-URL strings (e.g., relative paths) — return original
|
|
35
37
|
return url;
|
|
36
38
|
}
|
|
37
39
|
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* URL Validation Utilities
|
|
3
3
|
*/
|
|
4
|
+
import { normalizeError } from "../errors/normalize";
|
|
4
5
|
export function isValidUrl(url) {
|
|
5
6
|
try {
|
|
6
7
|
new URL(url);
|
|
7
8
|
return true;
|
|
8
9
|
}
|
|
9
|
-
catch {
|
|
10
|
+
catch (_err) {
|
|
11
|
+
void normalizeError(_err); // URL constructor throws TypeError for invalid URL syntax
|
|
10
12
|
return false;
|
|
11
13
|
}
|
|
12
14
|
}
|
|
@@ -17,7 +19,8 @@ export function isValidUrlWithProtocol(url, protocols = ["http", "https"]) {
|
|
|
17
19
|
const urlObj = new URL(url);
|
|
18
20
|
return protocols.includes(urlObj.protocol.replace(":", ""));
|
|
19
21
|
}
|
|
20
|
-
catch {
|
|
22
|
+
catch (_err) {
|
|
23
|
+
void normalizeError(_err); // URL constructor throws TypeError for invalid URL syntax
|
|
21
24
|
return false;
|
|
22
25
|
}
|
|
23
26
|
}
|
|
@@ -30,7 +33,8 @@ export function isExternalUrl(url, currentDomain) {
|
|
|
30
33
|
(typeof window !== "undefined" ? window.location.hostname : "");
|
|
31
34
|
return urlObj.hostname !== currentHost;
|
|
32
35
|
}
|
|
33
|
-
catch {
|
|
36
|
+
catch (_err) {
|
|
37
|
+
void normalizeError(_err); // URL constructor throws TypeError for invalid URL syntax
|
|
34
38
|
return false;
|
|
35
39
|
}
|
|
36
40
|
}
|