@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.
- package/README.md +4 -1
- package/package.json +1 -1
- package/skills/README.md +23 -0
- package/skills/wix-app/SKILL.md +10 -2
- package/skills/wix-app/references/AUTO_PATTERNS_DASHBOARD.md +326 -0
- package/skills/wix-app/references/CODE_QUALITY.md +1 -0
- package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +124 -115
- package/skills/wix-app/references/DASHBOARD_PAGE.md +3 -0
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +23 -9
- package/skills/wix-app/references/SITE_PLUGIN.md +1 -1
- package/skills/wix-app/references/auto-patterns-dashboard/action-cell.md +115 -0
- package/skills/wix-app/references/auto-patterns-dashboard/app-config-structure.md +264 -0
- package/skills/wix-app/references/auto-patterns-dashboard/app-context.md +37 -0
- package/skills/wix-app/references/auto-patterns-dashboard/bulk-actions.md +86 -0
- package/skills/wix-app/references/auto-patterns-dashboard/collection-page-actions.md +119 -0
- package/skills/wix-app/references/auto-patterns-dashboard/collection-page.md +103 -0
- package/skills/wix-app/references/auto-patterns-dashboard/custom-actions-override.md +76 -0
- package/skills/wix-app/references/auto-patterns-dashboard/custom-columns-override.md +82 -0
- package/skills/wix-app/references/auto-patterns-dashboard/custom-components-override.md +89 -0
- package/skills/wix-app/references/auto-patterns-dashboard/custom-header-override.md +124 -0
- package/skills/wix-app/references/auto-patterns-dashboard/custom-sections-override.md +91 -0
- package/skills/wix-app/references/auto-patterns-dashboard/custom-slots-override.md +71 -0
- package/skills/wix-app/references/auto-patterns-dashboard/entity-page-actions.md +70 -0
- package/skills/wix-app/references/auto-patterns-dashboard/entity-page-view-actions.md +73 -0
- package/skills/wix-app/references/auto-patterns-dashboard/entity-page.md +98 -0
- package/skills/wix-app/references/auto-patterns-dashboard/example-patterns.json +278 -0
- package/skills/wix-app/references/auto-patterns-dashboard/pages-configuration.md +94 -0
- package/skills/wix-app/references/auto-patterns-dashboard/resolved-action.md +43 -0
- package/skills/wix-app/references/auto-patterns-dashboard/sdk-utilities.md +66 -0
- package/skills/wix-app/references/auto-patterns-dashboard/views.md +76 -0
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +1 -14
- package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +54 -29
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONFIGURATION.md +2 -2
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +17 -105
- package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +127 -0
- package/skills/wix-app/references/editor-react-component/PARTS.md +2 -1
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +15 -15
- package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +25 -53
- package/skills/wix-app/scripts/generate-auto-patterns.js +510 -0
- package/skills/wix-auth/SKILL.md +12 -0
- package/skills/wix-auth/references/device-flow.md +140 -0
- package/skills/wix-docs/SKILL.md +188 -0
- package/skills/wix-docs/references/API_SPEC_SEARCH.md +213 -0
- package/skills/wix-docs/references/EXTRACTING.md +120 -0
- package/skills/wix-headless/SKILL.md +84 -130
- package/skills/wix-headless/entry/bootstrap.mjs +93 -0
- package/skills/wix-headless/entry/skill.md +78 -0
- package/skills/wix-headless/references/AI_FEATURES.md +178 -0
- package/skills/wix-headless/references/CAPABILITIES.md +84 -0
- package/skills/wix-headless/references/DISCOVERY.md +34 -56
- package/skills/wix-headless/references/DOC_DISCOVERY.md +37 -0
- package/skills/wix-headless/references/FEEDBACK.md +118 -0
- package/skills/wix-headless/references/IMAGE_GENERATION.md +69 -0
- package/skills/wix-headless/references/SDK_HANDOFF.md +97 -0
- package/skills/wix-headless/references/SEED.md +44 -188
- package/skills/wix-headless/references/SETUP.md +32 -103
- package/skills/wix-headless/references/astro.md +98 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-blog.md +196 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +236 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-bookings.md +279 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-cms.md +163 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-events.md +203 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-forms.md +222 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-astro.md +124 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-custom-login.md +160 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-non-astro.md +118 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-portfolio.md +132 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-pricing-plans.md +124 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-orders.md +188 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-reservations.md +173 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurants.md +161 -0
- package/skills/wix-headless/references/inline-recipes/setup-blog.md +175 -0
- package/skills/wix-headless/references/inline-recipes/setup-bookings.md +228 -0
- package/skills/wix-headless/references/inline-recipes/setup-cms.md +200 -0
- package/skills/wix-headless/references/inline-recipes/setup-events.md +196 -0
- package/skills/wix-headless/references/inline-recipes/setup-forms.md +174 -0
- package/skills/wix-headless/references/inline-recipes/setup-online-store.md +176 -0
- package/skills/wix-headless/references/inline-recipes/setup-portfolio.md +153 -0
- package/skills/wix-headless/references/inline-recipes/setup-pricing-plans.md +229 -0
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-orders.md +161 -0
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-reservations.md +117 -0
- package/skills/wix-headless/references/inline-recipes/setup-restaurants.md +152 -0
- package/skills/wix-headless/references/managed/AUTHENTICATION.md +57 -0
- package/skills/wix-headless/references/managed/CONNECT.md +74 -0
- package/skills/wix-headless/references/managed/CREATE.md +130 -0
- package/skills/wix-headless/references/managed/DEPLOYMENT.md +57 -0
- package/skills/wix-headless/references/non-astro.md +59 -0
- package/skills/wix-headless/references/self-managed/AUTHENTICATION.md +22 -0
- package/skills/wix-headless/references/self-managed/DEPLOYMENT.md +7 -0
- package/skills/wix-headless/references/stripe/AUTHENTICATION.md +55 -0
- package/skills/wix-headless/references/stripe/DEPLOYMENT.md +63 -0
- package/skills/wix-manage/SKILL.md +24 -1
- package/skills/wix-manage/references/analytics/query-site-analytics.md +241 -0
- package/skills/wix-manage/references/bookings/create-and-update-booking-services.md +1 -1
- package/skills/wix-manage/references/bookings/diagnose-availability-issues.md +265 -0
- package/skills/wix-manage/references/marketing/create-and-publish-social-post.md +436 -0
- package/skills/wix-manage/references/marketing/generate-and-publish-marketing-plan.md +209 -0
- package/skills/wix-manage/references/rich-content/author-ricos-rich-content.md +249 -0
- package/skills/wix-manage/references/stores/create-product-with-options-catalog-v3.md +51 -0
- package/skills/wix-vibe-headless/SKILL.md +136 -0
- package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +128 -0
- package/skills/wix-vibe-headless/references/blog/wix-blog.js +217 -0
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +153 -0
- package/skills/wix-vibe-headless/references/bookings/wix-bookings-checkout.js +147 -0
- package/skills/wix-vibe-headless/references/bookings/wix-bookings-services.js +215 -0
- package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +154 -0
- package/skills/wix-vibe-headless/references/cms/wix-cms.js +186 -0
- package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +142 -0
- package/skills/wix-vibe-headless/references/events/wix-events-browse.js +135 -0
- package/skills/wix-vibe-headless/references/events/wix-events-registration.js +187 -0
- package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +211 -0
- package/skills/wix-vibe-headless/references/members/wix-members-auth.js +387 -0
- package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +111 -0
- package/skills/wix-vibe-headless/references/portfolio/wix-portfolio.js +194 -0
- package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +127 -0
- package/skills/wix-vibe-headless/references/pricing-plans/wix-pricing-plans.js +157 -0
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +138 -0
- package/skills/wix-vibe-headless/references/restaurants/wix-restaurants-menu.js +224 -0
- package/skills/wix-vibe-headless/references/restaurants/wix-restaurants-ordering.js +156 -0
- package/skills/wix-vibe-headless/references/restaurants/wix-restaurants-reservations.js +107 -0
- package/skills/wix-vibe-headless/references/shared/wix-client.js +186 -0
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +155 -0
- package/skills/wix-vibe-headless/references/storefront/wix-store-cart.js +147 -0
- package/skills/wix-vibe-headless/references/storefront/wix-store-catalog.js +142 -0
- package/skills/wix-headless/references/BUILD-astro.md +0 -244
- package/skills/wix-headless/references/BUILD-own-build.md +0 -167
- package/skills/wix-headless/references/BUILD.md +0 -105
- package/skills/wix-headless/references/DESIGN_SYSTEM.md +0 -165
- package/skills/wix-headless/references/DISCOVERY-connect.md +0 -55
- package/skills/wix-headless/references/DISCOVERY-create.md +0 -238
- package/skills/wix-headless/references/PLAN-connect.md +0 -19
- package/skills/wix-headless/references/PLAN-create.md +0 -17
- package/skills/wix-headless/references/PLAN.md +0 -92
- package/skills/wix-headless/references/astro/blog/BLOG_PAGES.md +0 -300
- package/skills/wix-headless/references/astro/blog/BLOG_SETUP.md +0 -218
- package/skills/wix-headless/references/astro/bookings/COMPONENTS.md +0 -45
- package/skills/wix-headless/references/astro/bookings/SERVICES_PAGES.md +0 -77
- package/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md +0 -214
- package/skills/wix-headless/references/astro/cms/FAQ_KNOWLEDGE_BASE.md +0 -231
- package/skills/wix-headless/references/astro/cms/PORTFOLIO.md +0 -453
- package/skills/wix-headless/references/astro/cms/RESOURCE_LIBRARY.md +0 -354
- package/skills/wix-headless/references/astro/cms/TEAM_DIRECTORY.md +0 -299
- package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +0 -351
- package/skills/wix-headless/references/astro/ecom/CART_PAGES.md +0 -119
- package/skills/wix-headless/references/astro/ecom/CART_WIRING.md +0 -103
- package/skills/wix-headless/references/astro/ecom/COMPONENTS_CSS.md +0 -132
- package/skills/wix-headless/references/astro/forms/CONTACT_FORM.md +0 -463
- package/skills/wix-headless/references/astro/gift-cards/COMPONENTS.md +0 -119
- package/skills/wix-headless/references/astro/gift-cards/PAGES.md +0 -129
- package/skills/wix-headless/references/astro/stores/BACK_IN_STOCK.md +0 -178
- package/skills/wix-headless/references/astro/stores/CATEGORY_PAGES.md +0 -85
- package/skills/wix-headless/references/astro/stores/COMPONENTS_CSS.md +0 -129
- package/skills/wix-headless/references/astro/stores/HOME_AND_NAV.md +0 -229
- package/skills/wix-headless/references/astro/stores/PRODUCT_PAGES.md +0 -215
- package/skills/wix-headless/references/astro/stores/SHARED_WIRING.md +0 -165
- package/skills/wix-headless/references/astro/templates/Footer.astro +0 -27
- package/skills/wix-headless/references/astro/templates/Layout.astro +0 -132
- package/skills/wix-headless/references/astro/templates/Navigation.astro +0 -38
- package/skills/wix-headless/references/astro/templates/astro.config.mjs +0 -41
- package/skills/wix-headless/references/astro/templates/bookings/AvailabilityCalendar.tsx +0 -374
- package/skills/wix-headless/references/astro/templates/bookings/BookingForm.tsx +0 -136
- package/skills/wix-headless/references/astro/templates/bookings/SeoTags.astro +0 -25
- package/skills/wix-headless/references/astro/templates/bookings/ServiceBookingFlow.tsx +0 -71
- package/skills/wix-headless/references/astro/templates/bookings/ServiceCard.astro +0 -54
- package/skills/wix-headless/references/astro/templates/bookings/booking-confirmation.astro +0 -49
- package/skills/wix-headless/references/astro/templates/bookings/bookingDriver.ts +0 -245
- package/skills/wix-headless/references/astro/templates/bookings/components-bookings.css +0 -424
- package/skills/wix-headless/references/astro/templates/bookings/services/[slug].astro +0 -176
- package/skills/wix-headless/references/astro/templates/bookings/services/index.astro +0 -181
- package/skills/wix-headless/references/astro/templates/ecom/CartBadge.tsx +0 -84
- package/skills/wix-headless/references/astro/templates/ecom/CartView.tsx +0 -543
- package/skills/wix-headless/references/astro/templates/ecom/cart.astro +0 -13
- package/skills/wix-headless/references/astro/templates/ecom/components-ecom.css +0 -302
- package/skills/wix-headless/references/astro/templates/ecom/discounts.ts +0 -71
- package/skills/wix-headless/references/astro/templates/ecom/thank-you.astro +0 -24
- package/skills/wix-headless/references/astro/templates/gift-cards/GiftCardPurchase.tsx +0 -172
- package/skills/wix-headless/references/astro/templates/gift-cards/_home-teaser-snippet.astro +0 -42
- package/skills/wix-headless/references/astro/templates/gift-cards/_nav-snippet.astro +0 -11
- package/skills/wix-headless/references/astro/templates/gift-cards/components-gift-cards.css +0 -220
- package/skills/wix-headless/references/astro/templates/gift-cards/gift-cards.astro +0 -51
- package/skills/wix-headless/references/astro/templates/gift-cards/gift-cards.ts +0 -75
- package/skills/wix-headless/references/astro/templates/global.css +0 -288
- package/skills/wix-headless/references/astro/templates/index.astro +0 -56
- package/skills/wix-headless/references/astro/templates/stores/AddToCartButton.tsx +0 -77
- package/skills/wix-headless/references/astro/templates/stores/BackInStockForm.tsx +0 -176
- package/skills/wix-headless/references/astro/templates/stores/CategoryRail.astro +0 -69
- package/skills/wix-headless/references/astro/templates/stores/ProductCard.astro +0 -79
- package/skills/wix-headless/references/astro/templates/stores/ProductPurchase.tsx +0 -397
- package/skills/wix-headless/references/astro/templates/stores/SeoTags.astro +0 -21
- package/skills/wix-headless/references/astro/templates/stores/back-in-stock.ts +0 -67
- package/skills/wix-headless/references/astro/templates/stores/categories.ts +0 -155
- package/skills/wix-headless/references/astro/templates/stores/category/[slug].astro +0 -130
- package/skills/wix-headless/references/astro/templates/stores/components-stores.css +0 -400
- package/skills/wix-headless/references/astro/templates/stores/products/[slug].astro +0 -128
- package/skills/wix-headless/references/astro/templates/stores/products/index.astro +0 -101
- package/skills/wix-headless/references/blog/BLOG_CONTENT.md +0 -161
- package/skills/wix-headless/references/blog/INSTRUCTIONS.md +0 -32
- package/skills/wix-headless/references/bookings/FLOW.md +0 -246
- package/skills/wix-headless/references/bookings/INSTRUCTIONS.md +0 -95
- package/skills/wix-headless/references/bookings/SERVICES_DATA.md +0 -355
- package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +0 -176
- package/skills/wix-headless/references/cms/INSTRUCTIONS.md +0 -71
- package/skills/wix-headless/references/commands/known-apps.json +0 -6
- package/skills/wix-headless/references/custom/CONNECTION_PLAN.md +0 -142
- package/skills/wix-headless/references/custom/INSTRUCTIONS.md +0 -88
- package/skills/wix-headless/references/custom/blog/WIRING.md +0 -52
- package/skills/wix-headless/references/custom/bookings/WIRING.md +0 -197
- package/skills/wix-headless/references/custom/cms/WIRING.md +0 -97
- package/skills/wix-headless/references/custom/ecom/WIRING.md +0 -67
- package/skills/wix-headless/references/custom/forms/WIRING.md +0 -101
- package/skills/wix-headless/references/custom/stores/WIRING.md +0 -80
- package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +0 -59
- package/skills/wix-headless/references/forms/INSTRUCTIONS.md +0 -43
- package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +0 -51
- package/skills/wix-headless/references/images/INSTRUCTIONS.md +0 -357
- package/skills/wix-headless/references/seed-recipes.md +0 -49
- package/skills/wix-headless/references/shared/AGENTS_MD.md +0 -47
- package/skills/wix-headless/references/shared/AUTHENTICATION.md +0 -108
- package/skills/wix-headless/references/shared/BUILD_NOISE.md +0 -8
- package/skills/wix-headless/references/shared/DESIGN_MD.md +0 -84
- package/skills/wix-headless/references/shared/DOCS_SEARCH.md +0 -41
- package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +0 -186
- package/skills/wix-headless/references/shared/IMPLEMENTER.md +0 -206
- package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +0 -97
- package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +0 -143
- package/skills/wix-headless/references/shared/STYLING.md +0 -141
- package/skills/wix-headless/references/stores/INSTRUCTIONS.md +0 -126
- package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +0 -252
- package/skills/wix-headless/references/verticals/_schema.json +0 -74
- package/skills/wix-headless/references/verticals/_schema.md +0 -95
- package/skills/wix-headless/references/verticals/blog.md +0 -31
- package/skills/wix-headless/references/verticals/bookings.md +0 -41
- package/skills/wix-headless/references/verticals/cms.md +0 -30
- package/skills/wix-headless/references/verticals/ecom.md +0 -25
- package/skills/wix-headless/references/verticals/forms.md +0 -26
- package/skills/wix-headless/references/verticals/gift-cards.md +0 -24
- package/skills/wix-headless/references/verticals/stores.md +0 -33
- package/skills/wix-headless/scripts/check-manifest.mjs +0 -335
- package/skills/wix-headless/scripts/compose.mjs +0 -525
- package/skills/wix-headless/scripts/emit-design-tokens.mjs +0 -157
- package/skills/wix-headless/scripts/flatten.sh +0 -61
- package/skills/wix-headless/scripts/generate-images.mjs +0 -242
- package/skills/wix-headless/scripts/patch-decorative-slots.mjs +0 -148
- package/skills/wix-headless/scripts/plan-entity-image-waves.mjs +0 -190
- package/skills/wix-headless/scripts/scaffold.sh +0 -139
- package/skills/wix-headless/scripts/seed-utilities.sh +0 -93
- package/skills/wix-headless/scripts/write-decorative-json.mjs +0 -62
- package/skills/wix-headless/shared-utilities/analytics.ts +0 -40
- package/skills/wix-headless/shared-utilities/ricos.ts +0 -179
- 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
|
+
}
|