@porulle/adapter-shopify 0.67.0 → 0.68.1
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/dist/graphql.js +7 -3
- package/dist/index.js +2 -2
- package/dist/oauth.d.ts +2 -0
- package/dist/oauth.js +7 -5
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +4 -2
- package/src/graphql.ts +7 -3
- package/src/index.ts +2 -2
- package/src/oauth.ts +7 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@porulle/adapter-shopify",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.68.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -16,12 +16,14 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"zod": "^4.1.11",
|
|
19
|
-
"@porulle/core": "0.
|
|
19
|
+
"@porulle/core": "0.68.1"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "^24.5.2",
|
|
23
|
+
"esbuild": "0.25.12",
|
|
23
24
|
"eslint": "^9.39.1",
|
|
24
25
|
"graphql": "^16.11.0",
|
|
26
|
+
"miniflare": "4.20260722.1",
|
|
25
27
|
"typescript": "5.9.2",
|
|
26
28
|
"vitest": "^3.2.4",
|
|
27
29
|
"@porulle/eslint-config": "0.1.0",
|
package/src/graphql.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Err, Ok } from "@porulle/core";
|
|
1
|
+
import { CHANNEL_CREDENTIALS_REJECTED, Err, Ok } from "@porulle/core";
|
|
2
2
|
import type { ChannelConnectorError, Result } from "@porulle/core";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
@@ -73,6 +73,10 @@ export async function shopifyGraphql<T>(
|
|
|
73
73
|
schema: z.ZodType<T>,
|
|
74
74
|
): Promise<Result<T, ChannelConnectorError>> {
|
|
75
75
|
const sleep = target.sleep ?? defaultSleep;
|
|
76
|
+
// Called unbound, never as `target.fetchImpl(...)`: workerd's global `fetch` refuses any `this`
|
|
77
|
+
// but the global scope ("Illegal invocation"), and Node's does not, so a method call passes on
|
|
78
|
+
// Node and fails every Admin API call on a Worker. test/workerd-fetch.e2e.test.ts holds it.
|
|
79
|
+
const { fetchImpl } = target;
|
|
76
80
|
const url = `${target.origin}/admin/api/${SHOPIFY_API_VERSION}/graphql.json`;
|
|
77
81
|
let waited = 0;
|
|
78
82
|
for (let attempt = 0; ; attempt += 1) {
|
|
@@ -80,7 +84,7 @@ export async function shopifyGraphql<T>(
|
|
|
80
84
|
try {
|
|
81
85
|
// `manual`, never `error`: workerd implements only `follow` and `manual` and throws on `error`
|
|
82
86
|
// from the Request constructor. A 3xx is then simply not ok, which refuses the redirect.
|
|
83
|
-
response = await
|
|
87
|
+
response = await fetchImpl(url, {
|
|
84
88
|
method: "POST",
|
|
85
89
|
redirect: "manual",
|
|
86
90
|
headers: { accept: "application/json", "content-type": "application/json", "x-shopify-access-token": target.accessToken },
|
|
@@ -90,7 +94,7 @@ export async function shopifyGraphql<T>(
|
|
|
90
94
|
return Err({ code: "SHOPIFY_API_FAILED", message: error instanceof Error ? error.message : "Shopify API request failed.", retriable: true });
|
|
91
95
|
}
|
|
92
96
|
if (response.status === 401 || response.status === 403) {
|
|
93
|
-
return Err({ code:
|
|
97
|
+
return Err({ code: CHANNEL_CREDENTIALS_REJECTED, message: `Shopify refused the access token (${response.status}).`, retriable: false });
|
|
94
98
|
}
|
|
95
99
|
if (response.status !== 429 && !response.ok) {
|
|
96
100
|
return Err({ code: "SHOPIFY_API_FAILED", message: `Shopify API request failed (${response.status}).`, retriable: response.status >= 500 });
|
package/src/index.ts
CHANGED
|
@@ -152,10 +152,10 @@ export function shopifyConnector(options: ShopifyConnectorOptions): ChannelConne
|
|
|
152
152
|
if (!credentials.ok) return credentials;
|
|
153
153
|
return Ok({ credentials: { ...credentials.value }, storeDomain: shop });
|
|
154
154
|
},
|
|
155
|
-
async liveCredentials(store) {
|
|
155
|
+
async liveCredentials(store, refresh) {
|
|
156
156
|
const credentials = parseShopifyCredentials(store.credentials);
|
|
157
157
|
if (!credentials) return Err(credentialsRequired);
|
|
158
|
-
const refreshed = await refreshIfExpiring({ fetchImpl, origin: origin(store.storeDomain), clientId: options.clientId, clientSecret: options.clientSecret, credentials, now: Date.now() });
|
|
158
|
+
const refreshed = await refreshIfExpiring({ fetchImpl, origin: origin(store.storeDomain), clientId: options.clientId, clientSecret: options.clientSecret, credentials, now: Date.now(), ...(refresh?.force === true ? { force: true } : {}) });
|
|
159
159
|
if (!refreshed.ok) return refreshed;
|
|
160
160
|
return Ok(refreshed.value === null ? null : { ...refreshed.value });
|
|
161
161
|
},
|
package/src/oauth.ts
CHANGED
|
@@ -181,11 +181,15 @@ export async function refreshIfExpiring(params: {
|
|
|
181
181
|
clientSecret: string;
|
|
182
182
|
credentials: ShopifyCredentials;
|
|
183
183
|
now: number;
|
|
184
|
+
/** Refresh even though the stated expiry is far off: Shopify just rejected the token. */
|
|
185
|
+
force?: boolean;
|
|
184
186
|
}): Promise<Result<ShopifyCredentials | null, ChannelConnectorError>> {
|
|
185
187
|
const { credentials } = params;
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
|
|
188
|
+
if (params.force !== true) {
|
|
189
|
+
if (credentials.accessTokenExpiresAt === undefined) return Ok(null);
|
|
190
|
+
const expiresAt = Date.parse(credentials.accessTokenExpiresAt);
|
|
191
|
+
if (Number.isFinite(expiresAt) && expiresAt - params.now > ACCESS_TOKEN_REFRESH_MARGIN_MS) return Ok(null);
|
|
192
|
+
}
|
|
189
193
|
if (credentials.refreshToken === undefined) return oauthError("SHOPIFY_REAUTHORIZATION_REQUIRED", "The store's access token expired and no refresh token is held; the store must be reconnected.");
|
|
190
194
|
const token = await postTokenEndpoint(params.fetchImpl, params.origin, {
|
|
191
195
|
client_id: params.clientId,
|