@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 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 { z } from "zod";
3
3
  /**
4
4
  * The one way this adapter talks to a store: the GraphQL Admin API.
@@ -51,6 +51,10 @@ async function readEnvelope(response) {
51
51
  */
52
52
  export async function shopifyGraphql(target, query, variables, schema) {
53
53
  const sleep = target.sleep ?? defaultSleep;
54
+ // Called unbound, never as `target.fetchImpl(...)`: workerd's global `fetch` refuses any `this`
55
+ // but the global scope ("Illegal invocation"), and Node's does not, so a method call passes on
56
+ // Node and fails every Admin API call on a Worker. test/workerd-fetch.e2e.test.ts holds it.
57
+ const { fetchImpl } = target;
54
58
  const url = `${target.origin}/admin/api/${SHOPIFY_API_VERSION}/graphql.json`;
55
59
  let waited = 0;
56
60
  for (let attempt = 0;; attempt += 1) {
@@ -58,7 +62,7 @@ export async function shopifyGraphql(target, query, variables, schema) {
58
62
  try {
59
63
  // `manual`, never `error`: workerd implements only `follow` and `manual` and throws on `error`
60
64
  // from the Request constructor. A 3xx is then simply not ok, which refuses the redirect.
61
- response = await target.fetchImpl(url, {
65
+ response = await fetchImpl(url, {
62
66
  method: "POST",
63
67
  redirect: "manual",
64
68
  headers: { accept: "application/json", "content-type": "application/json", "x-shopify-access-token": target.accessToken },
@@ -69,7 +73,7 @@ export async function shopifyGraphql(target, query, variables, schema) {
69
73
  return Err({ code: "SHOPIFY_API_FAILED", message: error instanceof Error ? error.message : "Shopify API request failed.", retriable: true });
70
74
  }
71
75
  if (response.status === 401 || response.status === 403) {
72
- return Err({ code: "SHOPIFY_UNAUTHORIZED", message: `Shopify refused the access token (${response.status}); the store must be reconnected.`, retriable: false });
76
+ return Err({ code: CHANNEL_CREDENTIALS_REJECTED, message: `Shopify refused the access token (${response.status}).`, retriable: false });
73
77
  }
74
78
  if (response.status !== 429 && !response.ok) {
75
79
  return Err({ code: "SHOPIFY_API_FAILED", message: `Shopify API request failed (${response.status}).`, retriable: response.status >= 500 });
package/dist/index.js CHANGED
@@ -107,11 +107,11 @@ export function shopifyConnector(options) {
107
107
  return credentials;
108
108
  return Ok({ credentials: { ...credentials.value }, storeDomain: shop });
109
109
  },
110
- async liveCredentials(store) {
110
+ async liveCredentials(store, refresh) {
111
111
  const credentials = parseShopifyCredentials(store.credentials);
112
112
  if (!credentials)
113
113
  return Err(credentialsRequired);
114
- const refreshed = await refreshIfExpiring({ fetchImpl, origin: origin(store.storeDomain), clientId: options.clientId, clientSecret: options.clientSecret, credentials, now: Date.now() });
114
+ const refreshed = await refreshIfExpiring({ fetchImpl, origin: origin(store.storeDomain), clientId: options.clientId, clientSecret: options.clientSecret, credentials, now: Date.now(), ...(refresh?.force === true ? { force: true } : {}) });
115
115
  if (!refreshed.ok)
116
116
  return refreshed;
117
117
  return Ok(refreshed.value === null ? null : { ...refreshed.value });
package/dist/oauth.d.ts CHANGED
@@ -63,4 +63,6 @@ export declare function refreshIfExpiring(params: {
63
63
  clientSecret: string;
64
64
  credentials: ShopifyCredentials;
65
65
  now: number;
66
+ /** Refresh even though the stated expiry is far off: Shopify just rejected the token. */
67
+ force?: boolean;
66
68
  }): Promise<Result<ShopifyCredentials | null, ChannelConnectorError>>;
package/dist/oauth.js CHANGED
@@ -149,11 +149,13 @@ export async function exchangeCallback(params) {
149
149
  */
150
150
  export async function refreshIfExpiring(params) {
151
151
  const { credentials } = params;
152
- if (credentials.accessTokenExpiresAt === undefined)
153
- return Ok(null);
154
- const expiresAt = Date.parse(credentials.accessTokenExpiresAt);
155
- if (Number.isFinite(expiresAt) && expiresAt - params.now > ACCESS_TOKEN_REFRESH_MARGIN_MS)
156
- return Ok(null);
152
+ if (params.force !== true) {
153
+ if (credentials.accessTokenExpiresAt === undefined)
154
+ return Ok(null);
155
+ const expiresAt = Date.parse(credentials.accessTokenExpiresAt);
156
+ if (Number.isFinite(expiresAt) && expiresAt - params.now > ACCESS_TOKEN_REFRESH_MARGIN_MS)
157
+ return Ok(null);
158
+ }
157
159
  if (credentials.refreshToken === undefined)
158
160
  return oauthError("SHOPIFY_REAUTHORIZATION_REQUIRED", "The store's access token expired and no refresh token is held; the store must be reconnected.");
159
161
  const token = await postTokenEndpoint(params.fetchImpl, params.origin, {