itmar-block-packages 1.8.0 → 1.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "itmar-block-packages",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "We have put together a package of common React components used for WordPress custom blocks.",
5
5
  "main": "build/index.js",
6
6
  "scripts": {
package/src/index.js CHANGED
@@ -113,3 +113,10 @@ export { fetchZipToAddress } from "./ZipAddress";
113
113
 
114
114
  //書式設定用のコンポーネント
115
115
  export { FormatSelectControl, displayFormated } from "./formatCreate";
116
+
117
+ //ShopifyAPI関連のコンポーネント
118
+ export {
119
+ checkCustomerLoginState,
120
+ redirectCustomerAuthorize,
121
+ sendRegistrationRequest,
122
+ } from "./shopfiApi";
@@ -0,0 +1,187 @@
1
+ function generateState() {
2
+ const timestamp = Date.now().toString();
3
+ const randomString = Math.random().toString(36).substring(2);
4
+ return timestamp + randomString;
5
+ }
6
+
7
+ function generateNonce(length = 32) {
8
+ const characters =
9
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
10
+ let nonce = "";
11
+ for (let i = 0; i < length; i++) {
12
+ nonce += characters.charAt(Math.floor(Math.random() * characters.length));
13
+ }
14
+ return nonce;
15
+ }
16
+
17
+ function generateCodeVerifier(length = 128) {
18
+ const charset =
19
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
20
+ let result = "";
21
+ for (let i = 0; i < length; i++) {
22
+ result += charset.charAt(Math.floor(Math.random() * charset.length));
23
+ }
24
+ return result;
25
+ }
26
+
27
+ async function generateCodeChallenge(codeVerifier) {
28
+ const encoder = new TextEncoder();
29
+ const data = encoder.encode(codeVerifier);
30
+ const digest = await crypto.subtle.digest("SHA-256", data);
31
+ return btoa(String.fromCharCode(...new Uint8Array(digest)))
32
+ .replace(/\+/g, "-")
33
+ .replace(/\//g, "_")
34
+ .replace(/=+$/, "");
35
+ }
36
+
37
+ // ✅ Shopifyへの認証リダイレクト
38
+ export async function redirectCustomerAuthorize(
39
+ shopId,
40
+ clientId,
41
+ userMail,
42
+ callbackUri,
43
+ redirectUri
44
+ ) {
45
+ //呼び出し元の戻り先
46
+ const statePayload = {
47
+ ts: Date.now(),
48
+ random: Math.random().toString(36).substring(2),
49
+ return_url: redirectUri,
50
+ };
51
+ //stateで渡しておく
52
+ const state = btoa(JSON.stringify(statePayload));
53
+ const nonce = generateNonce();
54
+ const codeVerifier = generateCodeVerifier();
55
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
56
+
57
+ localStorage.setItem("shopify_code_verifier", codeVerifier);
58
+ localStorage.setItem("shopify_state", state);
59
+ localStorage.setItem("shopify_nonce", nonce);
60
+ localStorage.setItem("shopify_client_id", clientId);
61
+ localStorage.setItem("shopify_user_mail", userMail);
62
+ localStorage.setItem("shopify_shop_id", shopId);
63
+ localStorage.setItem("shopify_redirect_uri", callbackUri); //ログアウトの処理で使用する
64
+
65
+ const url = new URL(
66
+ `https://shopify.com/authentication/${shopId}/oauth/authorize`
67
+ );
68
+ url.searchParams.append("scope", "openid email customer-account-api:full");
69
+ url.searchParams.append("client_id", clientId);
70
+ url.searchParams.append("response_type", "code");
71
+ url.searchParams.append("redirect_uri", callbackUri);
72
+ url.searchParams.append("state", state);
73
+ url.searchParams.append("nonce", nonce);
74
+ url.searchParams.append("code_challenge", codeChallenge);
75
+ url.searchParams.append("code_challenge_method", "S256");
76
+
77
+ window.location.href = url.toString();
78
+ }
79
+
80
+ // ✅ トークンがあれば検証(Storefront API 経由で customer を取得)
81
+ export async function checkCustomerLoginState() {
82
+ const token = localStorage.getItem("shopify_customer_token");
83
+
84
+ if (!token) return false;
85
+
86
+ const checkUrl = "/wp-json/itmar-ec-relate/v1/shopify-login-check";
87
+ const postData = {
88
+ nonce: itmar_option.nonce,
89
+ token: JSON.stringify({ token }),
90
+ };
91
+ sendRegistrationAjax(checkUrl, postData, true)
92
+ .done(function (res) {
93
+ console.log(res);
94
+ })
95
+ .fail(function (xhr, status, error) {
96
+ alert("サーバーエラー: " + error);
97
+ console.error(xhr.responseText);
98
+ });
99
+ }
100
+
101
+ /**
102
+ * @param {string} urlOrPath - RESTは '/itmar-shopify/v1/...' でも '/wp-json/...' でもOK。admin-ajaxは '/wp-admin/admin-ajax.php'
103
+ * @param {object} data - 送信するデータ。nonce は data._wpnonce か data.nonce に入れておけばOK
104
+ * @param {'auto'|'rest'|'ajax'} mode - 既定は 'auto'
105
+ */
106
+ export async function sendRegistrationRequest(
107
+ urlOrPath,
108
+ data = {},
109
+ mode = "auto"
110
+ ) {
111
+ const isRestUrlLike = (u) =>
112
+ u.startsWith("/wp-json") || !u.startsWith("/wp-admin");
113
+
114
+ // 送信先の確定(RESTのときは /wp-json 省略パスでもOKにする)
115
+ let isRest;
116
+ if (mode === "auto") {
117
+ isRest = isRestUrlLike(urlOrPath);
118
+ } else {
119
+ isRest = mode === "rest";
120
+ }
121
+
122
+ let url = urlOrPath;
123
+
124
+ if (isRest) {
125
+ // '/itmar-shopify/v1/...' のようなパスだけ渡されたら /wp-json を補う
126
+ if (!url.startsWith("/wp-json")) {
127
+ const root =
128
+ (window.wpApiSettings && window.wpApiSettings.root) || "/wp-json/";
129
+ url = root.replace(/\/+$/, "/") + url.replace(/^\/+/, "");
130
+ }
131
+ } else {
132
+ // admin-ajax の既定URL
133
+ if (!url) url = "/wp-admin/admin-ajax.php";
134
+ }
135
+
136
+ const fetchOptions = {
137
+ method: "POST",
138
+ credentials: "same-origin", // 同一オリジン Cookie
139
+ headers: {},
140
+ };
141
+
142
+ // ノンス抽出(_wpnonce を優先)
143
+ const nonce =
144
+ data._wpnonce ||
145
+ data.nonce ||
146
+ (window.wpApiSettings && window.wpApiSettings.nonce);
147
+
148
+ if (isRest) {
149
+ fetchOptions.headers["Content-Type"] = "application/json";
150
+ if (nonce) fetchOptions.headers["X-WP-Nonce"] = nonce;
151
+
152
+ // nonce系は本文から除く(任意)
153
+ const { _wpnonce, nonce: _legacyNonce, ...rest } = data;
154
+ fetchOptions.body = JSON.stringify(rest);
155
+ } else {
156
+ // admin-ajax は URLSearchParams で送る
157
+ const form = new URLSearchParams();
158
+ Object.entries(data).forEach(([k, v]) => {
159
+ if (v !== undefined && v !== null) form.append(k, String(v));
160
+ });
161
+ // ノンスは _wpnonce に統一
162
+ if (nonce && !form.has("_wpnonce")) form.append("_wpnonce", nonce);
163
+ fetchOptions.body = form;
164
+ }
165
+
166
+ const res = await fetch(url, fetchOptions);
167
+
168
+ // WP REST はメソッド不一致・ルートなし等でも JSON 返さないことがあるので分岐
169
+ const contentType = res.headers.get("content-type") || "";
170
+ const tryJson = contentType.includes("application/json");
171
+
172
+ if (!res.ok) {
173
+ let msg = `HTTP ${res.status}`;
174
+ if (tryJson) {
175
+ try {
176
+ const j = await res.json();
177
+ msg += j.message ? `: ${j.message}` : "";
178
+ } catch {}
179
+ } else {
180
+ const t = await res.text();
181
+ if (t) msg += `: ${t.slice(0, 200)}`;
182
+ }
183
+ throw new Error(msg);
184
+ }
185
+
186
+ return tryJson ? res.json() : res.text();
187
+ }