UNPKG

itmar-block-packages

Version:

We have put together a package of common React components used for WordPress custom blocks.

188 lines (165 loc) 6.21 kB
function generateState() { const timestamp = Date.now().toString(); const randomString = Math.random().toString(36).substring(2); return timestamp + randomString; } function generateNonce(length = 32) { const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let nonce = ""; for (let i = 0; i < length; i++) { nonce += characters.charAt(Math.floor(Math.random() * characters.length)); } return nonce; } function generateCodeVerifier(length = 128) { const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"; let result = ""; for (let i = 0; i < length; i++) { result += charset.charAt(Math.floor(Math.random() * charset.length)); } return result; } async function generateCodeChallenge(codeVerifier) { const encoder = new TextEncoder(); const data = encoder.encode(codeVerifier); const digest = await crypto.subtle.digest("SHA-256", data); return btoa(String.fromCharCode(...new Uint8Array(digest))) .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=+$/, ""); } // ✅ Shopifyへの認証リダイレクト export async function redirectCustomerAuthorize( shopId, clientId, userMail, callbackUri, redirectUri ) { //呼び出し元の戻り先 const statePayload = { ts: Date.now(), random: Math.random().toString(36).substring(2), return_url: redirectUri, }; //stateで渡しておく const state = btoa(JSON.stringify(statePayload)); const nonce = generateNonce(); const codeVerifier = generateCodeVerifier(); const codeChallenge = await generateCodeChallenge(codeVerifier); localStorage.setItem("shopify_code_verifier", codeVerifier); localStorage.setItem("shopify_state", state); localStorage.setItem("shopify_nonce", nonce); localStorage.setItem("shopify_client_id", clientId); localStorage.setItem("shopify_user_mail", userMail); localStorage.setItem("shopify_shop_id", shopId); localStorage.setItem("shopify_redirect_uri", callbackUri); //ログアウトの処理で使用する const url = new URL( `https://shopify.com/authentication/${shopId}/oauth/authorize` ); url.searchParams.append("scope", "openid email customer-account-api:full"); url.searchParams.append("client_id", clientId); url.searchParams.append("response_type", "code"); url.searchParams.append("redirect_uri", callbackUri); url.searchParams.append("state", state); url.searchParams.append("nonce", nonce); url.searchParams.append("code_challenge", codeChallenge); url.searchParams.append("code_challenge_method", "S256"); window.location.href = url.toString(); } // ✅ トークンがあれば検証(Storefront API 経由で customer を取得) export async function checkCustomerLoginState() { const token = localStorage.getItem("shopify_customer_token"); if (!token) return false; const checkUrl = "/wp-json/itmar-ec-relate/v1/shopify-login-check"; const postData = { nonce: itmar_option.nonce, token: JSON.stringify({ token }), }; sendRegistrationAjax(checkUrl, postData, true) .done(function (res) { console.log(res); }) .fail(function (xhr, status, error) { alert("サーバーエラー: " + error); console.error(xhr.responseText); }); } /** * @param {string} urlOrPath - RESTは '/itmar-shopify/v1/...' でも '/wp-json/...' でもOK。admin-ajaxは '/wp-admin/admin-ajax.php' * @param {object} data - 送信するデータ。nonce は data._wpnonce か data.nonce に入れておけばOK * @param {'auto'|'rest'|'ajax'} mode - 既定は 'auto' */ export async function sendRegistrationRequest( urlOrPath, data = {}, mode = "auto" ) { const isRestUrlLike = (u) => u.startsWith("/wp-json") || !u.startsWith("/wp-admin"); // 送信先の確定(RESTのときは /wp-json 省略パスでもOKにする) let isRest; if (mode === "auto") { isRest = isRestUrlLike(urlOrPath); } else { isRest = mode === "rest"; } let url = urlOrPath; if (isRest) { // '/itmar-shopify/v1/...' のようなパスだけ渡されたら /wp-json を補う if (!url.startsWith("/wp-json")) { const root = (window.wpApiSettings && window.wpApiSettings.root) || "/wp-json/"; url = root.replace(/\/+$/, "/") + url.replace(/^\/+/, ""); } } else { // admin-ajax の既定URL if (!url) url = "/wp-admin/admin-ajax.php"; } const fetchOptions = { method: "POST", credentials: "same-origin", // 同一オリジン Cookie headers: {}, }; // ノンス抽出(_wpnonce を優先) const nonce = data._wpnonce || data.nonce || (window.wpApiSettings && window.wpApiSettings.nonce); if (isRest) { fetchOptions.headers["Content-Type"] = "application/json"; if (nonce) fetchOptions.headers["X-WP-Nonce"] = nonce; // nonce系は本文から除く(任意) const { _wpnonce, nonce: _legacyNonce, ...rest } = data; fetchOptions.body = JSON.stringify(rest); } else { // admin-ajax は URLSearchParams で送る const form = new URLSearchParams(); Object.entries(data).forEach(([k, v]) => { if (v !== undefined && v !== null) form.append(k, String(v)); }); // ノンスは _wpnonce に統一 if (nonce && !form.has("_wpnonce")) form.append("_wpnonce", nonce); fetchOptions.body = form; } const res = await fetch(url, fetchOptions); // WP REST はメソッド不一致・ルートなし等でも JSON 返さないことがあるので分岐 const contentType = res.headers.get("content-type") || ""; const tryJson = contentType.includes("application/json"); if (!res.ok) { let msg = `HTTP ${res.status}`; if (tryJson) { try { const j = await res.json(); msg += j.message ? `: ${j.message}` : ""; } catch {} } else { const t = await res.text(); if (t) msg += `: ${t.slice(0, 200)}`; } throw new Error(msg); } return tryJson ? res.json() : res.text(); }