Setting the file. One moment.
Wix Client · Wix Vibe Headless · wix/skills · Skills Docs
ContentsBack to the top of the page Wix Blog
references/shared/app/rest/ wix-client.js
JavaScript · 184 lines · 6 KB
from
"./wix-config.js"
;
14
15 export const WIX_API_BASE = "https://www.wixapis.com" ;
16 const OAUTH_TOKEN_URL = `${ WIX_API_BASE }/oauth2/token` ;
17
18 // Scope the storage key by client id so two headless sites served from the same
19 // origin (e.g. localhost:4321 across projects) don't share one visitor token —
20 // which would load site A's token for site B and mix up carts/identity.
21 const TOKEN_STORAGE_KEY = `wix-visitor-token-${ WIX_CLIENT_ID }` ;
22 let tokenCache = null ;
23
24 function loadToken () {
25 if (tokenCache) return tokenCache;
26 if ( typeof window === "undefined" ) return null ;
27 try {
28 const raw = window.localStorage. getItem ( TOKEN_STORAGE_KEY );
29 if (raw) tokenCache = JSON . parse (raw);
30 } catch {
31 /* ignore disabled/full storage */
32 }
33 return tokenCache;
34 }
35
36 function saveToken ( t ) {
37 tokenCache = t;
38 if ( typeof window === "undefined" ) return ;
39 try {
40 window.localStorage. setItem ( TOKEN_STORAGE_KEY , JSON . stringify (t));
41 } catch {
42 /* ignore */
43 }
44 }
45
46 async function mintToken ( body ) {
47 const res = await fetch ( OAUTH_TOKEN_URL , {
48 method: "POST" ,
49 headers: { "Content-Type" : "application/json" },
50 body: JSON . stringify (body),
51 });
52 if ( ! res.ok) throw new Error ( `Wix OAuth failed: ${ res . status }` );
53 const data = await res. json ();
54 return {
55 accessToken: data.access_token,
56 refreshToken: data.refresh_token,
57 expiresAt: Date. now () + data.expires_in * 1000 ,
58 };
59 }
60
61 /**
62 * Log a member in on THIS client by swapping the persisted token set for the
63 * member's tokens (see references/members/). Member tokens are the SAME shape as
64 * visitor tokens and refresh via the same `refresh_token` grant — logging in just
65 * replaces the set the client already carries, so EVERY subsequent `wixApiRequest`
66 * (cart, orders, bookings, "my …" reads) now runs as the member. Called by the
67 * members helper after a successful credential / social / SSO login.
68 *
69 * @param {{ accessToken: string, refreshToken: string, expiresIn: number }} tokens
70 */
71 export function setSessionTokens ({ accessToken , refreshToken , expiresIn }) {
72 saveToken ({
73 accessToken,
74 refreshToken,
75 expiresAt: Date. now () + expiresIn * 1000 ,
76 role: "member" ,
77 });
78 }
79
80 /**
81 * Drop the persisted session so the next call mints a FRESH anonymous visitor.
82 * Call on logout — otherwise the dead member token lingers and reads fail.
83 */
84 export function clearSession () {
85 tokenCache = null ;
86 if ( typeof window === "undefined" ) return ;
87 try {
88 window.localStorage. removeItem ( TOKEN_STORAGE_KEY );
89 } catch {
90 /* ignore */
91 }
92 }
93
94 /**
95 * True once a member is logged in on this client (vs. an anonymous visitor).
96 * Gate account UI on this, or on the members helper's `isLoggedIn()`.
97 * @returns {boolean}
98 */
99 export function isMember () {
100 return loadToken ()?.role === "member" ;
101 }
102
103 /**
104 * Get a visitor access token from the client id.
105 *
106 * The visitor token IS the identity of the Wix "current cart", so we persist the
107 * refresh token to localStorage and REFRESH it on expiry — re-minting a fresh
108 * `anonymous` token would create a NEW visitor and silently empty the cart on
109 * every reload / after the 4h token lifetime. Anonymous mint happens only once
110 * (or when no refresh token is stored).
111 */
112 async function getAccessToken () {
113 const cached = loadToken ();
114 if (cached && cached.expiresAt > Date. now () + 60_000 ) return cached.accessToken;
115
116 if (cached?.refreshToken) {
117 try {
118 const refreshed = await mintToken ({ clientId: WIX_CLIENT_ID , grantType: "refresh_token" , refreshToken: cached.refreshToken });
119 // Refreshing preserves identity: a member refresh token yields member tokens.
120 refreshed.role = cached.role || "visitor" ;
121 saveToken (refreshed);
122 return refreshed.accessToken;
123 } catch {
124 /* refresh failed — fall through to a fresh anonymous visitor */
125 }
126 }
127 const fresh = await mintToken ({ clientId: WIX_CLIENT_ID , grantType: "anonymous" });
128 fresh.role = "visitor" ;
129 saveToken (fresh);
130 return fresh.accessToken;
131 }
132
133 /**
134 * Core transport — mirrors storefrontApiRequest. Adds the Authorization header,
135 * resolves the path against the Wix API base, parses JSON, surfaces errors.
136 *
137 * @param {string} path
138 * @param {{ method?: "GET"|"POST"|"PUT"|"DELETE", body?: unknown, query?: Record<string, string | undefined> }} [options]
139 */
140 export async function wixApiRequest ( path , options = {}) {
141 const { method = "POST" , body , query } = options;
142 const token = await getAccessToken ();
143
144 const url = new URL (path. startsWith ( "http" ) ? path : `${ WIX_API_BASE }${ path }` );
145 if (query) {
146 for ( const [ k , v ] of Object. entries (query)) {
147 if (v === undefined ) continue ;
148 if (Array. isArray (v)) {
149 for ( const item of v) url.searchParams. append (k, item);
150 } else {
151 url.searchParams. set (k, v);
152 }
153 }
154 }
155
156 const res = await fetch (url. toString (), {
157 method,
158 headers: {
159 "Content-Type" : "application/json" ,
160 Authorization: token, // Wix expects the raw access token (no "Bearer " prefix)
161 },
162 body: body !== undefined ? JSON . stringify (body) : undefined ,
163 });
164
165 if (res.status === 402 ) {
166 // This API requires an active plan / premium feature on the site.
167 console. warn ( "Wix: Payment required (402) — this API needs an active plan/premium feature." );
168 return ;
169 }
170 if ( ! res.ok) {
171 const text = await res. text (). catch (() => "" );
172 let parsed;
173 try { parsed = JSON . parse (text); } catch { parsed = text; }
174 // Attach status + parsed body so callers (e.g. the members helper) can map a
175 // meaningful auth outcome (404 = bad credentials, 409 = email exists) to a
176 // friendly message instead of parsing the message string.
177 const err = new Error ( `Wix API error ${ res . status }: ${ text }` );
178 err.status = res.status;
179 err.body = parsed;
180 throw err;
181 }
182 if (res.status === 204 ) return undefined ;
183 return await res. json ();
184 }