@swell/apps-sdk 1.0.190 → 2.0.0-alpha.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.
Files changed (235) hide show
  1. package/README.md +188 -254
  2. package/dist/backend.d.cts +81 -0
  3. package/dist/backend.d.ts +81 -0
  4. package/dist/backend.js +154 -0
  5. package/dist/browser-refusal.d.cts +1 -0
  6. package/dist/browser-refusal.d.ts +1 -0
  7. package/dist/browser-refusal.js +2 -0
  8. package/dist/context.d.cts +7 -0
  9. package/dist/context.d.ts +7 -0
  10. package/dist/context.js +29 -0
  11. package/dist/error.d.cts +23 -0
  12. package/dist/error.d.ts +23 -0
  13. package/dist/error.js +30 -0
  14. package/dist/guard.d.cts +1 -0
  15. package/dist/guard.d.ts +1 -0
  16. package/dist/guard.js +4 -0
  17. package/dist/index.d.cts +11 -0
  18. package/dist/index.d.ts +11 -0
  19. package/dist/index.js +6 -20299
  20. package/dist/jwks.d.cts +1 -0
  21. package/dist/jwks.d.ts +1 -0
  22. package/dist/jwks.js +70 -0
  23. package/dist/request-context.d.cts +37 -0
  24. package/dist/request-context.d.ts +37 -0
  25. package/dist/request-context.js +99 -0
  26. package/dist/staff.d.cts +7 -0
  27. package/dist/staff.d.ts +7 -0
  28. package/dist/staff.js +7 -0
  29. package/dist/storefront.d.cts +28 -0
  30. package/dist/storefront.d.ts +28 -0
  31. package/dist/storefront.js +44 -0
  32. package/dist/version.d.cts +1 -0
  33. package/dist/version.d.ts +1 -0
  34. package/dist/version.js +2 -0
  35. package/dist/workflow.d.cts +1 -0
  36. package/dist/workflow.d.ts +1 -0
  37. package/dist/workflow.js +35 -0
  38. package/package.json +48 -63
  39. package/dist/index.cjs +0 -20547
  40. package/dist/index.cjs.map +0 -7
  41. package/dist/index.js.map +0 -7
  42. package/dist/index.mjs +0 -20404
  43. package/dist/index.mjs.map +0 -7
  44. package/dist/src/api.d.ts +0 -96
  45. package/dist/src/cache/cache.d.ts +0 -41
  46. package/dist/src/cache/cf-worker-kv-keyv-adapter.d.ts +0 -18
  47. package/dist/src/cache/constants.d.ts +0 -7
  48. package/dist/src/cache/content-cache.d.ts +0 -21
  49. package/dist/src/cache/html-cache/html-cache-backend.d.ts +0 -64
  50. package/dist/src/cache/html-cache/html-cache-factory.d.ts +0 -9
  51. package/dist/src/cache/html-cache/html-cache-kv.d.ts +0 -38
  52. package/dist/src/cache/html-cache/html-cache-worker.d.ts +0 -8
  53. package/dist/src/cache/html-cache/html-cache.d.ts +0 -120
  54. package/dist/src/cache/html-cache/index.d.ts +0 -4
  55. package/dist/src/cache/index.d.ts +0 -9
  56. package/dist/src/cache/kv-variety.d.ts +0 -10
  57. package/dist/src/cache/request-cache.d.ts +0 -4
  58. package/dist/src/cache/resource-cache.d.ts +0 -4
  59. package/dist/src/cache/theme-cache.d.ts +0 -4
  60. package/dist/src/cache/theme-file-cache.d.ts +0 -87
  61. package/dist/src/compatibility/drops/all_products.d.ts +0 -10
  62. package/dist/src/compatibility/drops/articles.d.ts +0 -10
  63. package/dist/src/compatibility/drops/blogs.d.ts +0 -10
  64. package/dist/src/compatibility/drops/collections.d.ts +0 -19
  65. package/dist/src/compatibility/drops/image-src.d.ts +0 -15
  66. package/dist/src/compatibility/drops/image.d.ts +0 -29
  67. package/dist/src/compatibility/drops/images.d.ts +0 -9
  68. package/dist/src/compatibility/drops/money.d.ts +0 -10
  69. package/dist/src/compatibility/drops/object-handles.d.ts +0 -6
  70. package/dist/src/compatibility/drops/pages.d.ts +0 -17
  71. package/dist/src/compatibility/drops/robots-rule.d.ts +0 -9
  72. package/dist/src/compatibility/drops/template.d.ts +0 -9
  73. package/dist/src/compatibility/shopify-configs.d.ts +0 -8
  74. package/dist/src/compatibility/shopify-fonts.d.ts +0 -1
  75. package/dist/src/compatibility/shopify-objects/address.d.ts +0 -7
  76. package/dist/src/compatibility/shopify-objects/article.d.ts +0 -7
  77. package/dist/src/compatibility/shopify-objects/blog.d.ts +0 -6
  78. package/dist/src/compatibility/shopify-objects/cart.d.ts +0 -6
  79. package/dist/src/compatibility/shopify-objects/collection.d.ts +0 -7
  80. package/dist/src/compatibility/shopify-objects/collections.d.ts +0 -6
  81. package/dist/src/compatibility/shopify-objects/content.d.ts +0 -12
  82. package/dist/src/compatibility/shopify-objects/currency.d.ts +0 -5
  83. package/dist/src/compatibility/shopify-objects/customer.d.ts +0 -6
  84. package/dist/src/compatibility/shopify-objects/filter.d.ts +0 -6
  85. package/dist/src/compatibility/shopify-objects/font.d.ts +0 -5
  86. package/dist/src/compatibility/shopify-objects/form.d.ts +0 -29
  87. package/dist/src/compatibility/shopify-objects/image.d.ts +0 -10
  88. package/dist/src/compatibility/shopify-objects/index.d.ts +0 -27
  89. package/dist/src/compatibility/shopify-objects/line_item.d.ts +0 -7
  90. package/dist/src/compatibility/shopify-objects/link.d.ts +0 -5
  91. package/dist/src/compatibility/shopify-objects/localization.d.ts +0 -5
  92. package/dist/src/compatibility/shopify-objects/media.d.ts +0 -9
  93. package/dist/src/compatibility/shopify-objects/money.d.ts +0 -3
  94. package/dist/src/compatibility/shopify-objects/order.d.ts +0 -11
  95. package/dist/src/compatibility/shopify-objects/page.d.ts +0 -5
  96. package/dist/src/compatibility/shopify-objects/paginate.d.ts +0 -5
  97. package/dist/src/compatibility/shopify-objects/predictive_search.d.ts +0 -5
  98. package/dist/src/compatibility/shopify-objects/product.d.ts +0 -9
  99. package/dist/src/compatibility/shopify-objects/recommendations.d.ts +0 -5
  100. package/dist/src/compatibility/shopify-objects/resource.d.ts +0 -27
  101. package/dist/src/compatibility/shopify-objects/search.d.ts +0 -5
  102. package/dist/src/compatibility/shopify-objects/shop.d.ts +0 -5
  103. package/dist/src/compatibility/shopify-objects/template.d.ts +0 -4
  104. package/dist/src/compatibility/shopify-objects/variant.d.ts +0 -6
  105. package/dist/src/compatibility/shopify.d.ts +0 -134
  106. package/dist/src/constants.d.ts +0 -598
  107. package/dist/src/content.d.ts +0 -8
  108. package/dist/src/easyblocks/config.d.ts +0 -152
  109. package/dist/src/easyblocks/index.d.ts +0 -2
  110. package/dist/src/easyblocks/utils.d.ts +0 -23
  111. package/dist/src/editor/resources.d.ts +0 -9
  112. package/dist/src/fonts.d.ts +0 -6
  113. package/dist/src/globals.d.ts +0 -7
  114. package/dist/src/index.d.ts +0 -16
  115. package/dist/src/liquid/color.d.ts +0 -33
  116. package/dist/src/liquid/drops/render.d.ts +0 -10
  117. package/dist/src/liquid/filters/asset_url.d.ts +0 -3
  118. package/dist/src/liquid/filters/brightness_difference.d.ts +0 -2
  119. package/dist/src/liquid/filters/color_brightness.d.ts +0 -2
  120. package/dist/src/liquid/filters/color_contrast.d.ts +0 -2
  121. package/dist/src/liquid/filters/color_darken.d.ts +0 -2
  122. package/dist/src/liquid/filters/color_desaturate.d.ts +0 -2
  123. package/dist/src/liquid/filters/color_difference.d.ts +0 -2
  124. package/dist/src/liquid/filters/color_extract.d.ts +0 -4
  125. package/dist/src/liquid/filters/color_lighten.d.ts +0 -2
  126. package/dist/src/liquid/filters/color_mix.d.ts +0 -2
  127. package/dist/src/liquid/filters/color_modify.d.ts +0 -3
  128. package/dist/src/liquid/filters/color_saturate.d.ts +0 -2
  129. package/dist/src/liquid/filters/color_to_hex.d.ts +0 -2
  130. package/dist/src/liquid/filters/color_to_hsl.d.ts +0 -2
  131. package/dist/src/liquid/filters/color_to_rgb.d.ts +0 -2
  132. package/dist/src/liquid/filters/date.d.ts +0 -7
  133. package/dist/src/liquid/filters/date_next_interval.d.ts +0 -3
  134. package/dist/src/liquid/filters/default_errors.d.ts +0 -3
  135. package/dist/src/liquid/filters/divided_by.d.ts +0 -2
  136. package/dist/src/liquid/filters/embedded_content.d.ts +0 -2
  137. package/dist/src/liquid/filters/escape.d.ts +0 -3
  138. package/dist/src/liquid/filters/font_face.d.ts +0 -2
  139. package/dist/src/liquid/filters/font_modify.d.ts +0 -3
  140. package/dist/src/liquid/filters/font_url.d.ts +0 -2
  141. package/dist/src/liquid/filters/format_address.d.ts +0 -6
  142. package/dist/src/liquid/filters/handleize.d.ts +0 -3
  143. package/dist/src/liquid/filters/image_tag.d.ts +0 -3
  144. package/dist/src/liquid/filters/image_url.d.ts +0 -8
  145. package/dist/src/liquid/filters/index.d.ts +0 -120
  146. package/dist/src/liquid/filters/inline_asset_content.d.ts +0 -2
  147. package/dist/src/liquid/filters/inline_editable.d.ts +0 -4
  148. package/dist/src/liquid/filters/json.d.ts +0 -3
  149. package/dist/src/liquid/filters/json_pretty.d.ts +0 -3
  150. package/dist/src/liquid/filters/locale_flag.d.ts +0 -2
  151. package/dist/src/liquid/filters/minus.d.ts +0 -2
  152. package/dist/src/liquid/filters/money.d.ts +0 -4
  153. package/dist/src/liquid/filters/money_with_currency.d.ts +0 -3
  154. package/dist/src/liquid/filters/money_without_currency.d.ts +0 -3
  155. package/dist/src/liquid/filters/money_without_trailing_zeros.d.ts +0 -3
  156. package/dist/src/liquid/filters/preload_tag.d.ts +0 -3
  157. package/dist/src/liquid/filters/script_tag.d.ts +0 -3
  158. package/dist/src/liquid/filters/shopify/asset_img_url.d.ts +0 -3
  159. package/dist/src/liquid/filters/shopify/default_pagination.d.ts +0 -3
  160. package/dist/src/liquid/filters/shopify/hex_to_rgba.d.ts +0 -2
  161. package/dist/src/liquid/filters/shopify/img_url.d.ts +0 -3
  162. package/dist/src/liquid/filters/shopify/item_count_for_variant.d.ts +0 -6
  163. package/dist/src/liquid/filters/shopify/payment_button.d.ts +0 -2
  164. package/dist/src/liquid/filters/shopify/payment_terms.d.ts +0 -2
  165. package/dist/src/liquid/filters/shopify/placeholder-svgs/index.d.ts +0 -5
  166. package/dist/src/liquid/filters/shopify/placeholder_svg_tag.d.ts +0 -3
  167. package/dist/src/liquid/filters/shopify/shopify_asset_url.d.ts +0 -3
  168. package/dist/src/liquid/filters/shopify/structured_data.d.ts +0 -3
  169. package/dist/src/liquid/filters/stylesheet_tag.d.ts +0 -3
  170. package/dist/src/liquid/filters/time_tag.d.ts +0 -2
  171. package/dist/src/liquid/filters/translate.d.ts +0 -3
  172. package/dist/src/liquid/filters/where.d.ts +0 -3
  173. package/dist/src/liquid/font.d.ts +0 -51
  174. package/dist/src/liquid/form.d.ts +0 -17
  175. package/dist/src/liquid/hash.d.ts +0 -5
  176. package/dist/src/liquid/index.d.ts +0 -52
  177. package/dist/src/liquid/operators.d.ts +0 -10
  178. package/dist/src/liquid/tags/assign.d.ts +0 -3
  179. package/dist/src/liquid/tags/case.d.ts +0 -3
  180. package/dist/src/liquid/tags/comment.d.ts +0 -3
  181. package/dist/src/liquid/tags/content_for.d.ts +0 -3
  182. package/dist/src/liquid/tags/doc.d.ts +0 -2
  183. package/dist/src/liquid/tags/for.d.ts +0 -3
  184. package/dist/src/liquid/tags/form.d.ts +0 -3
  185. package/dist/src/liquid/tags/if.d.ts +0 -4
  186. package/dist/src/liquid/tags/index.d.ts +0 -42
  187. package/dist/src/liquid/tags/inline_editable.d.ts +0 -3
  188. package/dist/src/liquid/tags/javascript.d.ts +0 -3
  189. package/dist/src/liquid/tags/layout.d.ts +0 -3
  190. package/dist/src/liquid/tags/paginate.d.ts +0 -3
  191. package/dist/src/liquid/tags/render.d.ts +0 -6
  192. package/dist/src/liquid/tags/section.d.ts +0 -3
  193. package/dist/src/liquid/tags/sections.d.ts +0 -3
  194. package/dist/src/liquid/tags/shopify/include.d.ts +0 -3
  195. package/dist/src/liquid/tags/shopify/schema.d.ts +0 -3
  196. package/dist/src/liquid/tags/style.d.ts +0 -3
  197. package/dist/src/liquid/tags/stylesheet.d.ts +0 -3
  198. package/dist/src/liquid/test-helpers.d.ts +0 -8
  199. package/dist/src/liquid/tokens/identifier-token.d.ts +0 -9
  200. package/dist/src/liquid/tokens/index.d.ts +0 -2
  201. package/dist/src/liquid/tokienizer.d.ts +0 -5
  202. package/dist/src/liquid/utils.d.ts +0 -45
  203. package/dist/src/menus.d.ts +0 -25
  204. package/dist/src/resources/account.d.ts +0 -6
  205. package/dist/src/resources/addresses.d.ts +0 -7
  206. package/dist/src/resources/blog.d.ts +0 -7
  207. package/dist/src/resources/blog_category.d.ts +0 -7
  208. package/dist/src/resources/cart.d.ts +0 -6
  209. package/dist/src/resources/categories.d.ts +0 -7
  210. package/dist/src/resources/category.d.ts +0 -7
  211. package/dist/src/resources/index.d.ts +0 -48
  212. package/dist/src/resources/order.d.ts +0 -7
  213. package/dist/src/resources/orders.d.ts +0 -7
  214. package/dist/src/resources/page.d.ts +0 -7
  215. package/dist/src/resources/predictive_search.d.ts +0 -6
  216. package/dist/src/resources/product.d.ts +0 -7
  217. package/dist/src/resources/product_helpers.d.ts +0 -21
  218. package/dist/src/resources/search.d.ts +0 -6
  219. package/dist/src/resources/subscription.d.ts +0 -7
  220. package/dist/src/resources/subscriptions.d.ts +0 -7
  221. package/dist/src/resources/swell_types.d.ts +0 -163
  222. package/dist/src/resources/variant.d.ts +0 -9
  223. package/dist/src/resources.d.ts +0 -113
  224. package/dist/src/theme/theme-loader.d.ts +0 -110
  225. package/dist/src/theme.d.ts +0 -204
  226. package/dist/src/utils/escape.d.ts +0 -1
  227. package/dist/src/utils/index.d.ts +0 -34
  228. package/dist/src/utils/kv-flavor.d.ts +0 -7
  229. package/dist/src/utils/logger.d.ts +0 -21
  230. package/dist/src/utils/md5.d.ts +0 -1
  231. package/dist/types/cloudflare.d.ts +0 -61
  232. package/dist/types/shopify.d.ts +0 -1035
  233. package/dist/types/swell.d.ts +0 -576
  234. package/types/index.d.ts +0 -4
  235. package/types/svg.d.ts +0 -4
@@ -0,0 +1 @@
1
+ export declare function getVerificationKey(url: string, kid: string): Promise<CryptoKey>;
package/dist/jwks.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function getVerificationKey(url: string, kid: string): Promise<CryptoKey>;
package/dist/jwks.js ADDED
@@ -0,0 +1,70 @@
1
+ import { SwellError } from './error.js';
2
+ const CACHE_TTL = 300_000;
3
+ const REFRESH_INTERVAL = 30_000;
4
+ const FAILURE_RETRY_INTERVAL = 1000;
5
+ const MAX_URLS = 8;
6
+ // Public keys only: no tokens, credentials, identities or request results.
7
+ const keySets = new Map();
8
+ async function refresh(url, entry) {
9
+ entry.refreshAfter = Date.now() + REFRESH_INTERVAL;
10
+ try {
11
+ const response = await fetch(url, { redirect: 'manual', signal: AbortSignal.timeout(5000) });
12
+ if (!response.ok)
13
+ throw new Error('JWKS request failed');
14
+ const body = await response.json();
15
+ if (!Array.isArray(body?.keys))
16
+ throw new Error('Invalid JWKS');
17
+ const keys = new Map();
18
+ for (const jwk of body.keys) {
19
+ if (!jwk || jwk.kty !== 'EC' || jwk.crv !== 'P-256' || (jwk.alg && jwk.alg !== 'ES256') || (jwk.use && jwk.use !== 'sig'))
20
+ continue;
21
+ if (typeof jwk.kid !== 'string' || !jwk.kid || keys.has(jwk.kid) || jwk.d)
22
+ throw new Error('Invalid JWKS key');
23
+ keys.set(jwk.kid, await crypto.subtle.importKey('jwk', jwk, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']));
24
+ }
25
+ if (!keys.size)
26
+ throw new Error('Empty JWKS');
27
+ entry.keys = keys;
28
+ entry.expiresAt = Date.now() + CACHE_TTL;
29
+ entry.error = undefined;
30
+ }
31
+ catch {
32
+ entry.refreshAfter = Date.now() + FAILURE_RETRY_INTERVAL;
33
+ entry.error = new SwellError('Swell verification keys unavailable', { status: 503, code: 'swell_jwks_unavailable' });
34
+ throw entry.error;
35
+ }
36
+ }
37
+ export async function getVerificationKey(url, kid) {
38
+ let entry = keySets.get(url);
39
+ if (!entry) {
40
+ if (keySets.size >= MAX_URLS)
41
+ keySets.delete(keySets.keys().next().value);
42
+ entry = { keys: new Map(), expiresAt: 0, refreshAfter: 0 };
43
+ keySets.set(url, entry);
44
+ }
45
+ const now = Date.now();
46
+ const cached = entry.keys.get(kid);
47
+ if (cached && now < entry.expiresAt)
48
+ return cached;
49
+ if (entry.pending) {
50
+ await entry.pending;
51
+ }
52
+ else if (now >= entry.expiresAt || !entry.keys.has(kid)) {
53
+ if (now >= entry.refreshAfter) {
54
+ entry.pending = refresh(url, entry);
55
+ try {
56
+ await entry.pending;
57
+ }
58
+ finally {
59
+ entry.pending = undefined;
60
+ }
61
+ }
62
+ else if (now >= entry.expiresAt) {
63
+ throw entry.error ?? new SwellError('Swell verification keys unavailable', { status: 503, code: 'swell_jwks_unavailable' });
64
+ }
65
+ }
66
+ const key = entry.keys.get(kid);
67
+ if (!key)
68
+ throw new SwellError('Unknown Swell signing key', { status: 401, code: 'invalid_swell_context' });
69
+ return key;
70
+ }
@@ -0,0 +1,37 @@
1
+ import type { HeaderReader } from './context.cjs';
2
+ import type { StaffIdentity } from './staff.cjs';
3
+ export interface SwellHeadersEnv {
4
+ SWELL_VERIFY_HEADERS?: string;
5
+ SWELL_HEADERS_JWKS_URL?: string;
6
+ }
7
+ export interface VerifySwellContextOptions {
8
+ /** Explicit Worker bindings or server configuration. Omit to read process.env when available. */
9
+ env?: SwellHeadersEnv;
10
+ /** Expected destination IDs from trusted app configuration, never incoming headers. */
11
+ appId?: string;
12
+ storeId?: string;
13
+ /** Trusted vault override; the unsigned Swell-Vault-Url header is not used. */
14
+ vaultUrl?: string;
15
+ }
16
+ /** Request-local server data. Only getStorefrontConfig's projection may be sent to the browser. */
17
+ export interface SwellRequestContext {
18
+ readonly storeId: string;
19
+ readonly appId: string;
20
+ readonly installationId: string;
21
+ readonly environmentId?: string;
22
+ readonly storefrontId?: string;
23
+ readonly apiHost: string;
24
+ readonly adminUrl: string;
25
+ readonly accessToken?: string;
26
+ readonly publicKey?: string;
27
+ readonly requestId?: string;
28
+ readonly vaultUrl?: string;
29
+ readonly staff: Readonly<StaffIdentity> | null;
30
+ /** False only when trusted runtime configuration explicitly disables verification. */
31
+ readonly signatureVerified: boolean;
32
+ }
33
+ /**
34
+ * Resolves Swell-Context once per request. Uses ES256 and a pinned JWKS endpoint;
35
+ * never discovers keys from the request. No fallback to plain headers or staff cookies.
36
+ */
37
+ export declare function verifySwellContext(headers: HeaderReader, options?: VerifySwellContextOptions): Promise<SwellRequestContext>;
@@ -0,0 +1,37 @@
1
+ import type { HeaderReader } from './context.js';
2
+ import type { StaffIdentity } from './staff.js';
3
+ export interface SwellHeadersEnv {
4
+ SWELL_VERIFY_HEADERS?: string;
5
+ SWELL_HEADERS_JWKS_URL?: string;
6
+ }
7
+ export interface VerifySwellContextOptions {
8
+ /** Explicit Worker bindings or server configuration. Omit to read process.env when available. */
9
+ env?: SwellHeadersEnv;
10
+ /** Expected destination IDs from trusted app configuration, never incoming headers. */
11
+ appId?: string;
12
+ storeId?: string;
13
+ /** Trusted vault override; the unsigned Swell-Vault-Url header is not used. */
14
+ vaultUrl?: string;
15
+ }
16
+ /** Request-local server data. Only getStorefrontConfig's projection may be sent to the browser. */
17
+ export interface SwellRequestContext {
18
+ readonly storeId: string;
19
+ readonly appId: string;
20
+ readonly installationId: string;
21
+ readonly environmentId?: string;
22
+ readonly storefrontId?: string;
23
+ readonly apiHost: string;
24
+ readonly adminUrl: string;
25
+ readonly accessToken?: string;
26
+ readonly publicKey?: string;
27
+ readonly requestId?: string;
28
+ readonly vaultUrl?: string;
29
+ readonly staff: Readonly<StaffIdentity> | null;
30
+ /** False only when trusted runtime configuration explicitly disables verification. */
31
+ readonly signatureVerified: boolean;
32
+ }
33
+ /**
34
+ * Resolves Swell-Context once per request. Uses ES256 and a pinned JWKS endpoint;
35
+ * never discovers keys from the request. No fallback to plain headers or staff cookies.
36
+ */
37
+ export declare function verifySwellContext(headers: HeaderReader, options?: VerifySwellContextOptions): Promise<SwellRequestContext>;
@@ -0,0 +1,99 @@
1
+ import { requireString, validateUrl } from './context.js';
2
+ import { SwellError } from './error.js';
3
+ import { getVerificationKey } from './jwks.js';
4
+ function invalidContext() {
5
+ return new SwellError('Missing or invalid Swell context', { status: 401, code: 'invalid_swell_context' });
6
+ }
7
+ function decode(value) {
8
+ if (!/^[A-Za-z0-9_-]+$/.test(value))
9
+ throw invalidContext();
10
+ return Uint8Array.from(atob(value.replace(/-/g, '+').replace(/_/g, '/')), char => char.charCodeAt(0));
11
+ }
12
+ function object(value) {
13
+ if (!value || typeof value !== 'object' || Array.isArray(value))
14
+ throw invalidContext();
15
+ }
16
+ /**
17
+ * Resolves Swell-Context once per request. Uses ES256 and a pinned JWKS endpoint;
18
+ * never discovers keys from the request. No fallback to plain headers or staff cookies.
19
+ */
20
+ export async function verifySwellContext(headers, options = {}) {
21
+ const runtime = globalThis;
22
+ const env = options.env ?? runtime.process?.env ?? {};
23
+ const verify = env.SWELL_VERIFY_HEADERS !== 'false';
24
+ const jwksUrl = validateUrl(env.SWELL_HEADERS_JWKS_URL ?? 'https://swell.store/.well-known/jwks.json', 'SWELL_HEADERS_JWKS_URL');
25
+ const issuer = new URL(jwksUrl).origin;
26
+ for (const field of ['appId', 'storeId']) {
27
+ if (options[field] !== undefined)
28
+ requireString(options[field], field);
29
+ }
30
+ const vaultUrl = options.vaultUrl === undefined ? undefined : validateUrl(options.vaultUrl, 'vaultUrl');
31
+ const token = headers.get('Swell-Context');
32
+ if (!token || token.length > 16_384)
33
+ throw invalidContext();
34
+ let payload;
35
+ let header;
36
+ let signature;
37
+ const parts = token.split('.');
38
+ try {
39
+ if (parts.length !== 3)
40
+ throw invalidContext();
41
+ header = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(decode(parts[0])));
42
+ payload = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(decode(parts[1])));
43
+ object(header);
44
+ object(payload);
45
+ signature = decode(parts[2]);
46
+ if (header.alg !== 'ES256' || typeof header.kid !== 'string' || !header.kid || header.crit !== undefined || header.b64 !== undefined || signature.length !== 64)
47
+ throw invalidContext();
48
+ }
49
+ catch {
50
+ throw invalidContext();
51
+ }
52
+ if (verify) {
53
+ const key = await getVerificationKey(jwksUrl, header.kid);
54
+ if (!await crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, key, signature, new TextEncoder().encode(`${parts[0]}.${parts[1]}`)))
55
+ throw invalidContext();
56
+ }
57
+ try {
58
+ const now = Date.now() / 1000;
59
+ // The issuer controls token lifetime; allow five seconds for clock skew.
60
+ if (typeof payload.iat !== 'number' || !Number.isFinite(payload.iat) || payload.iat > now + 5 ||
61
+ typeof payload.exp !== 'number' || !Number.isFinite(payload.exp) || payload.exp <= now - 5 ||
62
+ payload.exp <= payload.iat)
63
+ throw invalidContext();
64
+ if (payload.nbf !== undefined && (typeof payload.nbf !== 'number' || !Number.isFinite(payload.nbf) || payload.nbf > now + 5))
65
+ throw invalidContext();
66
+ requireString(payload.iss, 'iss');
67
+ if (verify && payload.iss !== issuer)
68
+ throw invalidContext();
69
+ for (const field of ['store_id', 'app_id', 'installation_id'])
70
+ requireString(payload[field], field);
71
+ if (payload.aud !== payload.app_id || (options.appId !== undefined && payload.aud !== options.appId) ||
72
+ (options.storeId !== undefined && payload.store_id !== options.storeId))
73
+ throw invalidContext();
74
+ for (const field of ['environment_id', 'storefront_id']) {
75
+ if (payload[field] != null)
76
+ requireString(payload[field], field);
77
+ }
78
+ let staff = null;
79
+ if (payload.admin !== null) {
80
+ object(payload.admin);
81
+ requireString(payload.admin.user_id, 'admin.user_id');
82
+ staff = Object.freeze({ userId: payload.admin.user_id, storeId: payload.store_id });
83
+ }
84
+ return Object.freeze({
85
+ storeId: payload.store_id, appId: payload.app_id,
86
+ installationId: payload.installation_id,
87
+ environmentId: payload.environment_id ?? undefined,
88
+ storefrontId: payload.storefront_id ?? undefined,
89
+ apiHost: validateUrl(payload.api_host, 'api_host'), adminUrl: validateUrl(payload.admin_url, 'admin_url'),
90
+ accessToken: headers.get('Swell-Access-Token') ?? undefined,
91
+ publicKey: headers.get('Swell-Public-Key') ?? undefined,
92
+ requestId: headers.get('Swell-Request-ID') ?? undefined,
93
+ vaultUrl, staff, signatureVerified: verify,
94
+ });
95
+ }
96
+ catch {
97
+ throw invalidContext();
98
+ }
99
+ }
@@ -0,0 +1,7 @@
1
+ import type { SwellRequestContext } from './request-context.cjs';
2
+ export interface StaffIdentity {
3
+ readonly userId: string;
4
+ readonly storeId: string;
5
+ }
6
+ /** Identity only; any dashboard role counts. The proxy owns write-origin checks; the app owns permissions. */
7
+ export declare function requireStaff(context: SwellRequestContext): StaffIdentity;
@@ -0,0 +1,7 @@
1
+ import type { SwellRequestContext } from './request-context.js';
2
+ export interface StaffIdentity {
3
+ readonly userId: string;
4
+ readonly storeId: string;
5
+ }
6
+ /** Identity only; any dashboard role counts. The proxy owns write-origin checks; the app owns permissions. */
7
+ export declare function requireStaff(context: SwellRequestContext): StaffIdentity;
package/dist/staff.js ADDED
@@ -0,0 +1,7 @@
1
+ import { SwellError } from './error.js';
2
+ /** Identity only; any dashboard role counts. The proxy owns write-origin checks; the app owns permissions. */
3
+ export function requireStaff(context) {
4
+ if (!context.staff)
5
+ throw new SwellError('Staff identity required', { status: 401, code: 'staff_required' });
6
+ return context.staff;
7
+ }
@@ -0,0 +1,28 @@
1
+ import './guard.cjs';
2
+ import type { PublicConfig, SwellClient } from 'swell-js';
3
+ export interface CookieOptions {
4
+ path?: string;
5
+ maxAge?: number;
6
+ sameSite?: 'lax' | 'strict' | 'none';
7
+ domain?: string;
8
+ expires?: Date;
9
+ secure?: boolean;
10
+ httpOnly?: boolean;
11
+ [attribute: string]: string | number | boolean | Date | undefined;
12
+ }
13
+ export interface CookieAdapter {
14
+ /** Return decoded current values, including this request's writes/deletes. The adapter owns state. */
15
+ get(name: string): string | undefined;
16
+ /** Owns write policy: may skip a write, or throw when required persistence is unavailable. */
17
+ set?(name: string, value: string, options: CookieOptions): void;
18
+ }
19
+ export interface StorefrontOptions {
20
+ cookies: CookieAdapter;
21
+ /** Replaces native defaults (/, one week, SameSite=Lax). Use {} for adapter-owned policy. */
22
+ cookieOptions?: CookieOptions;
23
+ }
24
+ /** Creates an isolated native swell-js client. Replace request before first use to add caching. */
25
+ export declare function createStorefrontClient(config: PublicConfig<'camel'> & {
26
+ useCamelCase: true;
27
+ }, options: StorefrontOptions): SwellClient<'camel'>;
28
+ export declare function createStorefrontClient<C extends 'snake' | 'camel' = 'snake'>(config: PublicConfig<C>, options: StorefrontOptions): SwellClient<C>;
@@ -0,0 +1,28 @@
1
+ import './guard.js';
2
+ import type { PublicConfig, SwellClient } from 'swell-js';
3
+ export interface CookieOptions {
4
+ path?: string;
5
+ maxAge?: number;
6
+ sameSite?: 'lax' | 'strict' | 'none';
7
+ domain?: string;
8
+ expires?: Date;
9
+ secure?: boolean;
10
+ httpOnly?: boolean;
11
+ [attribute: string]: string | number | boolean | Date | undefined;
12
+ }
13
+ export interface CookieAdapter {
14
+ /** Return decoded current values, including this request's writes/deletes. The adapter owns state. */
15
+ get(name: string): string | undefined;
16
+ /** Owns write policy: may skip a write, or throw when required persistence is unavailable. */
17
+ set?(name: string, value: string, options: CookieOptions): void;
18
+ }
19
+ export interface StorefrontOptions {
20
+ cookies: CookieAdapter;
21
+ /** Replaces native defaults (/, one week, SameSite=Lax). Use {} for adapter-owned policy. */
22
+ cookieOptions?: CookieOptions;
23
+ }
24
+ /** Creates an isolated native swell-js client. Replace request before first use to add caching. */
25
+ export declare function createStorefrontClient(config: PublicConfig<'camel'> & {
26
+ useCamelCase: true;
27
+ }, options: StorefrontOptions): SwellClient<'camel'>;
28
+ export declare function createStorefrontClient<C extends 'snake' | 'camel' = 'snake'>(config: PublicConfig<C>, options: StorefrontOptions): SwellClient<C>;
@@ -0,0 +1,44 @@
1
+ import './guard.js';
2
+ import swell from 'swell-js';
3
+ import { requireString, validateUrl } from './context.js';
4
+ export function createStorefrontClient(config, { cookies, cookieOptions = { path: '/', maxAge: 604800, sameSite: 'lax' } }) {
5
+ for (const field of ['getCookie', 'setCookie', 'getCart', 'updateCart']) {
6
+ if (field in config)
7
+ throw new Error(`Invalid public config callback: ${field}`);
8
+ }
9
+ requireString(config.storeId, 'storeId');
10
+ requireString(config.publicKey, 'publicKey');
11
+ for (const field of ['url', 'vaultUrl']) {
12
+ if (config[field] !== undefined)
13
+ validateUrl(config[field], field);
14
+ }
15
+ for (const field of ['locale', 'currency', 'session', 'store', 'key']) {
16
+ if (config[field] !== undefined && typeof config[field] !== 'string')
17
+ throw new Error(`Invalid ${field}`);
18
+ }
19
+ for (const field of ['useCamelCase', 'previewContent']) {
20
+ if (config[field] !== undefined && typeof config[field] !== 'boolean')
21
+ throw new Error(`Invalid ${field}`);
22
+ }
23
+ if (config.timeout !== undefined && (!Number.isFinite(config.timeout) || config.timeout < 0))
24
+ throw new Error('Invalid timeout');
25
+ if (config.headers !== undefined && (!config.headers || typeof config.headers !== 'object' || Array.isArray(config.headers) || Object.values(config.headers).some(value => typeof value !== 'string')))
26
+ throw new Error('Invalid headers');
27
+ if (!cookies || typeof cookies.get !== 'function' || (cookies.set !== undefined && typeof cookies.set !== 'function'))
28
+ throw new Error('A cookie adapter with get() and optional set() is required');
29
+ return swell.create(config.storeId, config.publicKey, {
30
+ ...config,
31
+ headers: { ...config.headers },
32
+ getCookie: name => cookies.get(name),
33
+ setCookie(name, value, attributes = {}) {
34
+ if (!cookies.set)
35
+ throw new Error('Cannot write Swell cookies with a read-only adapter; use a writable route handler or action');
36
+ const options = { ...cookieOptions };
37
+ for (const [key, value] of Object.entries(attributes)) {
38
+ const normalized = { 'max-age': 'maxAge', maxage: 'maxAge', samesite: 'sameSite', httponly: 'httpOnly' }[key.toLowerCase()] ?? key;
39
+ options[normalized] = value;
40
+ }
41
+ return cookies.set(name, value, options);
42
+ },
43
+ });
44
+ }
@@ -0,0 +1 @@
1
+ export declare const USER_AGENT = "swell-apps-sdk/2.0.0-alpha.1";
@@ -0,0 +1 @@
1
+ export declare const USER_AGENT = "swell-apps-sdk/2.0.0-alpha.1";
@@ -0,0 +1,2 @@
1
+ // Replaced with the package version by the build.
2
+ export const USER_AGENT = 'swell-apps-sdk/2.0.0-alpha.1';
@@ -0,0 +1 @@
1
+ export declare function validateWorkflowParams(params: unknown): unknown;
@@ -0,0 +1 @@
1
+ export declare function validateWorkflowParams(params: unknown): unknown;
@@ -0,0 +1,35 @@
1
+ import { SwellError } from './error.js';
2
+ function paramsError(code, message) {
3
+ return new SwellError({ error: { code, message, status: 400, retryable: false } }, { code, status: 400 });
4
+ }
5
+ function validateValue(value, seen) {
6
+ if (value === null || typeof value === 'string' || typeof value === 'boolean' || (typeof value === 'number' && Number.isFinite(value)))
7
+ return;
8
+ if (typeof value !== 'object' || value === null || seen.has(value) || Object.getOwnPropertySymbols(value).length) {
9
+ throw paramsError('workflow_params_unserializable', 'Workflow params must be JSON-safe values');
10
+ }
11
+ if (Array.isArray(value)) {
12
+ const keys = Object.keys(value);
13
+ if (keys.some(key => !/^(0|[1-9]\d*)$/.test(key) || Number(key) >= value.length)) {
14
+ throw paramsError('workflow_params_unserializable', 'Workflow params must be JSON-safe values');
15
+ }
16
+ for (let index = 0; index < value.length; index++) {
17
+ if (!Object.prototype.hasOwnProperty.call(value, index))
18
+ throw paramsError('workflow_params_unserializable', 'Workflow params must be JSON-safe values');
19
+ }
20
+ }
21
+ else if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
22
+ throw paramsError('workflow_params_unserializable', 'Workflow params must be JSON-safe values');
23
+ }
24
+ seen.add(value);
25
+ for (const item of Array.isArray(value) ? value : Object.values(value))
26
+ validateValue(item, seen);
27
+ seen.delete(value);
28
+ }
29
+ export function validateWorkflowParams(params) {
30
+ validateValue(params, new Set());
31
+ if (new TextEncoder().encode(JSON.stringify(params)).length > 128 * 1024) {
32
+ throw paramsError('workflow_params_too_large', 'Workflow params are too large; pass identifiers and re-fetch data inside the workflow');
33
+ }
34
+ return params;
35
+ }
package/package.json CHANGED
@@ -1,77 +1,62 @@
1
1
  {
2
2
  "name": "@swell/apps-sdk",
3
+ "version": "2.0.0-alpha.1",
4
+ "description": "Server SDK for Swell apps.",
3
5
  "type": "module",
4
- "version": "1.0.190",
5
- "description": "Swell SDK for building isomorphic apps.",
6
- "author": "Swell",
7
6
  "license": "MIT",
8
7
  "engines": {
9
- "node": ">=18.16.1",
10
- "npm": ">=9.3.1"
11
- },
12
- "repository": {
13
- "type": "git",
14
- "url": "https://github.com/swellstores/swell-apps-sdk"
15
- },
16
- "main": "./dist/index.cjs",
17
- "module": "./dist/index.mjs",
18
- "browser": "./dist/index.js",
19
- "types": "./types/index.d.ts",
20
- "exports": {
21
- ".": {
22
- "types": "./types/index.d.ts",
23
- "require": "./dist/index.cjs",
24
- "import": "./dist/index.mjs"
25
- }
8
+ "node": ">=22.22.2"
26
9
  },
27
10
  "files": [
28
- "dist",
29
- "types/*.d.ts"
11
+ "dist"
30
12
  ],
31
13
  "scripts": {
32
- "prebuild": "rimraf dist",
33
14
  "build": "node scripts/build.mjs",
34
- "postbuild": "tsc --project tsconfig.build.json",
35
- "prepare": "git config core.hooksPath .git-hooks || echo 'Not in a git repo'",
36
- "prepublishOnly": "npm i && npm run build",
37
- "watch": "node scripts/build.mjs watch",
38
- "test": "jest --silent"
15
+ "prepack": "npm run build",
16
+ "typecheck": "tsc -p tsconfig.json --noEmit",
17
+ "test": "npm run build && node --test test/*.test.mjs",
18
+ "test:package": "node scripts/verify-package.mjs",
19
+ "test:boundaries": "node scripts/verify-boundaries.mjs",
20
+ "test:worker": "node scripts/verify-worker.mjs",
21
+ "verify": "npm test && npm run typecheck && npm run test:package && npm run test:boundaries && npm run test:worker"
39
22
  },
40
- "dependencies": {
41
- "@swell/easyblocks-core": "^1.0.19",
42
- "cache-manager": "^7.0.1",
43
- "color": "^4.2.3",
44
- "country-flag-icons": "1.5.18",
45
- "json5": "^2.2.3",
46
- "keyv": "^5.3.4",
47
- "liquidjs": "^10.21.0",
48
- "lodash-es": "^4.17.21",
49
- "qs": "^6.12.3",
50
- "strftime": "^0.10.3",
51
- "swell-js": "^5.1.2"
23
+ "peerDependencies": {
24
+ "swell-js": ">=5.9.1"
52
25
  },
53
26
  "devDependencies": {
54
- "@cloudflare/workers-types": "^4.20250902.0",
55
- "@eslint/js": "^9.19.0",
56
- "@jest/transform": "^29.7.0",
57
- "@types/color": "^4.2.0",
58
- "@types/jest": "^29.5.14",
59
- "@types/lodash-es": "^4.17.12",
60
- "@types/qs": "^6.9.15",
61
- "@types/react": "^18.2.64",
62
- "@types/react-dom": "^18.2.21",
63
- "@types/strftime": "^0.9.8",
64
- "esbuild": "^0.24.2",
65
- "eslint": "^9.19.0",
66
- "eslint-config-prettier": "^10.0.1",
67
- "eslint-plugin-jest": "^28.11.0",
68
- "eslint-plugin-prettier": "^5.2.3",
69
- "jest": "29.7.0",
70
- "jest-esbuild": "^0.4.0",
71
- "prettier": "^3.4.2",
72
- "rimraf": "^6.0.1",
73
- "ts-jest": "^29.2.5",
74
- "typescript": "^5.7.3",
75
- "typescript-eslint": "^8.23.0"
76
- }
27
+ "esbuild": "0.24.2",
28
+ "miniflare": "5.20260911.1-alpha",
29
+ "swell-js": "5.9.1",
30
+ "typescript": "5.7.3"
31
+ },
32
+ "main": "./dist/index.js",
33
+ "types": "./dist/index.d.ts",
34
+ "exports": {
35
+ ".": {
36
+ "types": {
37
+ "import": "./dist/index.d.ts",
38
+ "require": "./dist/index.d.cts"
39
+ },
40
+ "workerd": "./dist/index.js",
41
+ "node": "./dist/index.js",
42
+ "browser": "./dist/browser-refusal.js",
43
+ "default": "./dist/index.js"
44
+ },
45
+ "./storefront": {
46
+ "types": {
47
+ "import": "./dist/storefront.d.ts",
48
+ "require": "./dist/storefront.d.cts"
49
+ },
50
+ "workerd": "./dist/storefront.js",
51
+ "node": "./dist/storefront.js",
52
+ "browser": "./dist/browser-refusal.js",
53
+ "default": "./dist/storefront.js"
54
+ }
55
+ },
56
+ "sideEffects": [
57
+ "./dist/index.js",
58
+ "./dist/storefront.js",
59
+ "./dist/guard.js",
60
+ "./dist/browser-refusal.js"
61
+ ]
77
62
  }