@mohasinac/appkit 4.53.2 → 4.54.0-next.0
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/.build-stamp.json +5 -5
- package/dist/_internal/server/features/auth/capabilities.js +7 -5
- package/dist/_internal/server/features/checkout/supplier-identity.js +28 -6
- package/dist/_internal/server/features/products/listing-tab-counts.js +81 -25
- package/dist/_internal/server/features/search/index-build/sources.js +43 -2
- package/dist/_internal/server/jobs/core/jobRunners.js +0 -10
- package/dist/_internal/server/jobs/core/onOrderCreate.js +10 -104
- package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
- package/dist/_internal/shared/actions/action-registry.js +128 -0
- package/dist/_internal/shared/features/categories/bundle-copy.d.ts +1 -1
- package/dist/_internal/shared/features/site-settings/supplier-of-record.d.ts +56 -0
- package/dist/_internal/shared/features/site-settings/supplier-of-record.js +57 -0
- package/dist/_internal/shared/listing-types/tiers.d.ts +94 -0
- package/dist/_internal/shared/listing-types/tiers.js +150 -0
- package/dist/cli/index.js +0 -2
- package/dist/client.d.ts +21 -2
- package/dist/client.js +23 -4
- package/dist/constants/api-endpoints.d.ts +21 -0
- package/dist/constants/api-endpoints.js +7 -0
- package/dist/features/account/components/UserOrdersView.js +26 -7
- package/dist/features/addresses/utils/postal-autofill.d.ts +39 -0
- package/dist/features/addresses/utils/postal-autofill.js +54 -0
- package/dist/features/admin/components/AdminStoreEditorView.js +73 -34
- package/dist/features/admin/components/OwnerQuickActions.d.ts +29 -0
- package/dist/features/admin/components/OwnerQuickActions.js +142 -0
- package/dist/features/admin/schemas/admin-editor-forms.d.ts +1 -1
- package/dist/features/admin/schemas/admin-editor-forms.js +4 -1
- package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +11 -1
- package/dist/features/auctions/components/MarketplaceAuctionCard.js +9 -2
- package/dist/features/auth/permissions/constants.d.ts +29 -2
- package/dist/features/auth/permissions/constants.js +68 -1
- package/dist/features/blog/components/BlogFeaturedCard.d.ts +11 -1
- package/dist/features/blog/components/BlogFeaturedCard.js +9 -2
- package/dist/features/cart/components/CartDrawer.js +3 -2
- package/dist/features/cart/components/CartGroupLineRow.js +2 -1
- package/dist/features/cart/hooks/useAddToCart.js +10 -5
- package/dist/features/cart/utils/pending-ops.d.ts +11 -1
- package/dist/features/cart/utils/pending-ops.js +14 -2
- package/dist/features/categories/components/BrandDetailPageView.js +2 -1
- package/dist/features/categories/components/CategoryDetailPageView.js +2 -1
- package/dist/features/categories/components/CategoryGrid.d.ts +11 -1
- package/dist/features/categories/components/CategoryGrid.js +9 -2
- package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +10 -5
- package/dist/features/checkout/actions/checkout-value-otp-actions.js +2 -88
- package/dist/features/events/components/EventCard.d.ts +11 -1
- package/dist/features/events/components/EventCard.js +9 -2
- package/dist/features/layout/AppLayoutShell.js +2 -1
- package/dist/features/layout/BottomActions.d.ts +1 -0
- package/dist/features/layout/BottomActions.js +330 -0
- package/dist/features/layout/BottomChrome.d.ts +45 -0
- package/dist/features/layout/BottomChrome.js +5 -17
- package/dist/features/layout/SideActionDock.js +36 -13
- package/dist/features/layout/index.d.ts +2 -7
- package/dist/features/layout/index.js +7 -7
- package/dist/features/layout/useIsDesktop.d.ts +36 -0
- package/dist/features/layout/useIsDesktop.js +48 -0
- package/dist/features/media/messages/en.json +67 -67
- package/dist/features/orders/components/YourPurchaseStrip.d.ts +24 -0
- package/dist/features/orders/components/YourPurchaseStrip.js +77 -0
- package/dist/features/orders/components/index.d.ts +2 -1
- package/dist/features/orders/components/index.js +1 -1
- package/dist/features/orders/utils/order-announcement-message.d.ts +18 -0
- package/dist/features/orders/utils/order-announcement-message.js +16 -0
- package/dist/features/orders/utils/payment-proof-message.d.ts +20 -0
- package/dist/features/orders/utils/payment-proof-message.js +17 -0
- package/dist/features/parts/schemas/firestore.d.ts +227 -0
- package/dist/features/parts/schemas/firestore.js +6 -6
- package/dist/features/pre-orders/components/MarketplacePreorderCard.d.ts +11 -1
- package/dist/features/pre-orders/components/MarketplacePreorderCard.js +10 -3
- package/dist/features/products/api/[id]/route.js +8 -1
- package/dist/features/products/api/route.js +16 -4
- package/dist/features/products/components/AvailabilityScopeHint.js +1 -1
- package/dist/features/products/components/InteractiveProductCard.d.ts +7 -1
- package/dist/features/products/components/InteractiveProductCard.js +16 -4
- package/dist/features/products/components/MarketplaceBundleCard.d.ts +11 -1
- package/dist/features/products/components/MarketplaceBundleCard.js +9 -2
- package/dist/features/products/components/MarketplacePrizeDrawCard.d.ts +11 -1
- package/dist/features/products/components/MarketplacePrizeDrawCard.js +9 -2
- package/dist/features/products/components/ProductDetailPageView.js +27 -2
- package/dist/features/products/components/ProductGrid.d.ts +14 -1
- package/dist/features/products/components/ProductGrid.js +22 -5
- package/dist/features/products/components/ProductsIndexListing.js +2 -2
- package/dist/features/reviews/api/[id]/route.js +83 -23
- package/dist/features/reviews/components/PurchaseReviewAffordance.d.ts +6 -0
- package/dist/features/reviews/components/PurchaseReviewAffordance.js +42 -0
- package/dist/features/reviews/components/ReviewFormDrawer.d.ts +34 -0
- package/dist/features/reviews/components/ReviewFormDrawer.js +127 -0
- package/dist/features/reviews/components/index.d.ts +4 -0
- package/dist/features/reviews/components/index.js +2 -0
- package/dist/features/seller/actions/seller-actions.d.ts +1 -1
- package/dist/features/seller/actions/seller-actions.js +49 -3
- package/dist/features/seller/components/SellerPayoutSettingsView.js +30 -2
- package/dist/features/seller/components/SellerProductShell.d.ts +17 -1
- package/dist/features/seller/hooks/useStoreListingEntitlements.d.ts +8 -0
- package/dist/features/seller/hooks/useStoreListingEntitlements.js +70 -0
- package/dist/features/stores/components/InteractiveStoreCard.d.ts +11 -1
- package/dist/features/stores/components/InteractiveStoreCard.js +9 -2
- package/dist/features/stores/components/index.d.ts +0 -2
- package/dist/features/stores/components/index.js +0 -1
- package/dist/features/stores/schemas/index.d.ts +1 -1
- package/dist/features/stores/schemas/index.js +10 -1
- package/dist/features/tester/seed-data/tester-checklist-seed-data.js +403 -0
- package/dist/index.d.ts +8 -27
- package/dist/index.js +14 -35
- package/dist/next/routing/route-map.d.ts +2 -0
- package/dist/next/routing/route-map.js +9 -0
- package/dist/providers/shipping-shiprocket/index.d.ts +98 -0
- package/dist/providers/shipping-shiprocket/index.js +353 -0
- package/dist/schemas/registry.d.ts +2 -2
- package/dist/security/csp.d.ts +16 -1
- package/dist/security/csp.js +5 -3
- package/dist/seed/_helpers/category-forest.js +27 -0
- package/dist/seed/_helpers/category-parts-glossary-map.js +1816 -86
- package/dist/seed/_helpers/category-parts.d.ts +1 -1
- package/dist/seed/_helpers/category-parts.js +13412 -3020
- package/dist/seed/categories-seed-data.js +48 -6
- package/dist/seed/homepage-sections-seed-data.js +41 -28
- package/dist/seed/model-composition-seed-data.js +6 -8
- package/dist/seed/part-glossary/chunk0.d.ts +9 -0
- package/dist/seed/part-glossary/chunk0.js +35044 -0
- package/dist/seed/part-glossary/chunk1.d.ts +9 -0
- package/dist/seed/part-glossary/chunk1.js +39955 -0
- package/dist/seed/part-glossary/chunk2.d.ts +9 -0
- package/dist/seed/part-glossary/chunk2.js +50335 -0
- package/dist/seed/part-glossary/chunk3.d.ts +9 -0
- package/dist/seed/part-glossary/chunk3.js +52386 -0
- package/dist/seed/part-glossary/chunk4.d.ts +9 -0
- package/dist/seed/part-glossary/chunk4.js +50549 -0
- package/dist/seed/part-glossary-seed-data.d.ts +1 -1
- package/dist/seed/part-glossary-seed-data.js +23 -47483
- package/dist/server.d.ts +4 -8
- package/dist/server.js +8 -20
- package/dist/styles.css +516 -358
- package/dist/tailwind-utilities.css +1 -1
- package/dist/tokens/themes/index.d.ts +3 -1
- package/dist/tokens/themes/index.js +11 -1
- package/dist/tokens/themes/technical-dark.d.ts +30 -0
- package/dist/tokens/themes/technical-dark.js +103 -0
- package/dist/tokens/themes/technical-light.d.ts +22 -0
- package/dist/tokens/themes/technical-light.js +89 -0
- package/dist/tokens/tokens.css +155 -0
- package/dist/ui/components/GatedPrice.js +30 -3
- package/dist/ui/components/GatedPrice.style.css +22 -19
- package/dist/ui/components/QuickCreateModal.style.css +78 -78
- package/dist/ui/forms/FormShell.style.css +260 -260
- package/package.json +1 -1
- package/dist/_internal/server/jobs/core/whatsappCatalogImport.d.ts +0 -27
- package/dist/_internal/server/jobs/core/whatsappCatalogImport.js +0 -172
- package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +0 -35
- package/dist/_internal/server/jobs/core/whatsappNotify.js +0 -140
- package/dist/features/admin/components/AdminWhatsAppGuideView.d.ts +0 -2
- package/dist/features/admin/components/AdminWhatsAppGuideView.js +0 -35
- package/dist/features/orders/components/MarketplaceOrderCard.d.ts +0 -36
- package/dist/features/orders/components/MarketplaceOrderCard.js +0 -57
- package/dist/features/stores/components/StoreWhatsAppGuideView.d.ts +0 -3
- package/dist/features/stores/components/StoreWhatsAppGuideView.js +0 -12
- package/dist/features/whatsapp-bot/api/send-status/route.d.ts +0 -12
- package/dist/features/whatsapp-bot/api/send-status/route.js +0 -38
- package/dist/features/whatsapp-bot/api/webhook/route.d.ts +0 -12
- package/dist/features/whatsapp-bot/api/webhook/route.js +0 -43
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.d.ts +0 -6
- package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +0 -270
- package/dist/features/whatsapp-bot/components/WhatsAppChatButton.d.ts +0 -9
- package/dist/features/whatsapp-bot/components/WhatsAppChatButton.js +0 -7
- package/dist/features/whatsapp-bot/components/index.d.ts +0 -3
- package/dist/features/whatsapp-bot/components/index.js +0 -2
- package/dist/features/whatsapp-bot/helpers/payment-proof-message.d.ts +0 -14
- package/dist/features/whatsapp-bot/helpers/payment-proof-message.js +0 -17
- package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +0 -52
- package/dist/features/whatsapp-bot/helpers/whatsapp.js +0 -283
- package/dist/features/whatsapp-bot/index.d.ts +0 -3
- package/dist/features/whatsapp-bot/index.js +0 -3
- package/dist/features/whatsapp-bot/manifest.d.ts +0 -2
- package/dist/features/whatsapp-bot/manifest.js +0 -13
- package/dist/features/whatsapp-bot/messages/en.json +0 -7
- package/dist/features/whatsapp-bot/server.d.ts +0 -7
- package/dist/features/whatsapp-bot/server.js +0 -7
- package/dist/features/whatsapp-bot/types/index.d.ts +0 -126
- package/dist/features/whatsapp-bot/types/index.js +0 -1
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mohasinac/shipping-shiprocket — IShippingProvider over Shiprocket's v1 API.
|
|
3
|
+
*
|
|
4
|
+
* ── Read this before trusting it ──────────────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* ── What is verified, and what is not ────────────────────────────────────
|
|
7
|
+
*
|
|
8
|
+
* ✅ VERIFIED against the live API (2026-10-20, real API-user credentials):
|
|
9
|
+
* `POST /v1/external/auth/login` returns HTTP 200 with
|
|
10
|
+
* `{ company_id, created_at, email, first_name, id, last_name, token }`.
|
|
11
|
+
* The `token` field is present and non-empty, which is the only field
|
|
12
|
+
* `loginResponse` requires — the rest are ignored, as Zod ignores unknown
|
|
13
|
+
* keys by default.
|
|
14
|
+
*
|
|
15
|
+
* 🛑 NOT VERIFIED: every other endpoint. Order creation, AWB assignment,
|
|
16
|
+
* tracking, serviceability and label generation all MUTATE or bill against
|
|
17
|
+
* a real carrier account, so none was called. Their field names below are
|
|
18
|
+
* claims taken from the published docs, not facts read off a payload.
|
|
19
|
+
*
|
|
20
|
+
* That asymmetry is the reason every response is parsed through Zod rather
|
|
21
|
+
* than cast: an unverified contract that is wrong surfaces as a named error at
|
|
22
|
+
* the boundary, instead of as an `undefined` AWB written to an order and shown
|
|
23
|
+
* to a buyer as a tracking number that tracks nothing.
|
|
24
|
+
*
|
|
25
|
+
* ── Auth: why a dedicated API user, and why the token is cached ───────────
|
|
26
|
+
*
|
|
27
|
+
* Shiprocket's core v1 API has no long-lived key/secret. You POST credentials
|
|
28
|
+
* to `/v1/external/auth/login` and receive a bearer token valid for roughly
|
|
29
|
+
* ten days, then log in again.
|
|
30
|
+
*
|
|
31
|
+
* 🛑 Those credentials should be a DEDICATED API USER (Shiprocket dashboard →
|
|
32
|
+
* Settings → API → Configure), not the dashboard login. Using the dashboard
|
|
33
|
+
* account means the token dies whenever someone changes that password — a
|
|
34
|
+
* shipping integration that breaks on an unrelated admin action — and it hands
|
|
35
|
+
* full account scope to this process. The env var is named
|
|
36
|
+
* `SHIPROCKET_LOGIN_USER_EMAIL` rather than `LOGIN_ID` because the login payload
|
|
37
|
+
* field is `email`; a var called "login id" invites someone to put a username
|
|
38
|
+
* in it and get a 400 they cannot explain.
|
|
39
|
+
*
|
|
40
|
+
* 🛑 The token is cached in module state with its expiry. Logging in per
|
|
41
|
+
* request would mean two round trips for every shipment and would hit
|
|
42
|
+
* Shiprocket's auth rate limit under any real volume. A 401 mid-flight clears
|
|
43
|
+
* the cache and retries ONCE — exactly once, because a credential that is
|
|
44
|
+
* genuinely wrong must fail fast rather than loop.
|
|
45
|
+
*
|
|
46
|
+
* ── What is deliberately NOT here ────────────────────────────────────────
|
|
47
|
+
*
|
|
48
|
+
* Inbound webhooks. Shiprocket pushes status updates to a URL configured in
|
|
49
|
+
* its dashboard and authenticates them with an `x-api-key` header whose value
|
|
50
|
+
* you set there — a THIRD secret, and an inbound route rather than a provider
|
|
51
|
+
* method. `trackShipment` below polls instead, which is correct for a provider
|
|
52
|
+
* contract and wasteful as a status mechanism; the webhook is the better
|
|
53
|
+
* answer and belongs in its own route with its own verification.
|
|
54
|
+
*
|
|
55
|
+
* @tag domain:shipping
|
|
56
|
+
* @tag layer:providers
|
|
57
|
+
* @tag access:server-only
|
|
58
|
+
*/
|
|
59
|
+
import { z } from "zod";
|
|
60
|
+
import { IShippingProvider } from "../../contracts/index.js";
|
|
61
|
+
import { ApiError } from "../../errors/index.js";
|
|
62
|
+
import { normalizeError } from "../../errors/normalize.js";
|
|
63
|
+
import { serverLogger } from "../../monitoring/server-logger.js";
|
|
64
|
+
const API_BASE = "https://apiv2.shiprocket.in/v1/external";
|
|
65
|
+
// ── Response contracts ──────────────────────────────────────────────────────
|
|
66
|
+
//
|
|
67
|
+
// Every one of these is a CLAIM about a payload nobody here has seen. Parsing
|
|
68
|
+
// rather than casting is what turns a wrong claim into a named error.
|
|
69
|
+
const loginResponse = z.object({
|
|
70
|
+
token: z.string().min(1),
|
|
71
|
+
});
|
|
72
|
+
const createOrderResponse = z.object({
|
|
73
|
+
order_id: z.union([z.number(), z.string()]),
|
|
74
|
+
shipment_id: z.union([z.number(), z.string()]),
|
|
75
|
+
/** Absent until an AWB is assigned — which is a separate call. */
|
|
76
|
+
awb_code: z.string().nullish(),
|
|
77
|
+
courier_name: z.string().nullish(),
|
|
78
|
+
status: z.string().nullish(),
|
|
79
|
+
});
|
|
80
|
+
const trackingResponse = z.object({
|
|
81
|
+
tracking_data: z.object({
|
|
82
|
+
shipment_status: z.union([z.number(), z.string()]).nullish(),
|
|
83
|
+
shipment_track: z
|
|
84
|
+
.array(z.object({
|
|
85
|
+
current_status: z.string().nullish(),
|
|
86
|
+
delivered_date: z.string().nullish(),
|
|
87
|
+
edd: z.string().nullish(),
|
|
88
|
+
}))
|
|
89
|
+
.nullish(),
|
|
90
|
+
shipment_track_activities: z
|
|
91
|
+
.array(z.object({
|
|
92
|
+
date: z.string().nullish(),
|
|
93
|
+
status: z.string().nullish(),
|
|
94
|
+
activity: z.string().nullish(),
|
|
95
|
+
location: z.string().nullish(),
|
|
96
|
+
}))
|
|
97
|
+
.nullish(),
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
const serviceabilityResponse = z.object({
|
|
101
|
+
data: z.object({
|
|
102
|
+
available_courier_companies: z
|
|
103
|
+
.array(z.object({
|
|
104
|
+
courier_name: z.string(),
|
|
105
|
+
estimated_delivery_days: z.union([z.number(), z.string()]).nullish(),
|
|
106
|
+
rate: z.union([z.number(), z.string()]).nullish(),
|
|
107
|
+
}))
|
|
108
|
+
.nullish(),
|
|
109
|
+
}),
|
|
110
|
+
});
|
|
111
|
+
function num(v, fallback = 0) {
|
|
112
|
+
const n = typeof v === "string" ? Number.parseFloat(v) : v;
|
|
113
|
+
return typeof n === "number" && Number.isFinite(n) ? n : fallback;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Parse an upstream response, or throw a named contract error.
|
|
117
|
+
*
|
|
118
|
+
* 🛑 `.parse()`, not `.safeParse()`. The only branch on failure is to throw,
|
|
119
|
+
* and a `safeParse` whose `.success` is checked once and converted straight
|
|
120
|
+
* into a throw is indistinguishable — to a reader and to
|
|
121
|
+
* `audit-unvalidated-safeparse` — from a result nobody handled.
|
|
122
|
+
*
|
|
123
|
+
* 🛑 Logs the FAILING PATHS, never the payload. A Shiprocket response carries
|
|
124
|
+
* the buyer's name, phone and full address, and an error log is the wrong
|
|
125
|
+
* place for all three.
|
|
126
|
+
*/
|
|
127
|
+
function parseUpstream(contract, json, what) {
|
|
128
|
+
try {
|
|
129
|
+
return contract.parse(json);
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
const issues = err instanceof z.ZodError
|
|
133
|
+
? err.issues.map((i) => i.path.join("."))
|
|
134
|
+
: [];
|
|
135
|
+
serverLogger.error("shiprocket: response contract mismatch", {
|
|
136
|
+
what,
|
|
137
|
+
issues,
|
|
138
|
+
});
|
|
139
|
+
throw new ApiError(502, `Shiprocket ${what} returned an unexpected shape.`, {
|
|
140
|
+
code: "SHIPROCKET_CONTRACT_MISMATCH",
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export class ShiprocketShippingProvider extends IShippingProvider {
|
|
145
|
+
constructor(config) {
|
|
146
|
+
super();
|
|
147
|
+
this.config = config;
|
|
148
|
+
this.name = "shiprocket";
|
|
149
|
+
this.token = null;
|
|
150
|
+
this.tokenExpiresAt = 0;
|
|
151
|
+
/*
|
|
152
|
+
* 🛑 Refuses at CONSTRUCTION, not at first use. `providers.config.ts`
|
|
153
|
+
* already argues this: a degraded shipping provider hands back a synthetic
|
|
154
|
+
* tracking id for a parcel no carrier was told about, so the seller sees an
|
|
155
|
+
* AWB-shaped string and a success toast while the buyer gets a number that
|
|
156
|
+
* tracks nothing. A loud failure at boot is attributable and recoverable by
|
|
157
|
+
* flipping one setting; the silent version is neither.
|
|
158
|
+
*/
|
|
159
|
+
if (!config.email?.trim() || !config.password?.trim()) {
|
|
160
|
+
throw new Error("[shiprocket] SHIPROCKET_LOGIN_USER_EMAIL / SHIPROCKET_API_PASSWORD are empty. " +
|
|
161
|
+
"Create a dedicated API user in Shiprocket (Settings -> API -> Configure) " +
|
|
162
|
+
"and set both, or set siteSettings.shipping.provider back to 'manual'. " +
|
|
163
|
+
"This refuses rather than degrading because a fallback would return a " +
|
|
164
|
+
"synthetic tracking id for a parcel that was never booked.");
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* A bearer token, logging in only when the cached one is absent or stale.
|
|
169
|
+
*
|
|
170
|
+
* The 60-second safety margin matters: a token that expires mid-request
|
|
171
|
+
* produces a 401 the caller has to interpret, and shaving a minute off a
|
|
172
|
+
* ten-day lifetime costs nothing.
|
|
173
|
+
*/
|
|
174
|
+
async getToken(force = false) {
|
|
175
|
+
if (!force && this.token && Date.now() < this.tokenExpiresAt - 60000) {
|
|
176
|
+
return this.token;
|
|
177
|
+
}
|
|
178
|
+
const res = await fetch(`${API_BASE}/auth/login`, {
|
|
179
|
+
method: "POST",
|
|
180
|
+
headers: { "Content-Type": "application/json" },
|
|
181
|
+
body: JSON.stringify({
|
|
182
|
+
email: this.config.email,
|
|
183
|
+
password: this.config.password,
|
|
184
|
+
}),
|
|
185
|
+
});
|
|
186
|
+
if (!res.ok) {
|
|
187
|
+
throw new ApiError(502, `Shiprocket auth failed (${res.status}). Check the API user's credentials.`, { code: "SHIPROCKET_AUTH_FAILED" });
|
|
188
|
+
}
|
|
189
|
+
const parsed = parseUpstream(loginResponse, await res.json(), "auth/login");
|
|
190
|
+
this.token = parsed.token;
|
|
191
|
+
// Their docs say ~10 days; treated as 9 so a boundary never surprises us.
|
|
192
|
+
this.tokenExpiresAt = Date.now() + 9 * 24 * 60 * 60 * 1000;
|
|
193
|
+
return this.token;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* One authenticated call, with a single re-auth on 401.
|
|
197
|
+
*
|
|
198
|
+
* 🛑 Exactly one retry. A token that expired early is worth one more attempt;
|
|
199
|
+
* a credential that is wrong must fail rather than loop, because a retry loop
|
|
200
|
+
* against an auth endpoint is how an integration gets rate-limited and then
|
|
201
|
+
* looks like an outage.
|
|
202
|
+
*/
|
|
203
|
+
async call(path, init, contract) {
|
|
204
|
+
const attempt = async (force) => {
|
|
205
|
+
const token = await this.getToken(force);
|
|
206
|
+
return fetch(`${API_BASE}${path}`, {
|
|
207
|
+
...init,
|
|
208
|
+
headers: {
|
|
209
|
+
"Content-Type": "application/json",
|
|
210
|
+
Authorization: `Bearer ${token}`,
|
|
211
|
+
...(init.headers ?? {}),
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
let res = await attempt(false);
|
|
216
|
+
if (res.status === 401) {
|
|
217
|
+
this.token = null;
|
|
218
|
+
res = await attempt(true);
|
|
219
|
+
}
|
|
220
|
+
if (!res.ok) {
|
|
221
|
+
throw new ApiError(502, `Shiprocket ${path} failed (${res.status}).`, { code: "SHIPROCKET_REQUEST_FAILED" });
|
|
222
|
+
}
|
|
223
|
+
const json = await res.json();
|
|
224
|
+
return parseUpstream(contract, json, path);
|
|
225
|
+
}
|
|
226
|
+
/** Their order payload wants the address flattened across many keys. */
|
|
227
|
+
addressFields(prefix, a) {
|
|
228
|
+
const p = prefix === "billing" ? "billing" : "shipping";
|
|
229
|
+
return {
|
|
230
|
+
[`${p}_customer_name`]: a.name,
|
|
231
|
+
[`${p}_address`]: a.addressLine1,
|
|
232
|
+
[`${p}_address_2`]: a.addressLine2 ?? "",
|
|
233
|
+
[`${p}_city`]: a.city,
|
|
234
|
+
[`${p}_pincode`]: a.pincode,
|
|
235
|
+
[`${p}_state`]: a.state,
|
|
236
|
+
[`${p}_country`]: a.country,
|
|
237
|
+
[`${p}_phone`]: a.phone,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
async createShipment(data) {
|
|
241
|
+
const body = {
|
|
242
|
+
order_id: data.orderId,
|
|
243
|
+
order_date: new Date().toISOString().slice(0, 10),
|
|
244
|
+
pickup_location: this.config.pickupLocation ?? "Primary",
|
|
245
|
+
...this.addressFields("billing", data.delivery),
|
|
246
|
+
shipping_is_billing: true,
|
|
247
|
+
/*
|
|
248
|
+
* Shiprocket requires at least one order item. We do not receive the
|
|
249
|
+
* line items on this contract — `CreateShipmentInput` carries only
|
|
250
|
+
* dimensions and the two addresses — so a single synthetic line stands
|
|
251
|
+
* for the parcel. 🛑 This is a known gap, not a simplification: a real
|
|
252
|
+
* integration needs per-item HSN and value for a commercial invoice, and
|
|
253
|
+
* that means widening the shared contract rather than inventing values
|
|
254
|
+
* here.
|
|
255
|
+
*/
|
|
256
|
+
order_items: [
|
|
257
|
+
{
|
|
258
|
+
name: `Order ${data.orderId}`,
|
|
259
|
+
sku: data.orderId,
|
|
260
|
+
units: 1,
|
|
261
|
+
selling_price: data.codAmount ?? 0,
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
payment_method: data.isCod ? "COD" : "Prepaid",
|
|
265
|
+
sub_total: data.codAmount ?? 0,
|
|
266
|
+
length: data.dimensions.length,
|
|
267
|
+
breadth: data.dimensions.width,
|
|
268
|
+
height: data.dimensions.height,
|
|
269
|
+
weight: data.dimensions.weight,
|
|
270
|
+
};
|
|
271
|
+
const out = await this.call("/orders/create/adhoc", { method: "POST", body: JSON.stringify(body) }, createOrderResponse);
|
|
272
|
+
return {
|
|
273
|
+
id: String(out.shipment_id),
|
|
274
|
+
/*
|
|
275
|
+
* 🛑 Falls back to the shipment id when no AWB has been assigned yet —
|
|
276
|
+
* assignment is a separate call. It does NOT invent a `manual_…` string:
|
|
277
|
+
* a synthetic tracking number that a buyer can paste into a courier site
|
|
278
|
+
* and get nothing from is the failure mode this whole provider is
|
|
279
|
+
* supposed to avoid.
|
|
280
|
+
*/
|
|
281
|
+
trackingId: out.awb_code?.trim() || String(out.shipment_id),
|
|
282
|
+
orderId: data.orderId,
|
|
283
|
+
status: out.status ?? "created",
|
|
284
|
+
courier: out.courier_name ?? undefined,
|
|
285
|
+
createdAt: new Date().toISOString(),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
async trackShipment(trackingId) {
|
|
289
|
+
const out = await this.call(`/courier/track/awb/${encodeURIComponent(trackingId)}`, { method: "GET" }, trackingResponse);
|
|
290
|
+
const t = out.tracking_data;
|
|
291
|
+
const head = t.shipment_track?.[0];
|
|
292
|
+
return {
|
|
293
|
+
trackingId,
|
|
294
|
+
currentStatus: head?.current_status ?? String(t.shipment_status ?? "unknown"),
|
|
295
|
+
estimatedDelivery: head?.edd ?? undefined,
|
|
296
|
+
events: (t.shipment_track_activities ?? []).map((e) => ({
|
|
297
|
+
status: e.status ?? "",
|
|
298
|
+
location: e.location ?? "",
|
|
299
|
+
timestamp: e.date ?? "",
|
|
300
|
+
description: e.activity ?? undefined,
|
|
301
|
+
})),
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
async cancelShipment(shipmentId) {
|
|
305
|
+
await this.call("/orders/cancel", { method: "POST", body: JSON.stringify({ ids: [shipmentId] }) },
|
|
306
|
+
// Their cancel returns a message envelope we do not read; accepting any
|
|
307
|
+
// object keeps us from asserting a shape we have not seen.
|
|
308
|
+
z.object({}).passthrough());
|
|
309
|
+
}
|
|
310
|
+
async checkServiceability(pincode, weight) {
|
|
311
|
+
/*
|
|
312
|
+
* 🛑 `pickup_postcode` is required by their API and we do not have it on
|
|
313
|
+
* this contract. Taken from the registered pickup location's name is NOT
|
|
314
|
+
* possible — it is a nickname, not a pincode — so this returns
|
|
315
|
+
* "unserviceable" rather than guessing a origin pincode, which would
|
|
316
|
+
* produce confidently wrong rates.
|
|
317
|
+
*/
|
|
318
|
+
const origin = process.env.SHIPROCKET_PICKUP_PINCODE?.trim();
|
|
319
|
+
if (!origin) {
|
|
320
|
+
serverLogger.warn("shiprocket: SHIPROCKET_PICKUP_PINCODE unset — serviceability cannot be checked", { pincode });
|
|
321
|
+
return { isServiceable: false, couriers: [] };
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
const out = await this.call(`/courier/serviceability/?pickup_postcode=${encodeURIComponent(origin)}` +
|
|
325
|
+
`&delivery_postcode=${encodeURIComponent(pincode)}` +
|
|
326
|
+
`&weight=${encodeURIComponent(String(weight))}&cod=0`, { method: "GET" }, serviceabilityResponse);
|
|
327
|
+
const couriers = (out.data.available_courier_companies ?? []).map((c) => ({
|
|
328
|
+
name: c.courier_name,
|
|
329
|
+
estimatedDays: num(c.estimated_delivery_days),
|
|
330
|
+
rate: num(c.rate),
|
|
331
|
+
}));
|
|
332
|
+
return { isServiceable: couriers.length > 0, couriers };
|
|
333
|
+
}
|
|
334
|
+
catch (err) {
|
|
335
|
+
void normalizeError(err);
|
|
336
|
+
/*
|
|
337
|
+
* Serviceability is a QUOTE, not a write. Failing closed here means the
|
|
338
|
+
* checkout says "we cannot ship there" on a transient carrier outage,
|
|
339
|
+
* which is wrong in the safe direction — the alternative is quoting a
|
|
340
|
+
* rate we cannot honour.
|
|
341
|
+
*/
|
|
342
|
+
return { isServiceable: false, couriers: [] };
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
async generateLabel(shipmentId) {
|
|
346
|
+
const out = await this.call("/courier/generate/label", { method: "POST", body: JSON.stringify({ shipment_id: [shipmentId] }) }, z.object({ label_url: z.string().url() }));
|
|
347
|
+
const res = await fetch(out.label_url);
|
|
348
|
+
if (!res.ok) {
|
|
349
|
+
throw new ApiError(502, `Shiprocket label download failed (${res.status}).`, { code: "SHIPROCKET_LABEL_FAILED" });
|
|
350
|
+
}
|
|
351
|
+
return res.arrayBuffer();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
@@ -4097,7 +4097,7 @@ export declare const SCHEMAS: {
|
|
|
4097
4097
|
totalReviews: number;
|
|
4098
4098
|
averageRating?: number | undefined;
|
|
4099
4099
|
}>>;
|
|
4100
|
-
capabilities: import("zod").ZodOptional<import("zod").ZodArray<import("zod").
|
|
4100
|
+
capabilities: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodEnum<[string, ...string[]]>, "many">>;
|
|
4101
4101
|
customCommissionRate: import("zod").ZodOptional<import("zod").ZodNumber>;
|
|
4102
4102
|
googleReviews: import("zod").ZodOptional<import("zod").ZodObject<{
|
|
4103
4103
|
placeId: import("zod").ZodString;
|
|
@@ -5602,7 +5602,7 @@ export declare const SCHEMAS: {
|
|
|
5602
5602
|
isVerified: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
5603
5603
|
isFeatured: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
5604
5604
|
suspensionReason: import("zod").ZodOptional<import("zod").ZodString>;
|
|
5605
|
-
capabilities: import("zod").ZodOptional<import("zod").ZodArray<import("zod").
|
|
5605
|
+
capabilities: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodEnum<[string, ...string[]]>, "many">>;
|
|
5606
5606
|
adminNotes: import("zod").ZodOptional<import("zod").ZodString>;
|
|
5607
5607
|
}, "strip", import("zod").ZodTypeAny, {
|
|
5608
5608
|
storeStatus: import("../features/stores").StoreStatus;
|
package/dist/security/csp.d.ts
CHANGED
|
@@ -12,5 +12,20 @@
|
|
|
12
12
|
*/
|
|
13
13
|
/** Generate a 16-byte base64 nonce for a single request. */
|
|
14
14
|
export declare function generateNonce(): string;
|
|
15
|
+
export interface BuildCSPOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Add `'wasm-unsafe-eval'` to `script-src`, permitting
|
|
18
|
+
* `WebAssembly.compile`/`instantiate` from bytes.
|
|
19
|
+
*
|
|
20
|
+
* Required by any page that compiles WebAssembly at runtime — without it the
|
|
21
|
+
* production policy (`script-src 'self' 'nonce-…'`) blocks compilation
|
|
22
|
+
* outright, because WebAssembly compilation counts as script evaluation.
|
|
23
|
+
*
|
|
24
|
+
* Narrower than `'unsafe-eval'` on purpose: it unblocks WASM only and still
|
|
25
|
+
* forbids `eval` and `new Function` for JavaScript. Default `false`, so
|
|
26
|
+
* existing consumers are unaffected.
|
|
27
|
+
*/
|
|
28
|
+
wasm?: boolean;
|
|
29
|
+
}
|
|
15
30
|
/** Build the full CSP header value for the given nonce. */
|
|
16
|
-
export declare function buildCSP(nonce: string): string;
|
|
31
|
+
export declare function buildCSP(nonce: string, options?: BuildCSPOptions): string;
|
package/dist/security/csp.js
CHANGED
|
@@ -17,13 +17,15 @@ export function generateNonce() {
|
|
|
17
17
|
return btoa(String.fromCharCode(...bytes));
|
|
18
18
|
}
|
|
19
19
|
/** Build the full CSP header value for the given nonce. */
|
|
20
|
-
export function buildCSP(nonce) {
|
|
20
|
+
export function buildCSP(nonce, options = {}) {
|
|
21
21
|
const isDev = process.env.NODE_ENV === "development";
|
|
22
|
+
const wasm = options.wasm === true ? " 'wasm-unsafe-eval'" : "";
|
|
22
23
|
return [
|
|
23
24
|
"default-src 'self'",
|
|
24
25
|
isDev
|
|
25
|
-
?
|
|
26
|
-
|
|
26
|
+
? // 'unsafe-eval' already subsumes 'wasm-unsafe-eval' in dev.
|
|
27
|
+
"script-src 'self' 'unsafe-eval' 'unsafe-inline'"
|
|
28
|
+
: `script-src 'self' 'nonce-${nonce}'${wasm}`,
|
|
27
29
|
"style-src 'self' 'unsafe-inline'",
|
|
28
30
|
"img-src 'self' data: https: blob:",
|
|
29
31
|
"font-src 'self' data:",
|
|
@@ -174,6 +174,13 @@ const BEYBLADE_ORIGINAL = {
|
|
|
174
174
|
node("category-original-attack-rings", "Attack Rings", "The upper contact ring — what actually strikes an opponent, and the part that decides a combo's attack profile.", partDefaults("category-original-attack-rings", "Attack Ring"), PART_TEMPLATES),
|
|
175
175
|
node("category-original-weight-disks", "Weight Disks", "The metal disk between ring and base. Heavier disks buy stamina; lighter ones buy speed.", partDefaults("category-original-weight-disks", "Weight Disk"), PART_TEMPLATES),
|
|
176
176
|
node("category-original-blade-bases", "Blade Bases", "The bottom assembly — tip shape, spin gear housing and, on later systems, the magnet or engine.", partDefaults("category-original-blade-bases", "Blade Base"), PART_TEMPLATES),
|
|
177
|
+
/*
|
|
178
|
+
* 🛑 Support Parts had been keyed on THIS BRANCH, which takes explicit
|
|
179
|
+
* children and so never reads `PART_LEAVES` — its 3 leaves were dropped
|
|
180
|
+
* silently. Same cause as the 82 CX leaves under `category-x-custom-cx`;
|
|
181
|
+
* see the note there.
|
|
182
|
+
*/
|
|
183
|
+
node("category-original-support-parts", "Support Parts", "Bolt-on extras that fill no stack position of their own — Cross Survivor, Reverse Attack and the other SP pieces.", partDefaults("category-original-support-parts", "Support Parts"), PART_TEMPLATES),
|
|
177
184
|
/*
|
|
178
185
|
* D13 — five part-type nodes the forest was missing, so every one of the
|
|
179
186
|
* 20 part classifications has a home. Measured before adding: 135 of 753
|
|
@@ -345,6 +352,26 @@ const BEYBLADE_X = {
|
|
|
345
352
|
node("category-x-blades", "Blades", "The upper piece that defines an X combo's attack shape and its ability to hold the rail.", partDefaults("category-x-blades", "Blade"), PART_TEMPLATES),
|
|
346
353
|
node("category-x-ratchets", "Ratchets", "The middle component. Written as protrusions-height — 3-60, 4-60, 9-60 — and the fastest lever on a build.", partDefaults("category-x-ratchets", "Ratchet"), PART_TEMPLATES),
|
|
347
354
|
node("category-x-bits", "Bits", "The floor-contact tip, the X-format equivalent of a Driver.", partDefaults("category-x-bits", "Bit"), PART_TEMPLATES),
|
|
355
|
+
/*
|
|
356
|
+
* 🛑 CX PARTS NEED THEIR OWN NODES, and their absence cost 82 leaves.
|
|
357
|
+
*
|
|
358
|
+
* `PART_SLOT_CATEGORY` pointed all five Custom Line slots at
|
|
359
|
+
* `category-x-custom-cx` — which is a `line()`, so it attaches
|
|
360
|
+
* `MODEL_LEAVES[id]` and never reads `PART_LEAVES[id]` at all. The
|
|
361
|
+
* generated map held 82 leaves under that key and every one of them was
|
|
362
|
+
* silently dropped: not mis-tiered, absent. Together with the 3 under
|
|
363
|
+
* `branch("category-original-parts")` that is exactly the 85-leaf
|
|
364
|
+
* shortfall the tier-4 guard had been reporting as a bare number.
|
|
365
|
+
*
|
|
366
|
+
* One node per classification, matching every other generation here —
|
|
367
|
+
* an 82-leaf grab-bag under a single "CX Parts" node would also have
|
|
368
|
+
* satisfied the count while being the wrong shelf for a buyer.
|
|
369
|
+
*/
|
|
370
|
+
node("category-x-lock-chips", "Lock Chips", "The CX centrepiece. It locks a Main Blade and an Assist Blade together and carries the beast art.", partDefaults("category-x-lock-chips", "Lock Chip"), PART_TEMPLATES),
|
|
371
|
+
node("category-x-main-blades", "Main Blades", "The primary contact blade of a CX build — the piece that sets its attack shape.", partDefaults("category-x-main-blades", "Main Blade"), PART_TEMPLATES),
|
|
372
|
+
node("category-x-assist-blades", "Assist Blades", "The lower CX blade, tuning weight distribution and how the build sits on the rail.", partDefaults("category-x-assist-blades", "Assist Blade"), PART_TEMPLATES),
|
|
373
|
+
node("category-x-over-blades", "Over Blades", "A CX blade that mounts above the Lock Chip, adding an upper contact surface.", partDefaults("category-x-over-blades", "Over Blade"), PART_TEMPLATES),
|
|
374
|
+
node("category-x-metal-blades", "Metal Blades", "A metal CX blade — the heaviest option in the format and the usual choice for an outspin build.", partDefaults("category-x-metal-blades", "Metal Blade"), PART_TEMPLATES),
|
|
348
375
|
]),
|
|
349
376
|
branch("category-x-launchers", "Beyblade X Launchers", "String launchers, winders and grips for the X format.", [
|
|
350
377
|
node("category-x-string-launchers", "String Launchers", "The standard X launcher, with a shot angle designed around reaching the Xtreme Line rail.", launcherDefaults("category-x-string-launchers", "Beyblade X"), PART_TEMPLATES),
|