@wix/agent-skills 1.13.0 → 1.15.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.
Files changed (250) hide show
  1. package/README.md +4 -1
  2. package/package.json +1 -1
  3. package/skills/README.md +23 -0
  4. package/skills/wix-app/SKILL.md +10 -2
  5. package/skills/wix-app/references/AUTO_PATTERNS_DASHBOARD.md +326 -0
  6. package/skills/wix-app/references/CODE_QUALITY.md +1 -0
  7. package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +124 -115
  8. package/skills/wix-app/references/DASHBOARD_PAGE.md +3 -0
  9. package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +23 -9
  10. package/skills/wix-app/references/SITE_PLUGIN.md +1 -1
  11. package/skills/wix-app/references/auto-patterns-dashboard/action-cell.md +115 -0
  12. package/skills/wix-app/references/auto-patterns-dashboard/app-config-structure.md +264 -0
  13. package/skills/wix-app/references/auto-patterns-dashboard/app-context.md +37 -0
  14. package/skills/wix-app/references/auto-patterns-dashboard/bulk-actions.md +86 -0
  15. package/skills/wix-app/references/auto-patterns-dashboard/collection-page-actions.md +119 -0
  16. package/skills/wix-app/references/auto-patterns-dashboard/collection-page.md +103 -0
  17. package/skills/wix-app/references/auto-patterns-dashboard/custom-actions-override.md +76 -0
  18. package/skills/wix-app/references/auto-patterns-dashboard/custom-columns-override.md +82 -0
  19. package/skills/wix-app/references/auto-patterns-dashboard/custom-components-override.md +89 -0
  20. package/skills/wix-app/references/auto-patterns-dashboard/custom-header-override.md +124 -0
  21. package/skills/wix-app/references/auto-patterns-dashboard/custom-sections-override.md +91 -0
  22. package/skills/wix-app/references/auto-patterns-dashboard/custom-slots-override.md +71 -0
  23. package/skills/wix-app/references/auto-patterns-dashboard/entity-page-actions.md +70 -0
  24. package/skills/wix-app/references/auto-patterns-dashboard/entity-page-view-actions.md +73 -0
  25. package/skills/wix-app/references/auto-patterns-dashboard/entity-page.md +98 -0
  26. package/skills/wix-app/references/auto-patterns-dashboard/example-patterns.json +278 -0
  27. package/skills/wix-app/references/auto-patterns-dashboard/pages-configuration.md +94 -0
  28. package/skills/wix-app/references/auto-patterns-dashboard/resolved-action.md +43 -0
  29. package/skills/wix-app/references/auto-patterns-dashboard/sdk-utilities.md +66 -0
  30. package/skills/wix-app/references/auto-patterns-dashboard/views.md +76 -0
  31. package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +1 -14
  32. package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +54 -29
  33. package/skills/wix-app/references/editor-react-component/COMPONENT-CONFIGURATION.md +2 -2
  34. package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +17 -105
  35. package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +127 -0
  36. package/skills/wix-app/references/editor-react-component/PARTS.md +2 -1
  37. package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +15 -15
  38. package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +25 -53
  39. package/skills/wix-app/scripts/generate-auto-patterns.js +510 -0
  40. package/skills/wix-auth/SKILL.md +12 -0
  41. package/skills/wix-auth/references/device-flow.md +140 -0
  42. package/skills/wix-docs/SKILL.md +188 -0
  43. package/skills/wix-docs/references/API_SPEC_SEARCH.md +213 -0
  44. package/skills/wix-docs/references/EXTRACTING.md +120 -0
  45. package/skills/wix-headless/SKILL.md +84 -130
  46. package/skills/wix-headless/entry/bootstrap.mjs +93 -0
  47. package/skills/wix-headless/entry/skill.md +78 -0
  48. package/skills/wix-headless/references/AI_FEATURES.md +178 -0
  49. package/skills/wix-headless/references/CAPABILITIES.md +84 -0
  50. package/skills/wix-headless/references/DISCOVERY.md +34 -56
  51. package/skills/wix-headless/references/DOC_DISCOVERY.md +37 -0
  52. package/skills/wix-headless/references/FEEDBACK.md +118 -0
  53. package/skills/wix-headless/references/IMAGE_GENERATION.md +69 -0
  54. package/skills/wix-headless/references/SDK_HANDOFF.md +97 -0
  55. package/skills/wix-headless/references/SEED.md +44 -188
  56. package/skills/wix-headless/references/SETUP.md +32 -103
  57. package/skills/wix-headless/references/astro.md +98 -0
  58. package/skills/wix-headless/references/inline-recipes/how-to-code-a-blog.md +196 -0
  59. package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +236 -0
  60. package/skills/wix-headless/references/inline-recipes/how-to-code-bookings.md +279 -0
  61. package/skills/wix-headless/references/inline-recipes/how-to-code-cms.md +163 -0
  62. package/skills/wix-headless/references/inline-recipes/how-to-code-events.md +203 -0
  63. package/skills/wix-headless/references/inline-recipes/how-to-code-forms.md +222 -0
  64. package/skills/wix-headless/references/inline-recipes/how-to-code-members-astro.md +124 -0
  65. package/skills/wix-headless/references/inline-recipes/how-to-code-members-custom-login.md +160 -0
  66. package/skills/wix-headless/references/inline-recipes/how-to-code-members-non-astro.md +118 -0
  67. package/skills/wix-headless/references/inline-recipes/how-to-code-portfolio.md +132 -0
  68. package/skills/wix-headless/references/inline-recipes/how-to-code-pricing-plans.md +124 -0
  69. package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-orders.md +188 -0
  70. package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-reservations.md +173 -0
  71. package/skills/wix-headless/references/inline-recipes/how-to-code-restaurants.md +161 -0
  72. package/skills/wix-headless/references/inline-recipes/setup-blog.md +175 -0
  73. package/skills/wix-headless/references/inline-recipes/setup-bookings.md +228 -0
  74. package/skills/wix-headless/references/inline-recipes/setup-cms.md +200 -0
  75. package/skills/wix-headless/references/inline-recipes/setup-events.md +196 -0
  76. package/skills/wix-headless/references/inline-recipes/setup-forms.md +174 -0
  77. package/skills/wix-headless/references/inline-recipes/setup-online-store.md +176 -0
  78. package/skills/wix-headless/references/inline-recipes/setup-portfolio.md +153 -0
  79. package/skills/wix-headless/references/inline-recipes/setup-pricing-plans.md +229 -0
  80. package/skills/wix-headless/references/inline-recipes/setup-restaurant-orders.md +161 -0
  81. package/skills/wix-headless/references/inline-recipes/setup-restaurant-reservations.md +117 -0
  82. package/skills/wix-headless/references/inline-recipes/setup-restaurants.md +152 -0
  83. package/skills/wix-headless/references/managed/AUTHENTICATION.md +57 -0
  84. package/skills/wix-headless/references/managed/CONNECT.md +74 -0
  85. package/skills/wix-headless/references/managed/CREATE.md +130 -0
  86. package/skills/wix-headless/references/managed/DEPLOYMENT.md +57 -0
  87. package/skills/wix-headless/references/non-astro.md +59 -0
  88. package/skills/wix-headless/references/self-managed/AUTHENTICATION.md +22 -0
  89. package/skills/wix-headless/references/self-managed/DEPLOYMENT.md +7 -0
  90. package/skills/wix-headless/references/stripe/AUTHENTICATION.md +55 -0
  91. package/skills/wix-headless/references/stripe/DEPLOYMENT.md +63 -0
  92. package/skills/wix-manage/SKILL.md +24 -1
  93. package/skills/wix-manage/references/analytics/query-site-analytics.md +241 -0
  94. package/skills/wix-manage/references/bookings/create-and-update-booking-services.md +1 -1
  95. package/skills/wix-manage/references/bookings/diagnose-availability-issues.md +265 -0
  96. package/skills/wix-manage/references/marketing/create-and-publish-social-post.md +436 -0
  97. package/skills/wix-manage/references/marketing/generate-and-publish-marketing-plan.md +209 -0
  98. package/skills/wix-manage/references/rich-content/author-ricos-rich-content.md +249 -0
  99. package/skills/wix-manage/references/stores/create-product-with-options-catalog-v3.md +51 -0
  100. package/skills/wix-vibe-headless/SKILL.md +136 -0
  101. package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +128 -0
  102. package/skills/wix-vibe-headless/references/blog/wix-blog.js +217 -0
  103. package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +153 -0
  104. package/skills/wix-vibe-headless/references/bookings/wix-bookings-checkout.js +147 -0
  105. package/skills/wix-vibe-headless/references/bookings/wix-bookings-services.js +215 -0
  106. package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +154 -0
  107. package/skills/wix-vibe-headless/references/cms/wix-cms.js +186 -0
  108. package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +142 -0
  109. package/skills/wix-vibe-headless/references/events/wix-events-browse.js +135 -0
  110. package/skills/wix-vibe-headless/references/events/wix-events-registration.js +187 -0
  111. package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +211 -0
  112. package/skills/wix-vibe-headless/references/members/wix-members-auth.js +387 -0
  113. package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +111 -0
  114. package/skills/wix-vibe-headless/references/portfolio/wix-portfolio.js +194 -0
  115. package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +127 -0
  116. package/skills/wix-vibe-headless/references/pricing-plans/wix-pricing-plans.js +157 -0
  117. package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +138 -0
  118. package/skills/wix-vibe-headless/references/restaurants/wix-restaurants-menu.js +224 -0
  119. package/skills/wix-vibe-headless/references/restaurants/wix-restaurants-ordering.js +156 -0
  120. package/skills/wix-vibe-headless/references/restaurants/wix-restaurants-reservations.js +107 -0
  121. package/skills/wix-vibe-headless/references/shared/wix-client.js +186 -0
  122. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +155 -0
  123. package/skills/wix-vibe-headless/references/storefront/wix-store-cart.js +147 -0
  124. package/skills/wix-vibe-headless/references/storefront/wix-store-catalog.js +142 -0
  125. package/skills/wix-headless/references/BUILD-astro.md +0 -244
  126. package/skills/wix-headless/references/BUILD-own-build.md +0 -167
  127. package/skills/wix-headless/references/BUILD.md +0 -105
  128. package/skills/wix-headless/references/DESIGN_SYSTEM.md +0 -165
  129. package/skills/wix-headless/references/DISCOVERY-connect.md +0 -55
  130. package/skills/wix-headless/references/DISCOVERY-create.md +0 -238
  131. package/skills/wix-headless/references/PLAN-connect.md +0 -19
  132. package/skills/wix-headless/references/PLAN-create.md +0 -17
  133. package/skills/wix-headless/references/PLAN.md +0 -92
  134. package/skills/wix-headless/references/astro/blog/BLOG_PAGES.md +0 -300
  135. package/skills/wix-headless/references/astro/blog/BLOG_SETUP.md +0 -218
  136. package/skills/wix-headless/references/astro/bookings/COMPONENTS.md +0 -45
  137. package/skills/wix-headless/references/astro/bookings/SERVICES_PAGES.md +0 -77
  138. package/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md +0 -214
  139. package/skills/wix-headless/references/astro/cms/FAQ_KNOWLEDGE_BASE.md +0 -231
  140. package/skills/wix-headless/references/astro/cms/PORTFOLIO.md +0 -453
  141. package/skills/wix-headless/references/astro/cms/RESOURCE_LIBRARY.md +0 -354
  142. package/skills/wix-headless/references/astro/cms/TEAM_DIRECTORY.md +0 -299
  143. package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +0 -351
  144. package/skills/wix-headless/references/astro/ecom/CART_PAGES.md +0 -119
  145. package/skills/wix-headless/references/astro/ecom/CART_WIRING.md +0 -103
  146. package/skills/wix-headless/references/astro/ecom/COMPONENTS_CSS.md +0 -132
  147. package/skills/wix-headless/references/astro/forms/CONTACT_FORM.md +0 -463
  148. package/skills/wix-headless/references/astro/gift-cards/COMPONENTS.md +0 -119
  149. package/skills/wix-headless/references/astro/gift-cards/PAGES.md +0 -129
  150. package/skills/wix-headless/references/astro/stores/BACK_IN_STOCK.md +0 -178
  151. package/skills/wix-headless/references/astro/stores/CATEGORY_PAGES.md +0 -85
  152. package/skills/wix-headless/references/astro/stores/COMPONENTS_CSS.md +0 -129
  153. package/skills/wix-headless/references/astro/stores/HOME_AND_NAV.md +0 -229
  154. package/skills/wix-headless/references/astro/stores/PRODUCT_PAGES.md +0 -215
  155. package/skills/wix-headless/references/astro/stores/SHARED_WIRING.md +0 -165
  156. package/skills/wix-headless/references/astro/templates/Footer.astro +0 -27
  157. package/skills/wix-headless/references/astro/templates/Layout.astro +0 -132
  158. package/skills/wix-headless/references/astro/templates/Navigation.astro +0 -38
  159. package/skills/wix-headless/references/astro/templates/astro.config.mjs +0 -41
  160. package/skills/wix-headless/references/astro/templates/bookings/AvailabilityCalendar.tsx +0 -374
  161. package/skills/wix-headless/references/astro/templates/bookings/BookingForm.tsx +0 -136
  162. package/skills/wix-headless/references/astro/templates/bookings/SeoTags.astro +0 -25
  163. package/skills/wix-headless/references/astro/templates/bookings/ServiceBookingFlow.tsx +0 -71
  164. package/skills/wix-headless/references/astro/templates/bookings/ServiceCard.astro +0 -54
  165. package/skills/wix-headless/references/astro/templates/bookings/booking-confirmation.astro +0 -49
  166. package/skills/wix-headless/references/astro/templates/bookings/bookingDriver.ts +0 -245
  167. package/skills/wix-headless/references/astro/templates/bookings/components-bookings.css +0 -424
  168. package/skills/wix-headless/references/astro/templates/bookings/services/[slug].astro +0 -176
  169. package/skills/wix-headless/references/astro/templates/bookings/services/index.astro +0 -181
  170. package/skills/wix-headless/references/astro/templates/ecom/CartBadge.tsx +0 -84
  171. package/skills/wix-headless/references/astro/templates/ecom/CartView.tsx +0 -543
  172. package/skills/wix-headless/references/astro/templates/ecom/cart.astro +0 -13
  173. package/skills/wix-headless/references/astro/templates/ecom/components-ecom.css +0 -302
  174. package/skills/wix-headless/references/astro/templates/ecom/discounts.ts +0 -71
  175. package/skills/wix-headless/references/astro/templates/ecom/thank-you.astro +0 -24
  176. package/skills/wix-headless/references/astro/templates/gift-cards/GiftCardPurchase.tsx +0 -172
  177. package/skills/wix-headless/references/astro/templates/gift-cards/_home-teaser-snippet.astro +0 -42
  178. package/skills/wix-headless/references/astro/templates/gift-cards/_nav-snippet.astro +0 -11
  179. package/skills/wix-headless/references/astro/templates/gift-cards/components-gift-cards.css +0 -220
  180. package/skills/wix-headless/references/astro/templates/gift-cards/gift-cards.astro +0 -51
  181. package/skills/wix-headless/references/astro/templates/gift-cards/gift-cards.ts +0 -75
  182. package/skills/wix-headless/references/astro/templates/global.css +0 -288
  183. package/skills/wix-headless/references/astro/templates/index.astro +0 -56
  184. package/skills/wix-headless/references/astro/templates/stores/AddToCartButton.tsx +0 -77
  185. package/skills/wix-headless/references/astro/templates/stores/BackInStockForm.tsx +0 -176
  186. package/skills/wix-headless/references/astro/templates/stores/CategoryRail.astro +0 -69
  187. package/skills/wix-headless/references/astro/templates/stores/ProductCard.astro +0 -79
  188. package/skills/wix-headless/references/astro/templates/stores/ProductPurchase.tsx +0 -397
  189. package/skills/wix-headless/references/astro/templates/stores/SeoTags.astro +0 -21
  190. package/skills/wix-headless/references/astro/templates/stores/back-in-stock.ts +0 -67
  191. package/skills/wix-headless/references/astro/templates/stores/categories.ts +0 -155
  192. package/skills/wix-headless/references/astro/templates/stores/category/[slug].astro +0 -130
  193. package/skills/wix-headless/references/astro/templates/stores/components-stores.css +0 -400
  194. package/skills/wix-headless/references/astro/templates/stores/products/[slug].astro +0 -128
  195. package/skills/wix-headless/references/astro/templates/stores/products/index.astro +0 -101
  196. package/skills/wix-headless/references/blog/BLOG_CONTENT.md +0 -161
  197. package/skills/wix-headless/references/blog/INSTRUCTIONS.md +0 -32
  198. package/skills/wix-headless/references/bookings/FLOW.md +0 -246
  199. package/skills/wix-headless/references/bookings/INSTRUCTIONS.md +0 -95
  200. package/skills/wix-headless/references/bookings/SERVICES_DATA.md +0 -355
  201. package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +0 -176
  202. package/skills/wix-headless/references/cms/INSTRUCTIONS.md +0 -71
  203. package/skills/wix-headless/references/commands/known-apps.json +0 -6
  204. package/skills/wix-headless/references/custom/CONNECTION_PLAN.md +0 -142
  205. package/skills/wix-headless/references/custom/INSTRUCTIONS.md +0 -88
  206. package/skills/wix-headless/references/custom/blog/WIRING.md +0 -52
  207. package/skills/wix-headless/references/custom/bookings/WIRING.md +0 -197
  208. package/skills/wix-headless/references/custom/cms/WIRING.md +0 -97
  209. package/skills/wix-headless/references/custom/ecom/WIRING.md +0 -67
  210. package/skills/wix-headless/references/custom/forms/WIRING.md +0 -101
  211. package/skills/wix-headless/references/custom/stores/WIRING.md +0 -80
  212. package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +0 -59
  213. package/skills/wix-headless/references/forms/INSTRUCTIONS.md +0 -43
  214. package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +0 -51
  215. package/skills/wix-headless/references/images/INSTRUCTIONS.md +0 -357
  216. package/skills/wix-headless/references/seed-recipes.md +0 -49
  217. package/skills/wix-headless/references/shared/AGENTS_MD.md +0 -47
  218. package/skills/wix-headless/references/shared/AUTHENTICATION.md +0 -108
  219. package/skills/wix-headless/references/shared/BUILD_NOISE.md +0 -8
  220. package/skills/wix-headless/references/shared/DESIGN_MD.md +0 -84
  221. package/skills/wix-headless/references/shared/DOCS_SEARCH.md +0 -41
  222. package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +0 -186
  223. package/skills/wix-headless/references/shared/IMPLEMENTER.md +0 -206
  224. package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +0 -97
  225. package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +0 -143
  226. package/skills/wix-headless/references/shared/STYLING.md +0 -141
  227. package/skills/wix-headless/references/stores/INSTRUCTIONS.md +0 -126
  228. package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +0 -252
  229. package/skills/wix-headless/references/verticals/_schema.json +0 -74
  230. package/skills/wix-headless/references/verticals/_schema.md +0 -95
  231. package/skills/wix-headless/references/verticals/blog.md +0 -31
  232. package/skills/wix-headless/references/verticals/bookings.md +0 -41
  233. package/skills/wix-headless/references/verticals/cms.md +0 -30
  234. package/skills/wix-headless/references/verticals/ecom.md +0 -25
  235. package/skills/wix-headless/references/verticals/forms.md +0 -26
  236. package/skills/wix-headless/references/verticals/gift-cards.md +0 -24
  237. package/skills/wix-headless/references/verticals/stores.md +0 -33
  238. package/skills/wix-headless/scripts/check-manifest.mjs +0 -335
  239. package/skills/wix-headless/scripts/compose.mjs +0 -525
  240. package/skills/wix-headless/scripts/emit-design-tokens.mjs +0 -157
  241. package/skills/wix-headless/scripts/flatten.sh +0 -61
  242. package/skills/wix-headless/scripts/generate-images.mjs +0 -242
  243. package/skills/wix-headless/scripts/patch-decorative-slots.mjs +0 -148
  244. package/skills/wix-headless/scripts/plan-entity-image-waves.mjs +0 -190
  245. package/skills/wix-headless/scripts/scaffold.sh +0 -139
  246. package/skills/wix-headless/scripts/seed-utilities.sh +0 -93
  247. package/skills/wix-headless/scripts/write-decorative-json.mjs +0 -62
  248. package/skills/wix-headless/shared-utilities/analytics.ts +0 -40
  249. package/skills/wix-headless/shared-utilities/ricos.ts +0 -179
  250. package/skills/wix-headless/shared-utilities/wix-image.ts +0 -72
@@ -0,0 +1,211 @@
1
+
2
+ # Wix Members — custom login (client-only REST)
3
+
4
+ > **Source files (in this skill):** the shared transport `references/shared/wix-client.js` and this
5
+ > vertical's `references/members/wix-members-auth.js`. Copy **both** into your app's `src/rest/` side by
6
+ > side — the helper does `import { … } from "./wix-client.js"`, so they must land in the same folder.
7
+
8
+ Adds real **member accounts** to a client-only headless front end: sign up, log in, log out, and
9
+ member-gated surfaces — all from the browser over the public `WIX_CLIENT_ID`, no SDK, no backend.
10
+
11
+ **This is *custom* login: the front owns its login UI.** The member types credentials in **your own
12
+ form on your own page** (or clicks your own social button) — they are **never redirected to a
13
+ Wix-hosted login page**. This is the REST equivalent of the SDK `OAuthStrategy` recipe; the older
14
+ "redirect the user to a Wix login page" flow is deliberately **not** used here.
15
+
16
+ ## When to use
17
+ - User wants members to **sign up / log in** on a page they built (email+password, "Continue with
18
+ Google/Facebook", or custom SSO), with the login UI living in their own app.
19
+ - User wants **member-gated** content, an **account / profile** page, or a "my orders / my bookings /
20
+ my plans" view (those reads need a logged-in member).
21
+ - **Pricing-plans is present.** Buying/reading a member's plans is members-only — pair this vertical
22
+ with `pricing-plans` so the member can log in *before* the subscribe / "my subscription" surface.
23
+
24
+ ## Two mechanisms — pick by the brief (a page may use both)
25
+
26
+ | | **(A) Direct-credential** | **(B) Social / SSO** |
27
+ | :-- | :-- | :-- |
28
+ | **Use when** | your own email+password form, or custom sign-up fields | "Sign in with Google / Facebook", or enterprise SSO |
29
+ | **User leaves your page?** | **No** — silent iframe token exchange | **Yes** — full-page redirect to the provider, back to your `/callback` |
30
+ | **Kick-off** | `register()` / `login()` | `startSocialLogin(idp, callbackUri)` |
31
+ | **Finish** | automatic on `SUCCESS` | `completeSocialLogin()` on the `/callback` page |
32
+ | **Custom sign-up fields?** | Yes (`register`'s `profile`) | No — the provider owns the identity |
33
+ | **Needs an allow-listed origin/URI?** | **Yes** — your app **origin** (postMessage target) | **Yes** — your `/callback` **URL** |
34
+
35
+ Both converge on the **same end state**: member tokens written onto the shared client, so **every
36
+ later `wixApiRequest` runs as the member** (login just swaps the token set on the client you already
37
+ have). Don't blend their exchange calls — the helper handles each internally.
38
+
39
+ ## Prerequisites
40
+ 1. A Wix site with the **Members Area app installed** if you need member *profile* data (name, photo,
41
+ roles) or custom field definitions. Pure "logged-in vs. not" gating needs no app — see the
42
+ identity-vs-profile split below.
43
+ 2. The site's public headless **`WIX_CLIENT_ID`** (from the handoff prompt), pasted into
44
+ `src/rest/wix-client.js`. Public, buyer-facing credential — safe to hardcode/commit.
45
+ 3. **OAuth-app allow-listing — the #1 gotcha. Two *different* fields gate the two mechanisms:**
46
+
47
+ | Mechanism | What must be allow-listed | OAuth-app field | On `localhost:4321`? |
48
+ | :-- | :-- | :-- | :-- |
49
+ | **(A) direct-credential** | your app **origin** (the hidden iframe `postMessage`s the code back to it) | `allowedRedirectDomains` | ✅ allowed by default |
50
+ | **(B) social / SSO** | the **exact `/callback` URL** (full-page redirect target) | `allowedRedirectUris` | ❌ **must be added** |
51
+
52
+ - `localhost:4321` is Wix's default-allowed dev **origin**, so **credential login works there with zero
53
+ setup**. But social **still fails on `localhost:4321`** until you add `http://localhost:4321/callback`
54
+ to `allowedRedirectUris` — the default covers the origin, not the callback URI. (These are genuinely
55
+ two separate fields; matching Oz's SDK `wix-headless` `managed/DEPLOYMENT.md`.)
56
+ - **Any non-default origin** — your deployed domain, a different localhost port —
57
+ must be registered too: the origin (for A) and the `<origin>/callback` (for B).
58
+ - **Symptoms:** (A) unregistered origin → login hangs, then `MemberAuthError('timeout')` + a console
59
+ *"Failed to execute 'postMessage'… target origin … does not match"*. (B) unregistered callback →
60
+ the Wix authorize page shows *"Invalid redirect URI. Please add an allowed URI to your OAuth App"*
61
+ **before** returning — so the client can't catch it; you must register it up front.
62
+ - Google & Facebook are enabled by default; **custom SSO** also needs an IDP connection created in
63
+ the Business Manager, whose **connection id** you pass as `idp` (see "Custom SSO" below).
64
+
65
+ > **⚠️ AGENT: this is a manual, owner-only step you cannot do from client-only code — surface it
66
+ > proactively.** As soon as a run uses social/SSO (or deploys credential login to a non-`4321`
67
+ > origin), **tell the user** the exact URIs to add and where, and note that **social login stays
68
+ > dead until they do it** (a client-only front has no account token to register it via the API).
69
+ > See "Point the user to their dashboard" below for the deep link and the exact values.
70
+
71
+ ## The API (copy as-is; do not re-derive it)
72
+ Copy `wix-client.js` + `wix-members-auth.js` into `src/rest/` and set `WIX_CLIENT_ID`. Exports of
73
+ `wix-members-auth.js`:
74
+ - **Direct-credential:** `register(email, password, profile?)`, `login(email, password)`,
75
+ `verifyEmail(code, stateToken)`, `sendPasswordResetEmail(email, redirectUri)`
76
+ - **Social / SSO:** `startSocialLogin(idp, callbackUri, returnTo?)`, `completeSocialLogin()`, `IDP`
77
+ - **Session:** `isLoggedIn()`, `getCurrentMember()`, `logout(returnTo?)`
78
+ - **Errors:** `MemberAuthError` (`.code` + `.message`)
79
+
80
+ The full state-machine, `profile`, and error semantics are documented as JSDoc at the top of
81
+ `wix-members-auth.js`. **Read it before wiring the UI.**
82
+
83
+ > **⚠️ Copy `wix-members-auth.js` verbatim — do NOT rewrite its internals.** Wire the UI to the
84
+ > exported functions, but leave the helper's bodies alone. The OAuth request shapes are **exact and
85
+ > unforgiving** — the `createRedirectSession` body in particular needs the `auth.authRequest` wrapper,
86
+ > flat PKCE fields, and `responseType`/`scope`; "simplifying" it into a spread of the input object
87
+ > returns **400 Bad Request** and login dies. Extend by *calling* the exports, not by editing them.
88
+
89
+ ## How to wire it (UI is the project's choice — the skill ships the REST layer only)
90
+
91
+ ### (A) Credential form + the state machine — handle every branch (not just SUCCESS)
92
+ `register()` / `login()` / `verifyEmail()` resolve to `{ state, member?, stateToken? }`:
93
+ - **`"SUCCESS"`** → logged in; `member` is populated (or `null` if the Members Area app isn't
94
+ installed). Render the account UI / navigate.
95
+ - **`"REQUIRE_EMAIL_VERIFICATION"`** → a 6-digit code was emailed. Collect it and call
96
+ `verifyEmail(code, stateToken)`. (Fires when a new registrant's email already exists as a contact,
97
+ or when email verification is enabled in Member Settings.)
98
+ - **`"REQUIRE_OWNER_APPROVAL"`** → show a "pending approval" notice; not an error.
99
+
100
+ Hard failures **throw** a `MemberAuthError` — catch it and show `err.message` (`.code` is stable:
101
+ `invalidCredentials`, `emailAlreadyExists`, …). `emailAlreadyExists` on register → route to log in.
102
+
103
+ ```js
104
+ import { login, register, verifyEmail, isLoggedIn, getCurrentMember, logout, MemberAuthError } from "./rest/wix-members-auth.js";
105
+ try {
106
+ const res = await login(email, password);
107
+ if (res.state === "SUCCESS") showAccount(res.member);
108
+ else if (res.state === "REQUIRE_EMAIL_VERIFICATION") promptForCode(res.stateToken);
109
+ else if (res.state === "REQUIRE_OWNER_APPROVAL") showPending();
110
+ } catch (e) {
111
+ if (e instanceof MemberAuthError) showError(e.message); else throw e;
112
+ }
113
+ ```
114
+
115
+ Custom sign-up fields go in `register`'s `profile` (`{ firstName, lastName, nickname, addresses, … }`).
116
+ Standard fields work as-is; arbitrary `customFields` keys must first be defined in the Members Area
117
+ app or they're silently dropped.
118
+
119
+ ### (B) Social / SSO — your button + a `/callback` route
120
+ ```js
121
+ // on your login page:
122
+ import { startSocialLogin, IDP } from "./rest/wix-members-auth.js";
123
+ const callbackUri = new URL("/callback", window.location.origin).href; // must be allow-listed
124
+ onGoogleClick = () => startSocialLogin(IDP.GOOGLE, callbackUri, window.location.pathname);
125
+ onFacebookClick = () => startSocialLogin(IDP.FACEBOOK, callbackUri, window.location.pathname);
126
+
127
+ // on the /callback page (a route/page at exactly that path):
128
+ import { completeSocialLogin } from "./rest/wix-members-auth.js";
129
+ const { member, returnTo } = await completeSocialLogin();
130
+ window.location.replace(returnTo || "/");
131
+ ```
132
+ **Custom SSO:** identical call — pass your connection id as `idp` instead of `IDP.GOOGLE`:
133
+ `startSocialLogin("<connectionId>", callbackUri)`. The connection id comes from an IDP connection you
134
+ create in the Business Manager (dashboard link below).
135
+
136
+ ### Session, account area, logout
137
+ - **Gate UI** on `isLoggedIn()`. Read the member with `await getCurrentMember()` (name / photo / roles
138
+ / custom fields) — returns `null` for a visitor or when the Members Area app isn't installed.
139
+ - **Logout:** `await logout()` — clears local tokens and redirects through the Wix logout URL; the
140
+ client is a clean anonymous visitor afterward.
141
+ - **Persistence is automatic.** Member tokens live in the same `localStorage` the visitor token used,
142
+ so a reload stays logged in and tokens auto-refresh via `wix-client.js` — do **not** add your own.
143
+
144
+ ## Identity vs. profile — don't conflate them
145
+ - **Identity / auth** — sign up, log in, log out, "is this caller a member?". Native to the headless
146
+ OAuth app. **No app install needed** — every login mechanism here runs on this layer alone.
147
+ - **Member profile / Members Area** — name / photo / roles, an account page, and custom-field
148
+ definitions. Served by the **Members Area app**, which must be installed. If `getCurrentMember()`
149
+ returns `null` while `isLoggedIn()` is `true`, suspect the app isn't installed — not a code bug.
150
+
151
+ ## Hard rules (do not violate)
152
+ - ✅ **Custom login only.** The member logs in on **your** UI; never redirect them to a Wix-hosted
153
+ login page, and never use a Wix login-page redirect for member auth.
154
+ - ✅ **One shared client.** Member login swaps the token set on `wix-client.js` — reuse the same
155
+ transport for everything so the member identity (and their cart) carries across the app. Never mint
156
+ a second client or re-mint anonymously after login (it drops the member).
157
+ - ✅ **For a Wix-backed feature, use the Wix member as the identity — keep it consistent.** When the
158
+ feature is about **Wix members** (the user gave you a `WIX_CLIENT_ID`), log in and gate on the
159
+ **Wix member** (`getCurrentMember()` / the Wix member token), and key member-owned Wix rows on
160
+ `_owner` (see the `cms` vertical). Don't identify one feature's user from two different sessions —
161
+ the ids won't match.
162
+ - ❌ **Don't blend the two exchanges.** Credential login finishes via the hidden iframe; social
163
+ finishes via `/callback`. The helper keeps them separate — don't cross-wire.
164
+ - ❌ **Never fake a member.** No mock "logged-in" state, no invented profile, roles, or member counts.
165
+ Render the real member or the logged-out state.
166
+ - ✅ **Fail loudly.** The helper throws `MemberAuthError` on bad credentials, state mismatch, timeout,
167
+ and token-exchange failure — surface them; don't swallow.
168
+ - ⛔ **No `elevate` / admin scope.** A member reading their **own** data (profile, own orders /
169
+ bookings / plans) is authorized by the member token alone. There is no admin elevation on a
170
+ client-only front, and own-data reads don't need it.
171
+ - ⛔ **MFA / TOTP is not available headless.** The security layers are password + email verification +
172
+ reCAPTCHA, all **dashboard-governed** — document them as host-configurable, don't set them from code.
173
+
174
+ ## Beyond the snippets
175
+ The helper covers sign-up / login / social / SSO / logout / current-member. For a genuine gap, extend
176
+ with `wixApiRequest` — but confirm the endpoint, method, and body in the API reference first (or via
177
+ the co-installed **`wix-docs`** skill). Never guess.
178
+ - Custom login (JS SDK, concepts mirror the REST calls): https://dev.wix.com/docs/go-headless/self-managed-headless/authentication/members/custom-login-page/custom-login/custom-login-using-the-js-sdk.md
179
+ - External / social login (REST): https://dev.wix.com/docs/rest/business-management/headless-authentication/redirects/create-redirect-session
180
+ - Retrieve Tokens (REST): https://dev.wix.com/docs/rest/business-management/headless-authentication/authentication/retrieve-tokens
181
+ - Members API (read/update profile, query members): https://dev.wix.com/docs/api-reference/members/members/members-v1/introduction
182
+
183
+ ## Point the user to their dashboard
184
+ Some setup only happens in the Wix dashboard — always give the user the deep links:
185
+ - **Members Area app** (for profile data + custom fields) — `https://manage.wix.com/dashboard/{metaSiteId}/member-permissions`
186
+ - **Signup security** (email verification, owner approval, reCAPTCHA) — `Dashboard → Settings → Login & Security` / member signup settings
187
+ - **Allowed authorization redirect URIs** — the OAuth-app / **Headless Settings** for the project:
188
+ `https://manage.wix.com/dashboard/{metaSiteId}/oauth-apps-settings`. Add:
189
+ - the **app origin** (e.g. `https://myapp.example.com`) — needed for direct-credential login from a
190
+ non-default origin (`localhost:4321` is pre-allowed);
191
+ - the **exact `/callback` URL** (e.g. `https://myapp.example.com/callback`) — required for social/SSO;
192
+ - also add password-reset / logout return URLs if you use them.
193
+ Tell the user these exact values; the callback URL must match your `startSocialLogin` `callbackUri`
194
+ character-for-character.
195
+ - **Custom SSO connection** (to obtain a `connectionId`) — created under the project's IAM / SSO
196
+ settings in the Business Manager.
197
+
198
+ Substitute the site's `metaSiteId` (from the handoff / `ListWixSites`).
199
+
200
+ ## Verification checklist (before declaring done)
201
+ - [ ] `WIX_CLIENT_ID` set to the prompt's value (not the `<YOUR-CLIENT-ID>` placeholder)
202
+ - [ ] Sign-up: `register()` returns `SUCCESS` (or `REQUIRE_EMAIL_VERIFICATION` handled via `verifyEmail`)
203
+ - [ ] Log-in: `login()` reaches `SUCCESS`; wrong password shows `invalidCredentials`, not a crash
204
+ - [ ] After login `isLoggedIn()` is `true` and `getCurrentMember()` returns the member (or `null` with
205
+ the Members Area app not installed — documented, not a bug)
206
+ - [ ] A member-scoped read (e.g. pricing-plans "my plans") now returns the member's data via the same client
207
+ - [ ] Social: the button redirects to the provider and `/callback` logs the member in (callback URL
208
+ allow-listed); custom SSO works with its `connectionId` (or is flagged as pending host setup)
209
+ - [ ] Logout clears the session and the next load is a clean anonymous visitor
210
+ - [ ] No mocked member state, profile, or roles anywhere
211
+ - [ ] Told the user about any required dashboard setup (Members Area app, allowed redirect URIs) with deep links
@@ -0,0 +1,387 @@
1
+ import {
2
+ wixApiRequest,
3
+ WIX_API_BASE,
4
+ WIX_CLIENT_ID,
5
+ setSessionTokens,
6
+ clearSession,
7
+ isMember,
8
+ } from "./wix-client.js";
9
+
10
+ /**
11
+ * Custom member login for a client-only headless front end — the REST analog of
12
+ * the SDK `OAuthStrategy` (`register`/`login`/`getMemberTokensForDirectLogin`/
13
+ * `getAuthUrl`/`getMemberTokens`/`logout`). **The front owns its login UI**; the
14
+ * member never bounces to a Wix-hosted login page.
15
+ *
16
+ * Two mechanisms — pick by the brief (a page can use both):
17
+ * (A) DIRECT-CREDENTIAL — your own email+password form. `register()` / `login()`
18
+ * → on SUCCESS an INVISIBLE iframe (`web_message`) mints member tokens. No
19
+ * navigation. Supports custom sign-up fields via `profile`.
20
+ * (B) SOCIAL / SSO — your own "Continue with Google/Facebook" (or custom SSO)
21
+ * button. Full-page redirect to the provider → back to YOUR `/callback` →
22
+ * token exchange. `idp` is always a connection id (see `IDP`).
23
+ *
24
+ * All paths converge on ONE end state: member tokens written onto the shared
25
+ * client via `setSessionTokens` (wix-client.js). Member tokens are the SAME shape
26
+ * as visitor tokens, so EVERY later `wixApiRequest` (cart, orders, "my …" reads)
27
+ * now runs as the member — login just swaps the token set on the client you have.
28
+ *
29
+ * State machine — `register()`/`login()`/`verifyEmail()` return `{ state, ... }`:
30
+ * - "SUCCESS" → logged in; `{ state, member }`.
31
+ * - "REQUIRE_EMAIL_VERIFICATION" → a 6-digit code was emailed; `{ state, stateToken }`.
32
+ * Collect the code, call `verifyEmail(code, stateToken)`.
33
+ * - "REQUIRE_OWNER_APPROVAL" → membership pending owner approval; `{ state }`.
34
+ * Hard failures throw a `MemberAuthError` with a `.code` (invalidCredentials,
35
+ * emailAlreadyExists, …) — surface `.message` in your form.
36
+ *
37
+ * `profile` (register, all optional): firstName, lastName, nickname, picture,
38
+ * language, privacyStatus ("PUBLIC"|"PRIVATE"), labels[], phonesV2[], addresses[],
39
+ * company, position, birthdate, and `customFields` (name→value map). Standard
40
+ * fields work as-is; arbitrary `customFields` keys must first be DEFINED in the
41
+ * Members Area app or they're silently dropped. Social (B) can't collect any of
42
+ * these — that's the reason to pick direct-credential.
43
+ *
44
+ * Docs (curl the .md): custom login <https://dev.wix.com/docs/go-headless/self-managed-headless/authentication/members/custom-login-page/custom-login/custom-login-using-the-js-sdk.md>
45
+ * · external/social login <https://dev.wix.com/docs/rest/business-management/headless-authentication/redirects/create-redirect-session>
46
+ * · Retrieve Tokens <https://dev.wix.com/docs/rest/business-management/headless-authentication/authentication/retrieve-tokens>
47
+ */
48
+
49
+ const AUTH_BASE = "/_api/iam/authentication/v2";
50
+ const VERIFY_URL = "/_api/iam/verification/v1/auth/verify";
51
+ const RECOVERY_URL = "/_api/iam/recovery/v1/send-email";
52
+ const REDIRECT_SESSION_URL = "/_api/redirects-api/v1/redirect-session";
53
+ const CURRENT_MEMBER_URL = "/members/v1/members/my";
54
+ const OAUTH_STASH_KEY = `wix-oauth-data-${WIX_CLIENT_ID}`;
55
+
56
+ /**
57
+ * `idp` connection ids for social login. Google & Facebook are enabled by default
58
+ * on every headless client. For custom SSO / OIDC (Okta, Auth0, …) pass the
59
+ * connection id of a connection you created in the Business Manager instead.
60
+ */
61
+ export const IDP = {
62
+ GOOGLE: "0e6a50f5-b523-4e29-990d-f37fa2ffdd69",
63
+ FACEBOOK: "3ecad13f-52c3-483d-911f-31dbcf2a6d23",
64
+ };
65
+
66
+ /** Auth outcome you can show the user. `.code` is stable; `.message` is human text. */
67
+ export class MemberAuthError extends Error {
68
+ constructor(code, message) {
69
+ super(message || code);
70
+ this.name = "MemberAuthError";
71
+ this.code = code;
72
+ }
73
+ }
74
+
75
+ // ─────────────────────────── (A) direct-credential ───────────────────────────
76
+
77
+ /**
78
+ * Sign up a new member with email + password (+ optional custom `profile`).
79
+ * On SUCCESS the member is logged in (member tokens written to the shared client).
80
+ *
81
+ * @param {string} email
82
+ * @param {string} password
83
+ * @param {object} [profile] see the `profile` note in the file header
84
+ * @returns {Promise<{ state: string, member?: object|null, stateToken?: string }>}
85
+ */
86
+ export async function register(email, password, profile) {
87
+ return runCredentialFlow(`${AUTH_BASE}/register`, {
88
+ loginId: { email },
89
+ password,
90
+ ...(profile ? { profile } : {}),
91
+ });
92
+ }
93
+
94
+ /**
95
+ * Log an existing member in with email + password.
96
+ * @param {string} email
97
+ * @param {string} password
98
+ * @returns {Promise<{ state: string, member?: object|null, stateToken?: string }>}
99
+ */
100
+ export async function login(email, password) {
101
+ return runCredentialFlow(`${AUTH_BASE}/login`, { loginId: { email }, password });
102
+ }
103
+
104
+ /**
105
+ * Continue a "REQUIRE_EMAIL_VERIFICATION" flow with the 6-digit code from the email.
106
+ * @param {string} code verification code the member received by email
107
+ * @param {string} stateToken the `stateToken` returned alongside REQUIRE_EMAIL_VERIFICATION
108
+ * @returns {Promise<{ state: string, member?: object|null, stateToken?: string }>}
109
+ */
110
+ export async function verifyEmail(code, stateToken) {
111
+ let res;
112
+ try {
113
+ res = await wixApiRequest(VERIFY_URL, { body: { code, stateToken } });
114
+ } catch (e) {
115
+ throw mapAuthError(e);
116
+ }
117
+ return resolveState(res);
118
+ }
119
+
120
+ /**
121
+ * Email the member a password-reset link. Wix hosts the reset page and returns
122
+ * them to `redirectUri`, which MUST be an allow-listed authorization redirect URI
123
+ * on the OAuth app (see INSTRUCTIONS.md → dashboard links).
124
+ * @param {string} email
125
+ * @param {string} redirectUri full URL to return to after reset
126
+ */
127
+ export async function sendPasswordResetEmail(email, redirectUri) {
128
+ await wixApiRequest(RECOVERY_URL, {
129
+ body: { email, redirect: { url: redirectUri, clientId: WIX_CLIENT_ID } },
130
+ });
131
+ }
132
+
133
+ async function runCredentialFlow(path, body) {
134
+ let res;
135
+ try {
136
+ res = await wixApiRequest(path, { body });
137
+ } catch (e) {
138
+ throw mapAuthError(e);
139
+ }
140
+ return resolveState(res);
141
+ }
142
+
143
+ // On SUCCESS, finish login and return the member; otherwise hand the state (and
144
+ // stateToken, if any) back to the UI so it can prompt for verification etc.
145
+ async function resolveState(res) {
146
+ if (res.state === "SUCCESS") {
147
+ const member = await completeDirectLogin(res.sessionToken);
148
+ return { state: res.state, member };
149
+ }
150
+ return { state: res.state, stateToken: res.stateToken };
151
+ }
152
+
153
+ // The `getMemberTokensForDirectLogin` analog: turn a session token into member
154
+ // tokens via an INVISIBLE iframe (responseMode: web_message, prompt: none). The
155
+ // member never sees a page — this is the silent plumbing behind your own form.
156
+ async function completeDirectLogin(sessionToken) {
157
+ const pkce = await generatePkce();
158
+ const { fullUrl } = await createRedirectSession({
159
+ authRequest: { responseMode: "web_message", sessionToken, pkce },
160
+ });
161
+ const { code } = await authorizeViaHiddenIframe(fullUrl, pkce.state);
162
+ const tokens = await exchangeCode(code, pkce.codeVerifier);
163
+ setSessionTokens(tokens);
164
+ return getCurrentMember();
165
+ }
166
+
167
+ // ─────────────────────────── (B) social / SSO ───────────────────────────
168
+
169
+ /**
170
+ * Start a social / SSO login. Redirects the whole page to the provider; returns
171
+ * through YOUR `callbackUri`, where you must call `completeSocialLogin()`.
172
+ *
173
+ * @param {string} idp `IDP.GOOGLE` / `IDP.FACEBOOK`, or a custom SSO connection id
174
+ * @param {string} callbackUri your `/callback` URL — MUST be an allow-listed
175
+ * authorization redirect URI on the OAuth app (see INSTRUCTIONS.md)
176
+ * @param {string} [returnTo="/"] where to send the member after login completes
177
+ */
178
+ export async function startSocialLogin(idp, callbackUri, returnTo = "/") {
179
+ const pkce = await generatePkce();
180
+ const { fullUrl } = await createRedirectSession({
181
+ authRequest: { responseMode: "fragment", idp, redirectUri: callbackUri, pkce },
182
+ });
183
+ // The callback runs on a separate page load — carry what the exchange needs.
184
+ window.localStorage.setItem(
185
+ OAUTH_STASH_KEY,
186
+ JSON.stringify({ codeVerifier: pkce.codeVerifier, state: pkce.state, redirectUri: callbackUri, returnTo }),
187
+ );
188
+ window.location.href = fullUrl;
189
+ }
190
+
191
+ /**
192
+ * Finish a social / SSO login on the `/callback` page: reads `#code`/`#state`
193
+ * from the URL, verifies state, exchanges the code, logs the member in.
194
+ * @returns {Promise<{ member: object|null, returnTo: string }>}
195
+ */
196
+ export async function completeSocialLogin() {
197
+ const raw = window.localStorage.getItem(OAUTH_STASH_KEY);
198
+ if (!raw) throw new MemberAuthError("missingOAuthData", "No pending login found — start the login again.");
199
+ window.localStorage.removeItem(OAUTH_STASH_KEY);
200
+ const stash = JSON.parse(raw);
201
+
202
+ const params = new URLSearchParams(window.location.hash.replace(/^#/, ""));
203
+ const error = params.get("error");
204
+ if (error) throw new MemberAuthError(error, params.get("error_description") || error);
205
+ const code = params.get("code");
206
+ const state = params.get("state");
207
+ if (!code || !state) throw new MemberAuthError("missingCode", "No authorization code returned.");
208
+ if (state !== stash.state) throw new MemberAuthError("stateMismatch", "OAuth state mismatch — possible CSRF; login aborted.");
209
+
210
+ const tokens = await exchangeCode(code, stash.codeVerifier, stash.redirectUri);
211
+ setSessionTokens(tokens);
212
+ return { member: await getCurrentMember(), returnTo: stash.returnTo || "/" };
213
+ }
214
+
215
+ // ─────────────────────────── session ───────────────────────────
216
+
217
+ /** True once a member is logged in on this client. Gate account UI on it. */
218
+ export function isLoggedIn() {
219
+ return isMember();
220
+ }
221
+
222
+ /**
223
+ * Read the logged-in member (name / photo / roles / custom fields). Needs the
224
+ * **Members Area app** installed; returns `null` for an anonymous visitor or when
225
+ * the app isn't installed (that's setup, not a code bug — see INSTRUCTIONS.md).
226
+ * @returns {Promise<object|null>}
227
+ */
228
+ export async function getCurrentMember() {
229
+ try {
230
+ const res = await wixApiRequest(CURRENT_MEMBER_URL, { method: "GET", query: { fieldSet: "FULL" } });
231
+ return res?.member || null;
232
+ } catch (e) {
233
+ if (e.status === 401 || e.status === 403 || e.status === 404) return null;
234
+ throw e;
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Log the member out: clears the local session immediately, then redirects
240
+ * through the Wix logout URL so the server session is dropped too. After this the
241
+ * client is a clean anonymous visitor again.
242
+ * @param {string} [returnTo] where to land after logout (defaults to the origin)
243
+ */
244
+ export async function logout(returnTo) {
245
+ const postFlowUrl = returnTo || (typeof window !== "undefined" ? window.location.origin : "/");
246
+ let logoutUrl;
247
+ try {
248
+ const { redirectSession } = await wixApiRequest(REDIRECT_SESSION_URL, {
249
+ body: { logout: { clientId: WIX_CLIENT_ID }, callbacks: { postFlowUrl } },
250
+ });
251
+ logoutUrl = redirectSession?.fullUrl;
252
+ } finally {
253
+ clearSession(); // drop local tokens even if building the logout URL failed
254
+ }
255
+ if (logoutUrl && typeof window !== "undefined") window.location.href = logoutUrl;
256
+ }
257
+
258
+ // ─────────────────────────── internals ───────────────────────────
259
+
260
+ async function createRedirectSession({ authRequest }) {
261
+ const { responseMode, sessionToken, idp, redirectUri, pkce } = authRequest;
262
+ // ⚠️ EXACT Wix wire shape — do NOT simplify or inline this. It is NOT the input
263
+ // object: the request needs the `auth.authRequest` wrapper, the PKCE fields FLAT
264
+ // (`codeChallenge` + `codeChallengeMethod`, not a nested `pkce` object; the
265
+ // `codeVerifier` is NOT sent here — it's kept for the token exchange), and
266
+ // `responseType`/`scope` present. Spreading the input straight onto the body →
267
+ // 400 Bad Request.
268
+ const { redirectSession } = await wixApiRequest(REDIRECT_SESSION_URL, {
269
+ body: {
270
+ auth: {
271
+ authRequest: {
272
+ clientId: WIX_CLIENT_ID,
273
+ codeChallenge: pkce.codeChallenge,
274
+ codeChallengeMethod: "S256",
275
+ responseMode,
276
+ responseType: "code",
277
+ scope: "offline_access",
278
+ state: pkce.state,
279
+ ...(redirectUri ? { redirectUri } : {}),
280
+ ...(sessionToken ? { sessionToken } : {}),
281
+ ...(idp ? { idp } : {}),
282
+ },
283
+ },
284
+ },
285
+ });
286
+ if (!redirectSession?.fullUrl) throw new MemberAuthError("noRedirectSession", "Wix did not return an authorization URL.");
287
+ return { fullUrl: redirectSession.fullUrl };
288
+ }
289
+
290
+ // Open the authorize URL in a hidden iframe and wait for the web_message postMessage
291
+ // carrying { code, state }. Matches @wix/sdk iframeUtils.addPostMessageListener.
292
+ function authorizeViaHiddenIframe(authUrl, expectedState) {
293
+ return new Promise((resolve, reject) => {
294
+ const iframe = document.createElement("iframe");
295
+ iframe.style.display = "none";
296
+ let settled = false;
297
+ const cleanup = () => {
298
+ window.removeEventListener("message", onMessage);
299
+ clearTimeout(timer);
300
+ if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
301
+ };
302
+ const onMessage = (e) => {
303
+ if (!e.data || e.data.state !== expectedState) return; // not our message
304
+ settled = true;
305
+ cleanup();
306
+ if (e.data.error) reject(new MemberAuthError(e.data.error, e.data.error_description || e.data.error));
307
+ else resolve({ code: e.data.code, state: e.data.state });
308
+ };
309
+ window.addEventListener("message", onMessage);
310
+ const timer = setTimeout(() => {
311
+ if (!settled) {
312
+ cleanup();
313
+ // The overwhelmingly common cause: this app's ORIGIN is not an allowed
314
+ // authorization redirect URI on the OAuth app, so the browser silently
315
+ // blocks the iframe's postMessage (check the console for a "target origin
316
+ // … does not match" error). Register the origin — see INSTRUCTIONS.md.
317
+ reject(new MemberAuthError(
318
+ "timeout",
319
+ `Login timed out. Most likely this app's origin (${typeof window !== "undefined" ? window.location.origin : "?"}) ` +
320
+ `is not an allowed authorization redirect URI on the Wix OAuth app — register it in the site's Headless Settings.`,
321
+ ));
322
+ }
323
+ }, 120000);
324
+ iframe.src = authUrl;
325
+ document.body.appendChild(iframe);
326
+ });
327
+ }
328
+
329
+ // Exchange an authorization code for member tokens. No Authorization header — this
330
+ // is the token-minting endpoint (mirrors the SDK's fetchTokens). `redirectUri` is
331
+ // sent only for social (it must match the redirect-session's redirectUri).
332
+ async function exchangeCode(code, codeVerifier, redirectUri) {
333
+ if (!code) throw new MemberAuthError("missingCode", "No authorization code to exchange.");
334
+ const res = await fetch(`${WIX_API_BASE}/oauth2/token`, {
335
+ method: "POST",
336
+ headers: { "Content-Type": "application/json" },
337
+ body: JSON.stringify({
338
+ clientId: WIX_CLIENT_ID,
339
+ grantType: "authorization_code",
340
+ code,
341
+ codeVerifier,
342
+ ...(redirectUri ? { redirectUri } : {}),
343
+ }),
344
+ });
345
+ if (!res.ok) {
346
+ let body;
347
+ try { body = await res.json(); } catch { /* ignore */ }
348
+ throw new MemberAuthError(body?.error || "tokenExchangeFailed", `Token exchange failed: ${res.status}${body ? ` ${JSON.stringify(body)}` : ""}`);
349
+ }
350
+ const data = await res.json();
351
+ return { accessToken: data.access_token, refreshToken: data.refresh_token, expiresIn: data.expires_in };
352
+ }
353
+
354
+ function mapAuthError(e) {
355
+ const code = e?.body?.details?.applicationError?.code;
356
+ // Email already exists (register) — route them to log in.
357
+ if (code === "-19995" || e?.status === 409) {
358
+ return new MemberAuthError("emailAlreadyExists", "An account with this email already exists — try logging in instead.");
359
+ }
360
+ // Bad credentials: -19999 identity-not-found (404), -19976 wrong-password (401).
361
+ if (code === "-19999" || code === "-19976" || e?.status === 404 || e?.status === 401) {
362
+ return new MemberAuthError("invalidCredentials", "Incorrect email or password.");
363
+ }
364
+ if (e instanceof MemberAuthError) return e;
365
+ return e; // unknown — bubble the raw transport error (fail loudly)
366
+ }
367
+
368
+ // ── PKCE (RFC 7636, S256) — browser Web Crypto, dependency-free ──
369
+ async function generatePkce() {
370
+ const codeVerifier = randomUnreserved(43);
371
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(codeVerifier));
372
+ return { codeVerifier, codeChallenge: base64Url(new Uint8Array(digest)), state: randomUnreserved(24) };
373
+ }
374
+
375
+ function randomUnreserved(length) {
376
+ const mask = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~";
377
+ const bytes = crypto.getRandomValues(new Uint8Array(length));
378
+ let out = "";
379
+ for (let i = 0; i < length; i++) out += mask[bytes[i] % mask.length];
380
+ return out;
381
+ }
382
+
383
+ function base64Url(bytes) {
384
+ let binary = "";
385
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
386
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
387
+ }