UNPKG

itmar-block-packages

Version:

Common React components for WordPress custom blocks, now fully TypeScript-ready.

171 lines (168 loc) 7.1 kB
import { __ } from '@wordpress/i18n'; 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); // Uint8Array への変換と Base64 エンコード const hashArray = new Uint8Array(digest); // バイナリデータを文字列に変換 // ※スプレッド構文 (...) は大規模なデータでスタックオーバーフローの可能性があるため // 小規模な PKCE 用途では問題ありませんが、型定義を明確にします。 const binaryString = String.fromCharCode(...Array.from(hashArray)); // Base64 を Base64URL 形式(Shopify / OAuth 2.0 仕様)に変換 return btoa(binaryString) .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=+$/, ""); } // ✅ Shopifyへの認証リダイレクト async function redirectCustomerAuthorize(shopId, clientId, userMail, callbackUri, // Shopify側の管理画面で登録したリダイレクト先 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(); } /** * Shopifyの顧客トークンを検証し、ログイン状態を確認する */ 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 が内部で jQuery.ajax を返している(Promise互換)と想定 try { const response = await sendRegistrationRequest(checkUrl, postData, "rest"); // 成功時の処理 console.log("Login check success:", response); // ここでサーバーからのレスポンス内容(成功/失敗)に応じて return するのが理想です return response.success === true; } catch (error) { // 失敗時の処理 const errorMessage = error.statusText || error || "Unknown Error"; alert(__("Server Error: ", "itmar-ec-relate") + errorMessage); console.error("Login check failed:", error); return false; } } /** * 統合された WordPress 通信関数 */ async function sendRegistrationRequest(urlOrPath, data = {}, mode = "auto") { // 1. REST かどうかを判定 const isRestUrlLike = (u) => u.startsWith("/wp-json") || (!u.startsWith("/wp-admin") && !u.includes("admin-ajax.php")); let isRest; if (mode === "auto") { isRest = isRestUrlLike(urlOrPath); } else { isRest = mode === "rest"; } // 2. パスの補完 (/wp-json/ を省略した場合の対応) let url = urlOrPath; if (isRest && !url.startsWith("http") && !url.startsWith("/wp-json")) { const root = window.wpApiSettings?.root || "/wp-json/"; url = root.replace(/\/+$/, "/") + url.replace(/^\/+/, ""); } // 3. ノンス (Security Token) の抽出 const nonce = data._wpnonce || data.nonce || window.wpApiSettings?.nonce; // 4. 通信オプションの構築 const fetchOptions = { method: "POST", credentials: "same-origin", // 同一オリジン Cookie headers: {}, }; if (isRest) { // --- REST API モード: JSON で送る --- fetchOptions.headers["Content-Type"] = "application/json"; if (nonce) fetchOptions.headers["X-WP-Nonce"] = nonce; // Body から nonce 関連を分離して純粋なデータのみにする const { _wpnonce, nonce: _legacyNonce, ...cleanData } = data; fetchOptions.body = JSON.stringify(cleanData); } 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; } // 5. 実行 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(); } export { checkCustomerLoginState, redirectCustomerAuthorize, sendRegistrationRequest }; //# sourceMappingURL=shopfiApi.js.map