Construire sur Wix à partir de Base44
Le connecteur Wix est connecté ; c'est ainsi que l'app se construit dessus — rassembler le contexte du site, trouver les APIs et apprendre leurs contrats depuis la doc, écrire le code. Découvrir tout : endpoints, chemins, URLs de doc, champs de requête et de réponse proviennent tous des appels ci-dessous, jamais de la mémoire ou du pattern. 404 ou vide ⇒ découvrir, pas permuter. Les exemples enseignent les mécaniques et deviennent obsolètes — vérifier avant de s'y fier.
Qu'est-ce que tu construis ?
L'audience de l'app choisit le token, et le token choisit l'architecture : le visitor token est
public — n'importe qui peut le créer à partir du clientId du site — et l'admin token est un secret,
celui du connecteur, côté serveur seulement.
browser ──(visitor token)─► wixapis.com lectures & actions du visiteur
base44/functions/* ──(admin token)───► wixapis.com travail nécessitant l'identité du propriétaire
exec_tool ──(admin token)───► wixapis.com toi : sondage/gestion ad hoc pendant la construction
Un site pour les visiteurs — storefront, blog, réservation. Headless signifie que le site Wix n'a pas de pages propres :
ton app EST son frontend. L'expérience visiteur complète —
chaque page, chaque lecture, chaque action que le visiteur fait — est des appels navigateur sur le visitor token ;
aucun d'eux n'a besoin d'une fonction backend. Les lectures publiques
incluses (le visitor token interroge le catalogue directement), et carts/current/* + checkout agissent
sur le panier de L'APPELANT, donc seul le visitor token accède au panier du visiteur. Un fichier porte tout :
src/lib/wixClient.js (Écris le code, ci-dessous). base44/functions/* n'apparaissent que là où le travail
nécessite l'identité du propriétaire — opérations à permissions élevées qu'un visiteur déclenche, webhooks, tâches planifiées — et pour le backend non-Wix de l'app.
Un outil admin pour le propriétaire — tableau de bord, back office. Les pages agissent comme le propriétaire, dont le token
est un secret : pages → base44/functions/* ──(admin token)──► wixapis.com.
Les helpers
La recherche et le sondage s'exécutent dans exec_tool, et un loader ouvre chaque exec (les execs ne partagent pas d'état — recharger chaque round ; le module réside sur disque à côté de ce fichier, réseau uniquement comme fallback au premier accès) :
const fs = require("fs"), P = ".agents/skills/wix-base44-connector/utils.js";
if (!fs.existsSync(P)) { fs.mkdirSync(".agents/skills/wix-base44-connector", { recursive: true });
fs.writeFileSync(P, await (await fetch("https://www.wix.com/skills/wix-base44-connector/scripts/utils.js")).text()); }
const wx = (() => { const m = { exports: {} };
new Function("module", "exports", "require", fs.readFileSync(P, "utf8"))(m, m.exports, require); return m.exports; })();
Les résultats ≤ 4 000 caractères reviennent en ligne (les résultats exec sont limités à ~5 000) ; tout ce qui est plus grand est sauvegardé
sous .agents/skills/wix-base44-connector/scratch/ et retourne { path, bytes, lines, outline } —
l'outline est la carte. Lis un fichier sauvegardé comme tu sais déjà le faire :
wx.bash("grep -n 'terme' <path> | head -40") pour trouver (GNU grep/sed, awk est mawk, pas rg ;
sur tout ce qui est sauvegardé : grep -rn 'terme' .agents/skills/wix-base44-connector/scratch/), et
read_file pour citer — une fenêtre via offset/limit aux lignes que grep a nommées, ou le fichier entier
quand il tient dans la limite de 45K de read_file. Les réponses API sont des données de site et ne finissent jamais dans
scratch — projette-les en faits. Récupère chaque URL à l'intérieur d'un exec avec fetch() — les outils website/browser
sont limités à 10 000 caractères silencieusement. Un exec par round ; timeout 10s, jusqu'à 120 via {timeout}.
Rassembler le contexte — le rapport de contexte dynamique
const { accessToken } = await base44.asServiceRole.connectors.getConnection("wix");
return await wx.context(accessToken, "Apps"); // pas d'arg section → l'outline du rapport
Un rapport : apps installées avec ids (incl. version du catalogue des Stores — V1 vs V3 décide ses
endpoints), l'id de l'app OAuth (aussi le clientId du visiteur), locale, devise, collections CMS.
Un rapport vide = mauvais token, jamais un site vide.
Apprendre Wix — trouver les APIs, apprendre leurs contrats
// connaître le produit ? browse est déterministe — menuUrl oriente seul (enfants + compte) ;
// filtrer avant de lister les méthodes, les listings non filtrés sont limités
await wx.browse("https://dev.wix.com/docs/api-reference/business-solutions/bookings/bookings",
{ include: ["METHOD"], filter: "resched", depth: 4 });
// ne pas savoir où ça vit ? search classe, ne dit jamais "pas de match" — éliminer les hits du mauvais produit
await wx.search("pause a pricing plan subscription and resume it");
// → { hits: [{ method, endpoint /* callable */, docsUrl, gist }] } — les hits sont souvent LA réponse
Approfondis pour les champs, les énums ou l'absence — seul l'index spec prouve l'absence.
Lire une page de doc
Les pages Method font 100 KB+, les halves REST et SDK jumelles répètent les noms de champs à des types différents —
page récupère, sauvegarde et mappe en un round ; cite seulement ta moitié :
const pg = await wx.page(docsUrl); // texte entier quand petit ; sinon { path, bytes, lines, outline }
await wx.bash(`grep -in 'refund' ${pg.path} | head -40`); // grep -n 'terme|^#' garde les sections visibles
// cite avec read_file(pg.path) + offset/limit aux lignes que grep a nommées — l'exemple REST EN PREMIER :
// sous ### Examples ci-dessous ## REST API se trouve une requête complète de travail
await wx.bash(`sed -n '53,949p' ${pg.path} | grep -oE '"[a-zA-Z]+":' | sort -u | head -60`);
// ↑ un énorme exemple clôturé → son vocabulaire de champs en un round ; le lire entièrement seulement si tu dois
search sauvegarde aussi son contenu brut à côté des hits en ligne — grep son path quand les six
lignes d'un hit n'étaient pas assez.
L'index spec
Répond aux questions sur les pages que tu as déjà trouvées — arrive avec un docsUrl, apparie par lui. Aussi la
seule preuve qu'une API N'EXISTE PAS :
await wx.spec(`
const r = lightIndex.find(x => x.docsUrl === "<docsUrl from browse/search>");
return r.methods.map(m => ({ op: m.operationId.split(".").pop(), verb: m.httpMethod,
call: m.publicUrl })); // publicUrl est callable — path est PARTIEL
`);
// champs de requête le round suivant : getResourceSchemaByUrl(docsUrl) →
// m.requestBody.content["application/json"].schema.properties — noms et types, creuse plus profond
// par round ; $circular stubs résolvent via s.components.schemas["<nom>"]
Recettes de gestion — vérifier avant de composer les flux admin
~100 recettes multi-étapes curées (installer apps, seeder les catalogues, configurer des verticals entiers) :
await wx.recipes(); // catégories avec comptes
await wx.recipes("stores"); // liste d'une catégorie — ou n'importe quel mot tâche : wx.recipes("coupon")
await wx.page(url); // lire la recette choisie — entière quand petite, sauvegardée + outline quand
// grande ; ensuite grep / read_file windows aux lignes de l'outline
Une recette correspondante vaut mieux que composer le flux à partir de endpoints isolés : elle porte l'ordre et les pièges multi-étapes qu'aucune page method ne mentionne.
Écris le code
Les formes de réponse obéissent à la règle de découverte dans chaque lane : code contre les champs que tu as vus dans une réponse en direct ou la schema — les noms mémorisés proviennent souvent de versions plus anciennes. Sonde d'abord une vraie ligne.
Appels admin — exec ad hoc, fonctions backend déployées
const { accessToken } = await base44.asServiceRole.connectors.getConnection("wix");
const data = await wx.post("https://www.wixapis.com/contacts/v5/contacts/query", // spec-index publicUrl
{ query: { cursorPaging: { limit: 10 } } }, accessToken);
return { count: data.contacts?.length, keys: Object.keys(data.contacts?.[0] || {}) }; // projette, ne déverse pas
Le même appel se déploie sous base44/functions/* (travail que l'app fait comme le propriétaire) — le code livré
porte son propre fetch de quatre lignes ; les helpers sont un outil de build, pas une dépendance runtime.
Un client visiteur — src/lib/wixClient.js
La forme "site pour visiteurs" (Qu'est-ce que tu construis ?), en code — un fichier que les pages importent. Ni
clientId (du rapport de contexte) ni le token créé ne sont un secret ; ensemble ils sont "un
visiteur anonyme", sûr dans le code livré :
// src/lib/wixClient.js
let token;
const mint = async (body) => {
const r = await (await fetch("https://www.wixapis.com/oauth2/token", { method: "POST",
headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) })).json();
token = r.access_token; sessionStorage.setItem("wixRefresh", r.refresh_token); // expires_in: 14400s = 4h
};
// première visite : mint({ clientId: WIX_CLIENT_ID, grantType: "anonymous" });
// à l'expiration : mint({ refreshToken: sessionStorage.getItem("wixRefresh"), grantType: "refresh_token" });
// une fraîche création anonyme est un NOUVEAU visiteur — le panier du vieux va avec
export const wix = (path, opts = {}) => fetch("https://www.wixapis.com" + path, { ...opts,
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" } });
Contrat de token : …/headless/authentication/retrieve-tokens. Prouve la lane en un exec avant
d'écrire les pages — crée un visiteur, fais une lecture publique avec :
const { access_token } = await wx.post("https://www.wixapis.com/oauth2/token",
{ clientId: WIX_CLIENT_ID, grantType: "anonymous" }); // clientId : du rapport de contexte
return await wx.post("<une lecture publique de Learn Wix>", { query: {} }, access_token);
// 200 ⇒ chaque page orientée visiteur de l'app est cet appel identique, pas de serveur entre