@tiledev/sdk-shopify 0.9.1 → 0.10.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.
Files changed (143) hide show
  1. package/README.md +650 -50
  2. package/dist/alertSettings.d.ts.map +1 -1
  3. package/dist/alertSettings.js +2 -0
  4. package/dist/alertSettings.js.map +1 -1
  5. package/dist/attribution.d.ts +21 -0
  6. package/dist/attribution.d.ts.map +1 -1
  7. package/dist/attribution.js +17 -0
  8. package/dist/attribution.js.map +1 -1
  9. package/dist/auth/pkce.d.ts +30 -0
  10. package/dist/auth/pkce.d.ts.map +1 -0
  11. package/dist/auth/pkce.js +128 -0
  12. package/dist/auth/pkce.js.map +1 -0
  13. package/dist/auth/session.d.ts +118 -0
  14. package/dist/auth/session.d.ts.map +1 -0
  15. package/dist/auth/session.js +896 -0
  16. package/dist/auth/session.js.map +1 -0
  17. package/dist/auth/storeCredit.d.ts +64 -0
  18. package/dist/auth/storeCredit.d.ts.map +1 -0
  19. package/dist/auth/storeCredit.js +274 -0
  20. package/dist/auth/storeCredit.js.map +1 -0
  21. package/dist/cart.d.ts +25 -1
  22. package/dist/cart.d.ts.map +1 -1
  23. package/dist/cart.js +95 -2
  24. package/dist/cart.js.map +1 -1
  25. package/dist/cartPolicy.js +4 -4
  26. package/dist/checkout.d.ts +123 -0
  27. package/dist/checkout.d.ts.map +1 -0
  28. package/dist/checkout.js +205 -0
  29. package/dist/checkout.js.map +1 -0
  30. package/dist/customer.d.ts.map +1 -1
  31. package/dist/customer.js +8 -0
  32. package/dist/customer.js.map +1 -1
  33. package/dist/index.d.ts +12 -4
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +74 -2
  36. package/dist/index.js.map +1 -1
  37. package/dist/messages.d.ts.map +1 -1
  38. package/dist/messages.js +2 -0
  39. package/dist/messages.js.map +1 -1
  40. package/dist/money.d.ts +16 -0
  41. package/dist/money.d.ts.map +1 -1
  42. package/dist/money.js +55 -0
  43. package/dist/money.js.map +1 -1
  44. package/dist/orders.d.ts +99 -0
  45. package/dist/orders.d.ts.map +1 -0
  46. package/dist/orders.js +439 -0
  47. package/dist/orders.js.map +1 -0
  48. package/dist/productPage.d.ts +100 -0
  49. package/dist/productPage.d.ts.map +1 -0
  50. package/dist/productPage.js +298 -0
  51. package/dist/productPage.js.map +1 -0
  52. package/dist/productStore.d.ts +1 -1
  53. package/dist/productStore.d.ts.map +1 -1
  54. package/dist/productStore.js +63 -5
  55. package/dist/productStore.js.map +1 -1
  56. package/dist/products.d.ts.map +1 -1
  57. package/dist/products.js +15 -3
  58. package/dist/products.js.map +1 -1
  59. package/dist/purchaseRules.d.ts +74 -0
  60. package/dist/purchaseRules.d.ts.map +1 -0
  61. package/dist/purchaseRules.js +73 -0
  62. package/dist/purchaseRules.js.map +1 -0
  63. package/dist/queries.d.ts +15 -13
  64. package/dist/queries.d.ts.map +1 -1
  65. package/dist/queries.js +43 -7
  66. package/dist/queries.js.map +1 -1
  67. package/dist/react/ShopifyProvider.d.ts +167 -25
  68. package/dist/react/ShopifyProvider.d.ts.map +1 -1
  69. package/dist/react/ShopifyProvider.js +352 -207
  70. package/dist/react/ShopifyProvider.js.map +1 -1
  71. package/dist/react/appForeground.d.ts +6 -0
  72. package/dist/react/appForeground.d.ts.map +1 -0
  73. package/dist/react/appForeground.js +19 -0
  74. package/dist/react/appForeground.js.map +1 -0
  75. package/dist/react/appForeground.native.d.ts +2 -0
  76. package/dist/react/appForeground.native.d.ts.map +1 -0
  77. package/dist/react/appForeground.native.js +23 -0
  78. package/dist/react/appForeground.native.js.map +1 -0
  79. package/dist/react/index.d.ts +7 -1
  80. package/dist/react/index.d.ts.map +1 -1
  81. package/dist/react/index.js +23 -1
  82. package/dist/react/index.js.map +1 -1
  83. package/dist/react/tileCreditClient.d.ts +4 -0
  84. package/dist/react/tileCreditClient.d.ts.map +1 -0
  85. package/dist/react/tileCreditClient.js +27 -0
  86. package/dist/react/tileCreditClient.js.map +1 -0
  87. package/dist/react/useAppDiscountCode.d.ts +19 -0
  88. package/dist/react/useAppDiscountCode.d.ts.map +1 -0
  89. package/dist/react/useAppDiscountCode.js +44 -0
  90. package/dist/react/useAppDiscountCode.js.map +1 -0
  91. package/dist/react/useCartStoreCredit.d.ts +25 -0
  92. package/dist/react/useCartStoreCredit.d.ts.map +1 -0
  93. package/dist/react/useCartStoreCredit.js +292 -0
  94. package/dist/react/useCartStoreCredit.js.map +1 -0
  95. package/dist/react/useOrders.d.ts +104 -0
  96. package/dist/react/useOrders.d.ts.map +1 -0
  97. package/dist/react/useOrders.js +287 -0
  98. package/dist/react/useOrders.js.map +1 -0
  99. package/dist/react/useProduct.d.ts.map +1 -1
  100. package/dist/react/useProduct.js +1 -9
  101. package/dist/react/useProduct.js.map +1 -1
  102. package/dist/react/useProductPage.d.ts +148 -0
  103. package/dist/react/useProductPage.d.ts.map +1 -0
  104. package/dist/react/useProductPage.js +178 -0
  105. package/dist/react/useProductPage.js.map +1 -0
  106. package/dist/react/useProductRecommendations.d.ts +27 -0
  107. package/dist/react/useProductRecommendations.d.ts.map +1 -0
  108. package/dist/react/useProductRecommendations.js +76 -0
  109. package/dist/react/useProductRecommendations.js.map +1 -0
  110. package/dist/react/useStoreCredit.d.ts +58 -0
  111. package/dist/react/useStoreCredit.d.ts.map +1 -0
  112. package/dist/react/useStoreCredit.js +209 -0
  113. package/dist/react/useStoreCredit.js.map +1 -0
  114. package/dist/react/useTileCredit.d.ts +4 -3
  115. package/dist/react/useTileCredit.d.ts.map +1 -1
  116. package/dist/react/useTileCredit.js +63 -55
  117. package/dist/react/useTileCredit.js.map +1 -1
  118. package/dist/shopify.d.ts.map +1 -1
  119. package/dist/shopify.js +2 -0
  120. package/dist/shopify.js.map +1 -1
  121. package/dist/storedList.d.ts +27 -0
  122. package/dist/storedList.d.ts.map +1 -0
  123. package/dist/storedList.js +152 -0
  124. package/dist/storedList.js.map +1 -0
  125. package/dist/tileCredit.d.ts +25 -10
  126. package/dist/tileCredit.d.ts.map +1 -1
  127. package/dist/tileCredit.js +97 -45
  128. package/dist/tileCredit.js.map +1 -1
  129. package/dist/types.d.ts +407 -34
  130. package/dist/types.d.ts.map +1 -1
  131. package/dist/types.js +1 -2
  132. package/dist/types.js.map +1 -1
  133. package/dist/variants.d.ts.map +1 -1
  134. package/dist/variants.js +4 -3
  135. package/dist/variants.js.map +1 -1
  136. package/dist/waitlist.d.ts +4 -0
  137. package/dist/waitlist.d.ts.map +1 -0
  138. package/dist/waitlist.js +213 -0
  139. package/dist/waitlist.js.map +1 -0
  140. package/dist/wishlist.d.ts.map +1 -1
  141. package/dist/wishlist.js +70 -47
  142. package/dist/wishlist.js.map +1 -1
  143. package/package.json +2 -2
@@ -0,0 +1,896 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RENEW_WITHIN_MS = exports.EXPIRY_SKEW_MS = exports.LEGACY_PASSWORD_TOKEN_KEY = exports.PASSWORD_SESSION_KEYS = exports.SHOPIFY_SESSION_KEYS = void 0;
4
+ exports.customerAccountUrls = customerAccountUrls;
5
+ exports.callbackParams = callbackParams;
6
+ exports.toCustomer = toCustomer;
7
+ exports.createCustomerSession = createCustomerSession;
8
+ /**
9
+ * The shopper's session, for both ways of signing in (`AuthMethod`):
10
+ *
11
+ * - `password`: a Storefront `customerAccessToken` from email and password.
12
+ * - `shopify`: Shopify's web sign-in for new customer accounts, OAuth 2 authorization code with PKCE
13
+ * against `shopify.com/authentication/<shopId>`, then the Customer Account API with that token.
14
+ *
15
+ * One session at a time; signing in one way ends a session of the other. Framework-free, so the
16
+ * base cases are tested without React (test/auth-*.test.mjs). `ShopifyProvider` holds one and mirrors
17
+ * its state into `useCustomer()`.
18
+ *
19
+ * Failure rules, the same for both:
20
+ * - The shopper's own answer (wrong password, a refused sign-in) resolves `false` and emits
21
+ * `auth:loginFailed`. Backing out of the sign-in page resolves `false` and emits nothing.
22
+ * - The store being unreachable throws, and never ends a session: being offline is not being signed out.
23
+ * - A session Shopify no longer accepts ends: silently on app open, with `auth:sessionExpired` later.
24
+ */
25
+ const shopify_1 = require("../shopify");
26
+ const client_1 = require("../client");
27
+ const errors_1 = require("../errors");
28
+ const types_1 = require("../types");
29
+ const pkce_1 = require("./pkce");
30
+ /**
31
+ * Production Amber's keychain keys, so its signed-in shoppers stay signed in after updating to a
32
+ * Tile build. `expiresAt` is epoch milliseconds as a decimal string.
33
+ */
34
+ exports.SHOPIFY_SESSION_KEYS = {
35
+ accessToken: 'auth.token',
36
+ refreshToken: 'auth.refreshToken',
37
+ expiresAt: 'auth.expiresAt',
38
+ /** Kept only to sign out with: Shopify's logout endpoint identifies the session by `id_token_hint`. */
39
+ idToken: 'auth.idToken',
40
+ };
41
+ /**
42
+ * The password session. Every key here is valid for expo-secure-store, which takes only letters,
43
+ * digits, `.`, `-` and `_`: a `:` makes it throw, so SDK 0.8's `shopify:customer-token:v1` can't
44
+ * be used in the keychain (it failed restore on an iPhone, 2026-10-02).
45
+ */
46
+ exports.PASSWORD_SESSION_KEYS = {
47
+ accessToken: 'auth.storefrontToken',
48
+ expiresAt: 'auth.storefrontExpiresAt',
49
+ };
50
+ /** Where SDK 0.8 and earlier kept the password token, in the provider's `storage`. Moved on first read. */
51
+ exports.LEGACY_PASSWORD_TOKEN_KEY = 'shopify:customer-token:v1';
52
+ /** Refresh a little early, so a request in flight can't land on a token that just expired. */
53
+ exports.EXPIRY_SKEW_MS = 60000;
54
+ /** A password token is renewed on app open once it has less than this left. */
55
+ exports.RENEW_WITHIN_MS = 7 * 24 * 60 * 60 * 1000;
56
+ const DEFAULT_SCOPES = ['openid', 'email', 'customer-account-api:full'];
57
+ const DEFAULT_CUSTOMER_ACCOUNT_API_VERSION = '2025-07';
58
+ /** Shopify refused the grant (`invalid_grant` and kin): a final answer, unlike a network failure. */
59
+ class TokenRejected extends Error {
60
+ constructor(code) {
61
+ super(`Shopify refused the token request: ${code}`);
62
+ this.code = code;
63
+ this.name = 'TokenRejected';
64
+ }
65
+ }
66
+ function customerAccountUrls(c, fallbackVersion) {
67
+ const auth = `https://shopify.com/authentication/${c.shopId}`;
68
+ const version = c.apiVersion || fallbackVersion || DEFAULT_CUSTOMER_ACCOUNT_API_VERSION;
69
+ return {
70
+ authorize: `${auth}/oauth/authorize`,
71
+ token: `${auth}/oauth/token`,
72
+ logout: `${auth}/logout`,
73
+ graphql: `https://shopify.com/${c.shopId}/account/customer/api/${version}/graphql`,
74
+ redirectUri: c.redirectUri || `shop.${c.shopId}.app://callback`,
75
+ };
76
+ }
77
+ /** `a=1&b=2`, by hand: React Native's `URLSearchParams` is incomplete. */
78
+ function form(params) {
79
+ return Object.keys(params)
80
+ .map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`)
81
+ .join('&');
82
+ }
83
+ /** The query and fragment parameters of a redirect URL. */
84
+ function callbackParams(url) {
85
+ const out = {};
86
+ const q = url.indexOf('?');
87
+ const h = url.indexOf('#');
88
+ const parts = [q >= 0 ? url.slice(q + 1, h > q ? h : undefined) : '', h >= 0 ? url.slice(h + 1) : ''];
89
+ for (const part of parts) {
90
+ for (const pair of part.split('&')) {
91
+ if (!pair)
92
+ continue;
93
+ const eq = pair.indexOf('=');
94
+ const key = decodeURIComponent((eq < 0 ? pair : pair.slice(0, eq)).replace(/\+/g, ' '));
95
+ const value = eq < 0 ? '' : decodeURIComponent(pair.slice(eq + 1).replace(/\+/g, ' '));
96
+ if (!(key in out))
97
+ out[key] = value;
98
+ }
99
+ }
100
+ return out;
101
+ }
102
+ function jwtClaims(jwt) {
103
+ var _a;
104
+ try {
105
+ return JSON.parse((0, pkce_1.base64urlDecodeText)((_a = jwt.split('.')[1]) !== null && _a !== void 0 ? _a : ''));
106
+ }
107
+ catch {
108
+ return null;
109
+ }
110
+ }
111
+ const describe = (error) => (error instanceof Error ? error.message : String(error));
112
+ const PROFILE_QUERY = /* GraphQL */ `
113
+ query CustomerProfile {
114
+ customer {
115
+ id
116
+ firstName
117
+ lastName
118
+ emailAddress { emailAddress marketingState }
119
+ phoneNumber { phoneNumber }
120
+ defaultAddress { id firstName lastName address1 address2 city province country zip phoneNumber }
121
+ }
122
+ }
123
+ `;
124
+ /** Names only: the Customer Account API's `CustomerUpdateInput` has no other field a shopper edits. */
125
+ const NAME_UPDATE_MUTATION = /* GraphQL */ `
126
+ mutation CustomerNameUpdate($input: CustomerUpdateInput!) {
127
+ customerUpdate(input: $input) {
128
+ userErrors { field message code }
129
+ }
130
+ }
131
+ `;
132
+ /**
133
+ * Consent has a mutation per direction, each acting on the signed-in shopper with no arguments, so
134
+ * the wanted state picks the document.
135
+ */
136
+ const MARKETING_SUBSCRIBE_MUTATION = /* GraphQL */ `
137
+ mutation CustomerEmailMarketingSubscribe {
138
+ customerEmailMarketingSubscribe {
139
+ emailAddress { marketingState }
140
+ userErrors { field message code }
141
+ }
142
+ }
143
+ `;
144
+ const MARKETING_UNSUBSCRIBE_MUTATION = /* GraphQL */ `
145
+ mutation CustomerEmailMarketingUnsubscribe {
146
+ customerEmailMarketingUnsubscribe {
147
+ emailAddress { marketingState }
148
+ userErrors { field message code }
149
+ }
150
+ }
151
+ `;
152
+ /**
153
+ * The fields of `changes` that differ from the loaded profile, so nothing Shopify already holds is
154
+ * written again. An unset name counts as `''`. With no profile loaded (offline on app open), every
155
+ * field given counts as changed.
156
+ */
157
+ function changedProfileFields(current, changes) {
158
+ var _a, _b;
159
+ const changed = {};
160
+ if (changes.firstName !== undefined && (!current || ((_a = current.firstName) !== null && _a !== void 0 ? _a : '') !== changes.firstName)) {
161
+ changed.firstName = changes.firstName;
162
+ }
163
+ if (changes.lastName !== undefined && (!current || ((_b = current.lastName) !== null && _b !== void 0 ? _b : '') !== changes.lastName)) {
164
+ changed.lastName = changes.lastName;
165
+ }
166
+ if (changes.acceptsMarketing !== undefined && (!current || current.acceptsMarketing !== changes.acceptsMarketing)) {
167
+ changed.acceptsMarketing = changes.acceptsMarketing;
168
+ }
169
+ return changed;
170
+ }
171
+ /** A Customer Account API customer in the SDK's `Customer` shape, so screens don't care which sign-in it was. */
172
+ function toCustomer(raw) {
173
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
174
+ const a = raw.defaultAddress;
175
+ const address = a
176
+ ? {
177
+ id: a.id,
178
+ firstName: (_a = a.firstName) !== null && _a !== void 0 ? _a : undefined,
179
+ lastName: (_b = a.lastName) !== null && _b !== void 0 ? _b : undefined,
180
+ address1: (_c = a.address1) !== null && _c !== void 0 ? _c : '',
181
+ address2: (_d = a.address2) !== null && _d !== void 0 ? _d : undefined,
182
+ city: (_e = a.city) !== null && _e !== void 0 ? _e : '',
183
+ province: (_f = a.province) !== null && _f !== void 0 ? _f : undefined,
184
+ country: (_g = a.country) !== null && _g !== void 0 ? _g : '',
185
+ zip: (_h = a.zip) !== null && _h !== void 0 ? _h : '',
186
+ phone: (_j = a.phoneNumber) !== null && _j !== void 0 ? _j : undefined,
187
+ }
188
+ : null;
189
+ return {
190
+ id: raw.id,
191
+ email: (_l = (_k = raw.emailAddress) === null || _k === void 0 ? void 0 : _k.emailAddress) !== null && _l !== void 0 ? _l : '',
192
+ firstName: raw.firstName,
193
+ lastName: raw.lastName,
194
+ phone: (_o = (_m = raw.phoneNumber) === null || _m === void 0 ? void 0 : _m.phoneNumber) !== null && _o !== void 0 ? _o : null,
195
+ defaultAddress: address,
196
+ // Only SUBSCRIBED is consent; every other marketing state reads as opted out.
197
+ acceptsMarketing: ((_p = raw.emailAddress) === null || _p === void 0 ? void 0 : _p.marketingState) === 'SUBSCRIBED',
198
+ };
199
+ }
200
+ function defaultRandom() {
201
+ const c = globalThis.crypto;
202
+ if (!(c === null || c === void 0 ? void 0 : c.getRandomValues))
203
+ return null;
204
+ return (n) => c.getRandomValues(new Uint8Array(n));
205
+ }
206
+ function createCustomerSession(deps) {
207
+ var _a;
208
+ const now = (_a = deps.now) !== null && _a !== void 0 ? _a : (() => Date.now());
209
+ let state = { kind: null, customer: null, accessToken: null, loading: false, restoring: true };
210
+ const listeners = new Set();
211
+ let shopifyTokens = null;
212
+ let passwordToken = null;
213
+ /** Bumped whenever the session changes hands, so work started for an old one can't write over a new one. */
214
+ let generation = 0;
215
+ let refreshInFlight = null;
216
+ let latestAttempt = 0;
217
+ let busyCount = 0;
218
+ function set(patch) {
219
+ state = { ...state, ...patch };
220
+ listeners.forEach((listener) => listener());
221
+ }
222
+ async function busy(work) {
223
+ busyCount += 1;
224
+ if (busyCount === 1)
225
+ set({ loading: true });
226
+ try {
227
+ return await work();
228
+ }
229
+ finally {
230
+ busyCount -= 1;
231
+ if (busyCount === 0)
232
+ set({ loading: false });
233
+ }
234
+ }
235
+ function storage() {
236
+ const s = deps.storage();
237
+ if (!s)
238
+ throw new types_1.ShopifyError('ShopifyProvider: no storage for the customer session; pass auth.secureStorage');
239
+ return s;
240
+ }
241
+ async function read(key) {
242
+ var _a;
243
+ const s = deps.storage();
244
+ return s ? (_a = (await Promise.resolve(s.getItem(key)))) !== null && _a !== void 0 ? _a : null : null;
245
+ }
246
+ /** Per key and error-tolerant: the token must go even if another key's delete fails. */
247
+ async function removeKeys(keys) {
248
+ const s = deps.storage();
249
+ if (!s)
250
+ return;
251
+ await Promise.all(keys.map((key) => Promise.resolve().then(() => s.removeItem(key)).catch(() => undefined)));
252
+ }
253
+ function customerAccount() {
254
+ var _a;
255
+ const c = (_a = deps.options()) === null || _a === void 0 ? void 0 : _a.customerAccount;
256
+ if (!(c === null || c === void 0 ? void 0 : c.shopId) || !(c === null || c === void 0 ? void 0 : c.clientId)) {
257
+ throw new types_1.ShopifyError('ShopifyProvider: auth.customerAccount { shopId, clientId } is required for Shopify sign-in');
258
+ }
259
+ return c;
260
+ }
261
+ const urls = () => { var _a; return customerAccountUrls(customerAccount(), (_a = deps.apiVersion) === null || _a === void 0 ? void 0 : _a.call(deps)); };
262
+ // ── Storage ────────────────────────────────────────────────────────────────
263
+ async function saveShopifyTokens(tokens) {
264
+ const s = storage();
265
+ const k = exports.SHOPIFY_SESSION_KEYS;
266
+ await Promise.all([
267
+ s.setItem(k.accessToken, tokens.accessToken),
268
+ tokens.refreshToken ? s.setItem(k.refreshToken, tokens.refreshToken) : s.removeItem(k.refreshToken),
269
+ tokens.expiresAt !== null ? s.setItem(k.expiresAt, String(tokens.expiresAt)) : s.removeItem(k.expiresAt),
270
+ tokens.idToken ? s.setItem(k.idToken, tokens.idToken) : s.removeItem(k.idToken),
271
+ ]);
272
+ shopifyTokens = tokens;
273
+ }
274
+ async function savePasswordToken(token) {
275
+ const s = storage();
276
+ const expiresAt = Date.parse(token.expiresAt);
277
+ await Promise.all([
278
+ s.setItem(exports.PASSWORD_SESSION_KEYS.accessToken, token.accessToken),
279
+ Number.isFinite(expiresAt) ? s.setItem(exports.PASSWORD_SESSION_KEYS.expiresAt, String(expiresAt)) : s.removeItem(exports.PASSWORD_SESSION_KEYS.expiresAt),
280
+ ]);
281
+ passwordToken = { accessToken: token.accessToken, expiresAt: Number.isFinite(expiresAt) ? expiresAt : null };
282
+ }
283
+ async function readShopifyTokens() {
284
+ const k = exports.SHOPIFY_SESSION_KEYS;
285
+ const [accessToken, refreshToken, expiresAt, idToken] = await Promise.all([
286
+ read(k.accessToken), read(k.refreshToken), read(k.expiresAt), read(k.idToken),
287
+ ]);
288
+ if (!accessToken)
289
+ return null;
290
+ return { accessToken, refreshToken, expiresAt: expiresAt ? Number(expiresAt) : null, idToken };
291
+ }
292
+ async function readPasswordToken() {
293
+ var _a, _b, _c;
294
+ let accessToken = await read(exports.PASSWORD_SESSION_KEYS.accessToken);
295
+ const legacy = (_b = (_a = deps.legacyStorage) === null || _a === void 0 ? void 0 : _a.call(deps)) !== null && _b !== void 0 ? _b : null;
296
+ if (!accessToken && legacy) {
297
+ // Moved from its 0.8 key (plain storage) to the keychain key, once.
298
+ accessToken = (_c = (await Promise.resolve(legacy.getItem(exports.LEGACY_PASSWORD_TOKEN_KEY)))) !== null && _c !== void 0 ? _c : null;
299
+ if (accessToken) {
300
+ await Promise.resolve(storage().setItem(exports.PASSWORD_SESSION_KEYS.accessToken, accessToken));
301
+ await Promise.resolve(legacy.removeItem(exports.LEGACY_PASSWORD_TOKEN_KEY));
302
+ }
303
+ }
304
+ if (!accessToken)
305
+ return null;
306
+ const expiresAt = await read(exports.PASSWORD_SESSION_KEYS.expiresAt);
307
+ return { accessToken, expiresAt: expiresAt ? Number(expiresAt) : null };
308
+ }
309
+ /**
310
+ * Ends the session locally. `expired`: Shopify stopped accepting it, as opposed to the shopper
311
+ * signing out; it emits `auth:sessionExpired`, except on app open (see `restore`).
312
+ */
313
+ async function endSession(reason) {
314
+ var _a, _b;
315
+ const announce = reason === 'expired' && state.kind !== null && !state.restoring;
316
+ generation += 1;
317
+ shopifyTokens = null;
318
+ passwordToken = null;
319
+ await removeKeys([...Object.values(exports.SHOPIFY_SESSION_KEYS), ...Object.values(exports.PASSWORD_SESSION_KEYS)]);
320
+ const legacy = (_b = (_a = deps.legacyStorage) === null || _a === void 0 ? void 0 : _a.call(deps)) !== null && _b !== void 0 ? _b : null;
321
+ if (legacy)
322
+ await Promise.resolve().then(() => legacy.removeItem(exports.LEGACY_PASSWORD_TOKEN_KEY)).catch(() => undefined);
323
+ if (reason !== 'replaced')
324
+ set({ kind: null, customer: null, accessToken: null });
325
+ if (announce)
326
+ deps.emit('auth:sessionExpired');
327
+ }
328
+ // ── Shopify (new customer accounts) ───────────────────────────────────────
329
+ async function tokenRequest(params) {
330
+ let res;
331
+ try {
332
+ res = await fetch(urls().token, {
333
+ method: 'POST',
334
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
335
+ body: form(params),
336
+ });
337
+ }
338
+ catch (error) {
339
+ throw new types_1.ShopifyError(`Customer Account token request failed: ${describe(error)}`);
340
+ }
341
+ let json = null;
342
+ try {
343
+ json = JSON.parse(await res.text());
344
+ }
345
+ catch {
346
+ json = null;
347
+ }
348
+ if (!res.ok) {
349
+ const code = typeof (json === null || json === void 0 ? void 0 : json.error) === 'string' ? json.error : '';
350
+ // 400/401 with an OAuth error code is Shopify's final answer; anything else (5xx, 429, a
351
+ // proxy's HTML page) may pass, so it's a failure to reach the store, not a rejection.
352
+ if ((res.status === 400 || res.status === 401) && code)
353
+ throw new TokenRejected(code);
354
+ throw new types_1.ShopifyError(`Customer Account token request: HTTP ${res.status}`);
355
+ }
356
+ if (!json || typeof json.access_token !== 'string') {
357
+ throw new types_1.ShopifyError('Customer Account token response had no access_token');
358
+ }
359
+ const expiresIn = typeof json.expires_in === 'number' ? json.expires_in : Number(json.expires_in);
360
+ return {
361
+ accessToken: json.access_token,
362
+ refreshToken: typeof json.refresh_token === 'string' ? json.refresh_token : null,
363
+ expiresAt: Number.isFinite(expiresIn) && expiresIn > 0 ? now() + expiresIn * 1000 : null,
364
+ idToken: typeof json.id_token === 'string' ? json.id_token : null,
365
+ };
366
+ }
367
+ /**
368
+ * Shopify rotates the refresh token on use, so a second refresh with the same token is refused, and
369
+ * a refused refresh ends the session. Every caller wanting a token at once shares one refresh.
370
+ */
371
+ function refreshShopify() {
372
+ if (refreshInFlight)
373
+ return refreshInFlight;
374
+ const started = generation;
375
+ const current = shopifyTokens;
376
+ const run = async () => {
377
+ var _a, _b;
378
+ if (!(current === null || current === void 0 ? void 0 : current.refreshToken)) {
379
+ await endSession('expired');
380
+ return null;
381
+ }
382
+ let next;
383
+ try {
384
+ next = await tokenRequest({
385
+ grant_type: 'refresh_token',
386
+ client_id: customerAccount().clientId,
387
+ refresh_token: current.refreshToken,
388
+ });
389
+ }
390
+ catch (error) {
391
+ if (error instanceof TokenRejected) {
392
+ if (started === generation)
393
+ await endSession('expired');
394
+ return null;
395
+ }
396
+ throw error;
397
+ }
398
+ // Signed out (or in again) while this was in flight: the result belongs to a session that's gone.
399
+ if (started !== generation)
400
+ return null;
401
+ // A refresh response doesn't always carry a new refresh or id token; keep the ones we have.
402
+ await saveShopifyTokens({
403
+ accessToken: next.accessToken,
404
+ refreshToken: (_a = next.refreshToken) !== null && _a !== void 0 ? _a : current.refreshToken,
405
+ expiresAt: next.expiresAt,
406
+ idToken: (_b = next.idToken) !== null && _b !== void 0 ? _b : current.idToken,
407
+ });
408
+ set({ accessToken: next.accessToken });
409
+ return next.accessToken;
410
+ };
411
+ const flight = run().finally(() => {
412
+ if (refreshInFlight === flight)
413
+ refreshInFlight = null;
414
+ });
415
+ refreshInFlight = flight;
416
+ return flight;
417
+ }
418
+ /** A usable access token, refreshed or renewed when needed; null when signed out or the session ended. */
419
+ async function getAccessToken() {
420
+ if (state.kind === 'shopify' && shopifyTokens) {
421
+ const { accessToken, expiresAt } = shopifyTokens;
422
+ if (expiresAt === null || expiresAt - exports.EXPIRY_SKEW_MS > now())
423
+ return accessToken;
424
+ return refreshShopify();
425
+ }
426
+ if (state.kind === 'password' && passwordToken) {
427
+ const { accessToken, expiresAt } = passwordToken;
428
+ if (expiresAt === null || expiresAt - exports.EXPIRY_SKEW_MS > now())
429
+ return accessToken;
430
+ // Classic tokens can only be renewed before they expire; past that, the shopper signs in again.
431
+ await endSession('expired');
432
+ return null;
433
+ }
434
+ return null;
435
+ }
436
+ /**
437
+ * A token renewed now, for a caller whose service just refused the current one (Tile Credit's 401).
438
+ * A Shopify session refreshes, sharing the one refresh in flight with every other caller; a password
439
+ * session can't be renewed early, so null. Never ends the session itself: only Shopify refusing the
440
+ * refresh does, as it would on the next Customer Account API call. Throws when offline.
441
+ */
442
+ async function renewAccessToken() {
443
+ if (state.kind === 'shopify' && shopifyTokens)
444
+ return refreshShopify();
445
+ return null;
446
+ }
447
+ /**
448
+ * One Customer Account API call. The token goes in `Authorization` bare, with no `Bearer`: that's
449
+ * what this API expects. A 401 refreshes once and retries.
450
+ */
451
+ async function customerAccountRequest(query, variables) {
452
+ var _a;
453
+ if (state.kind !== 'shopify')
454
+ throw new types_1.ShopifyError('Not signed in with Shopify (new customer accounts)');
455
+ const send = async (token) => {
456
+ try {
457
+ return await fetch(urls().graphql, {
458
+ method: 'POST',
459
+ headers: { Authorization: token, 'Content-Type': 'application/json', Accept: 'application/json' },
460
+ body: JSON.stringify({ query, variables: variables !== null && variables !== void 0 ? variables : {} }),
461
+ });
462
+ }
463
+ catch (error) {
464
+ throw new types_1.ShopifyError(`Customer Account API request failed: ${describe(error)}`);
465
+ }
466
+ };
467
+ let token = await getAccessToken();
468
+ if (!token)
469
+ throw new types_1.ShopifyError('The customer session has ended');
470
+ let res = await send(token);
471
+ if (res.status === 401) {
472
+ // Shopify can revoke a token before its stated expiry (a password reset, an admin action).
473
+ token = await refreshShopify();
474
+ if (!token)
475
+ throw new types_1.ShopifyError('The customer session has ended');
476
+ res = await send(token);
477
+ if (res.status === 401) {
478
+ await endSession('expired');
479
+ throw new types_1.ShopifyError('The customer session has ended');
480
+ }
481
+ }
482
+ let json;
483
+ try {
484
+ json = JSON.parse(await res.text());
485
+ }
486
+ catch {
487
+ throw new types_1.ShopifyError(`Customer Account API HTTP ${res.status}: not JSON`);
488
+ }
489
+ if ((_a = json.errors) === null || _a === void 0 ? void 0 : _a.length)
490
+ throw new types_1.ShopifyError(`GraphQL error: ${json.errors.map((e) => e.message).join('; ')}`);
491
+ if (!json.data)
492
+ throw new types_1.ShopifyError(`Customer Account API HTTP ${res.status}: no data`);
493
+ return json.data;
494
+ }
495
+ async function loadShopifyProfile() {
496
+ const started = generation;
497
+ const data = await customerAccountRequest(PROFILE_QUERY);
498
+ if (started !== generation)
499
+ return null;
500
+ const customer = data.customer ? toCustomer(data.customer) : null;
501
+ set({ customer });
502
+ return customer;
503
+ }
504
+ function startSignIn() {
505
+ var _a, _b, _c;
506
+ const c = customerAccount();
507
+ const random = (_b = (_a = deps.options()) === null || _a === void 0 ? void 0 : _a.random) !== null && _b !== void 0 ? _b : defaultRandom();
508
+ if (!random)
509
+ throw new types_1.ShopifyError('ShopifyProvider: auth.random is required for Shopify sign-in on this engine (e.g. expo-crypto getRandomBytes)');
510
+ const pkce = (0, pkce_1.createPkce)(random);
511
+ const u = urls();
512
+ const id = ++latestAttempt;
513
+ let settled = false;
514
+ const params = {
515
+ client_id: c.clientId,
516
+ response_type: 'code',
517
+ redirect_uri: u.redirectUri,
518
+ scope: ((_c = c.scopes) !== null && _c !== void 0 ? _c : DEFAULT_SCOPES).join(' '),
519
+ state: pkce.state,
520
+ nonce: pkce.nonce,
521
+ code_challenge: pkce.challenge,
522
+ code_challenge_method: 'S256',
523
+ };
524
+ if (c.locale)
525
+ params.ui_locales = c.locale;
526
+ const isCallback = (url) => url === u.redirectUri || url.startsWith(`${u.redirectUri}?`) || url.startsWith(`${u.redirectUri}#`);
527
+ return {
528
+ url: `${u.authorize}?${form(params)}`,
529
+ redirectUri: u.redirectUri,
530
+ isCallback,
531
+ cancel: () => {
532
+ settled = true;
533
+ },
534
+ finish: async (callbackUrl) => {
535
+ // A second callback for the same attempt, or one for an attempt a newer sign-in replaced.
536
+ if (settled || id !== latestAttempt || !isCallback(callbackUrl))
537
+ return false;
538
+ settled = true;
539
+ return busy(() => completeSignIn(callbackUrl, pkce));
540
+ },
541
+ };
542
+ }
543
+ async function completeSignIn(callbackUrl, pkce) {
544
+ var _a;
545
+ const q = callbackParams(callbackUrl);
546
+ if (q.error) {
547
+ // The shopper declined: their choice, like closing the page, not a failure to toast.
548
+ if (q.error === 'access_denied')
549
+ return false;
550
+ deps.emit('auth:loginFailed', new types_1.ShopifyError(`Shopify sign-in failed: ${q.error}${q.error_description ? ` (${q.error_description})` : ''}`));
551
+ return false;
552
+ }
553
+ if (q.state !== pkce.state || !q.code) {
554
+ // Not the redirect this app asked for: never exchange a code it didn't start.
555
+ deps.emit('auth:loginFailed', new types_1.ShopifyError('Shopify sign-in returned an unexpected redirect (state mismatch)'));
556
+ return false;
557
+ }
558
+ let tokens;
559
+ try {
560
+ tokens = await tokenRequest({
561
+ grant_type: 'authorization_code',
562
+ client_id: customerAccount().clientId,
563
+ redirect_uri: urls().redirectUri,
564
+ code: q.code,
565
+ code_verifier: pkce.verifier,
566
+ });
567
+ }
568
+ catch (error) {
569
+ if (!(error instanceof TokenRejected))
570
+ throw error;
571
+ deps.emit('auth:loginFailed', error);
572
+ return false;
573
+ }
574
+ // The id token comes straight from Shopify's token endpoint over TLS, so its signature needn't be
575
+ // checked here (OpenID Connect Core 3.1.3.7); the nonce still ties it to this sign-in.
576
+ if (tokens.idToken && ((_a = jwtClaims(tokens.idToken)) === null || _a === void 0 ? void 0 : _a.nonce) !== pkce.nonce) {
577
+ deps.emit('auth:loginFailed', new types_1.ShopifyError('Shopify sign-in returned an id token for another sign-in (nonce mismatch)'));
578
+ return false;
579
+ }
580
+ await endSession('replaced');
581
+ await saveShopifyTokens(tokens);
582
+ set({ kind: 'shopify', accessToken: tokens.accessToken, customer: null });
583
+ try {
584
+ await loadShopifyProfile();
585
+ }
586
+ catch (error) {
587
+ // Signed in; the profile loads on the next `refresh()`.
588
+ console.warn('[sdk-shopify] signed in, but the profile did not load', error);
589
+ }
590
+ deps.emit('auth:loginSuccess');
591
+ return true;
592
+ }
593
+ async function signIn() {
594
+ var _a;
595
+ const open = (_a = deps.options()) === null || _a === void 0 ? void 0 : _a.openAuthSession;
596
+ if (!open) {
597
+ throw new types_1.ShopifyError('ShopifyProvider: auth.openAuthSession is required for signIn(); an in-app web view uses startSignIn()');
598
+ }
599
+ const attempt = startSignIn();
600
+ const result = await open(attempt.url, attempt.redirectUri);
601
+ if (result.type !== 'success' || !result.url) {
602
+ attempt.cancel();
603
+ return false;
604
+ }
605
+ return attempt.finish(result.url);
606
+ }
607
+ // ── Password (classic customer accounts) ──────────────────────────────────
608
+ async function startPasswordSession(token, customer) {
609
+ await endSession('replaced');
610
+ await savePasswordToken(token);
611
+ set({ kind: 'password', accessToken: token.accessToken, customer });
612
+ }
613
+ async function login(email, password) {
614
+ return busy(async () => {
615
+ let token;
616
+ try {
617
+ token = await shopify_1.shopify.customer.login({ email, password });
618
+ }
619
+ catch (error) {
620
+ // Only a credential problem is the shopper's mistake; the store being unreachable throws.
621
+ if ((0, errors_1.classifyAuthFailure)(error) === 'unknown')
622
+ throw error;
623
+ deps.emit('auth:loginFailed', error);
624
+ return false;
625
+ }
626
+ const customer = await shopify_1.shopify.customer.profile(token.accessToken);
627
+ await startPasswordSession(token, customer);
628
+ deps.emit('auth:loginSuccess');
629
+ return true;
630
+ });
631
+ }
632
+ async function signup(input) {
633
+ return busy(async () => {
634
+ let created;
635
+ try {
636
+ created = await shopify_1.shopify.customer.signup(input);
637
+ }
638
+ catch (error) {
639
+ if ((0, errors_1.classifyAuthFailure)(error) === 'unknown')
640
+ throw error;
641
+ deps.emit('auth:loginFailed', error);
642
+ return false;
643
+ }
644
+ await startPasswordSession(created.accessToken, created.customer);
645
+ // Signup signs the shopper in, so both fire: a host that only toasts on loginSuccess still says the right thing.
646
+ deps.emit('auth:signup');
647
+ deps.emit('auth:loginSuccess');
648
+ return true;
649
+ });
650
+ }
651
+ async function recoverPassword(email) {
652
+ await busy(async () => {
653
+ await shopify_1.shopify.customer.recoverPassword(email);
654
+ deps.emit('auth:recoverSent');
655
+ });
656
+ }
657
+ // ── Both ──────────────────────────────────────────────────────────────────
658
+ /** Re-reads the profile. Null when signed out or the session ended; throws when offline. */
659
+ async function refresh() {
660
+ if (state.kind === 'shopify')
661
+ return loadShopifyProfile();
662
+ if (state.kind === 'password') {
663
+ const started = generation;
664
+ const token = await getAccessToken();
665
+ if (!token)
666
+ return null;
667
+ const customer = await shopify_1.shopify.customer.profile(token);
668
+ if (started !== generation)
669
+ return null;
670
+ if (!customer) {
671
+ await endSession('expired');
672
+ return null;
673
+ }
674
+ set({ customer });
675
+ return customer;
676
+ }
677
+ return null;
678
+ }
679
+ /**
680
+ * Password session: one Storefront `customerUpdate`. It returns the updated customer, which becomes
681
+ * `customer`, so no second request is needed.
682
+ */
683
+ async function updatePasswordProfile(changed) {
684
+ const started = generation;
685
+ const token = await getAccessToken();
686
+ if (!token)
687
+ throw new types_1.ShopifyError('The customer session has ended');
688
+ const updated = await shopify_1.shopify.customer.updateProfile(token, changed);
689
+ if (started === generation)
690
+ set({ customer: updated });
691
+ }
692
+ /**
693
+ * Shopify session: `customerUpdate` for the names, then the consent mutation, stopping at the first
694
+ * refusal. Whatever Shopify accepted is re-read even when a later change was refused, so `customer`
695
+ * never shows a value Shopify doesn't hold.
696
+ */
697
+ async function updateShopifyProfile(changed) {
698
+ var _a, _b;
699
+ const { acceptsMarketing: subscribe, ...names } = changed;
700
+ const accepted = {};
701
+ try {
702
+ if (Object.keys(names).length > 0) {
703
+ const data = await customerAccountRequest(NAME_UPDATE_MUTATION, { input: names });
704
+ (0, client_1.assertNoUserErrors)('customerUpdate', (_a = data.customerUpdate) === null || _a === void 0 ? void 0 : _a.userErrors);
705
+ Object.assign(accepted, names);
706
+ }
707
+ if (subscribe !== undefined) {
708
+ const data = await customerAccountRequest(subscribe ? MARKETING_SUBSCRIBE_MUTATION : MARKETING_UNSUBSCRIBE_MUTATION);
709
+ const payload = subscribe ? data.customerEmailMarketingSubscribe : data.customerEmailMarketingUnsubscribe;
710
+ // "Already subscribed" is the state the shopper asked for, not a failure. It happens when the
711
+ // loaded profile was out of date (subscribed on another device since).
712
+ const errors = ((_b = payload === null || payload === void 0 ? void 0 : payload.userErrors) !== null && _b !== void 0 ? _b : []).filter((e) => !(subscribe && e.code === 'CUSTOMER_ALREADY_SUBSCRIBED'));
713
+ (0, client_1.assertNoUserErrors)(subscribe ? 'customerEmailMarketingSubscribe' : 'customerEmailMarketingUnsubscribe', errors);
714
+ accepted.acceptsMarketing = subscribe;
715
+ }
716
+ }
717
+ finally {
718
+ if (Object.keys(accepted).length > 0)
719
+ await reloadAfterUpdate(accepted);
720
+ }
721
+ }
722
+ /**
723
+ * Re-reads the profile after a write Shopify accepted. If that read fails (offline the moment after
724
+ * saving), the accepted values are shown instead, so the screen doesn't fall back to the old ones.
725
+ * Never throws.
726
+ */
727
+ async function reloadAfterUpdate(accepted) {
728
+ const started = generation;
729
+ try {
730
+ await loadShopifyProfile();
731
+ }
732
+ catch (error) {
733
+ console.warn('[sdk-shopify] profile saved, but did not reload', error);
734
+ const current = state.customer;
735
+ if (started === generation && current)
736
+ set({ customer: { ...current, ...accepted } });
737
+ }
738
+ }
739
+ /**
740
+ * Changes the shopper's name and email marketing consent, for either kind of session. Sends only
741
+ * the fields that differ from `customer` (nothing to send resolves `true` at once). `true` once
742
+ * Shopify accepted every change, with `customer` showing the new values. `false` when signed out, or
743
+ * when Shopify refused a value or couldn't be reached; the reason goes to `console.warn`. Never
744
+ * throws, so a form only has to check the answer.
745
+ */
746
+ async function updateProfile(changes) {
747
+ try {
748
+ return await busy(async () => {
749
+ const kind = state.kind;
750
+ if (!kind)
751
+ throw new types_1.ShopifyError('Not signed in');
752
+ const changed = changedProfileFields(state.customer, changes);
753
+ if (Object.keys(changed).length === 0)
754
+ return true;
755
+ if (kind === 'password')
756
+ await updatePasswordProfile(changed);
757
+ else
758
+ await updateShopifyProfile(changed);
759
+ return true;
760
+ });
761
+ }
762
+ catch (error) {
763
+ console.warn('[sdk-shopify] profile update failed', error);
764
+ return false;
765
+ }
766
+ }
767
+ /**
768
+ * Local first, so nothing between the tap and being signed out can fail; then Shopify is told, and
769
+ * its answer doesn't matter. Always resolves.
770
+ */
771
+ async function logout() {
772
+ await busy(async () => {
773
+ var _a, _b;
774
+ const kind = state.kind;
775
+ const idToken = (_a = shopifyTokens === null || shopifyTokens === void 0 ? void 0 : shopifyTokens.idToken) !== null && _a !== void 0 ? _a : null;
776
+ const classicToken = (_b = passwordToken === null || passwordToken === void 0 ? void 0 : passwordToken.accessToken) !== null && _b !== void 0 ? _b : null;
777
+ let logoutUrl = null;
778
+ try {
779
+ if (kind === 'shopify' && idToken)
780
+ logoutUrl = `${urls().logout}?id_token_hint=${encodeURIComponent(idToken)}`;
781
+ }
782
+ catch {
783
+ logoutUrl = null;
784
+ }
785
+ await endSession('signed-out');
786
+ if (logoutUrl) {
787
+ // Ends Shopify's session server-side. No `post_logout_redirect_uri`: Shopify would answer
788
+ // with a redirect to the app's scheme, which fetch can't follow. `manual`: only arriving matters.
789
+ void fetch(logoutUrl, { method: 'GET', redirect: 'manual' }).catch(() => undefined);
790
+ }
791
+ if (classicToken) {
792
+ try {
793
+ await shopify_1.shopify.customer.logout(classicToken);
794
+ }
795
+ catch (error) {
796
+ console.warn('[sdk-shopify] token delete failed; signed out locally', error);
797
+ }
798
+ }
799
+ deps.emit('auth:logout');
800
+ });
801
+ }
802
+ /**
803
+ * Reads a stored session on app open. A session Shopify no longer accepts is dropped silently: an
804
+ * expired login isn't a failed one, and a toast at a shopper who did nothing is noise. Offline
805
+ * keeps the session; the profile loads on the next `refresh()`.
806
+ */
807
+ async function restore() {
808
+ var _a;
809
+ const started = generation;
810
+ try {
811
+ const stored = await readShopifyTokens();
812
+ if (started !== generation)
813
+ return;
814
+ if (stored) {
815
+ shopifyTokens = stored;
816
+ set({ kind: 'shopify', accessToken: stored.accessToken });
817
+ const expired = stored.expiresAt !== null && stored.expiresAt - exports.EXPIRY_SKEW_MS <= now();
818
+ if (expired) {
819
+ // Null when Shopify refused it (the session is gone); offline keeps the session as is.
820
+ const token = await refreshShopify().catch((error) => {
821
+ console.warn('[sdk-shopify] session refresh failed on app open; kept', error);
822
+ return null;
823
+ });
824
+ if (!token)
825
+ return;
826
+ }
827
+ await loadShopifyProfile().catch((error) => console.warn('[sdk-shopify] profile did not load on app open', error));
828
+ return;
829
+ }
830
+ const classic = await readPasswordToken();
831
+ if (started !== generation || !classic)
832
+ return;
833
+ if (classic.expiresAt !== null && classic.expiresAt - exports.EXPIRY_SKEW_MS <= now()) {
834
+ await endSession('expired');
835
+ return;
836
+ }
837
+ passwordToken = classic;
838
+ set({ kind: 'password', accessToken: classic.accessToken });
839
+ if (classic.expiresAt !== null && classic.expiresAt - now() < exports.RENEW_WITHIN_MS) {
840
+ try {
841
+ const renewed = await shopify_1.shopify.customer.renew(classic.accessToken);
842
+ if (started === generation) {
843
+ await savePasswordToken(renewed);
844
+ set({ accessToken: renewed.accessToken });
845
+ }
846
+ }
847
+ catch (error) {
848
+ // Still valid for now; it's tried again on the next app open.
849
+ console.warn('[sdk-shopify] token renew failed; kept', error);
850
+ }
851
+ }
852
+ let customer;
853
+ try {
854
+ customer = await shopify_1.shopify.customer.profile((_a = passwordToken === null || passwordToken === void 0 ? void 0 : passwordToken.accessToken) !== null && _a !== void 0 ? _a : classic.accessToken);
855
+ }
856
+ catch (error) {
857
+ console.warn('[sdk-shopify] profile did not load on app open; session kept', error);
858
+ return;
859
+ }
860
+ if (started !== generation)
861
+ return;
862
+ if (!customer)
863
+ await endSession('expired');
864
+ else
865
+ set({ customer });
866
+ }
867
+ catch (error) {
868
+ console.warn('[sdk-shopify] session restore failed', error);
869
+ }
870
+ finally {
871
+ set({ restoring: false });
872
+ }
873
+ }
874
+ return {
875
+ getState: () => state,
876
+ subscribe(listener) {
877
+ listeners.add(listener);
878
+ return () => {
879
+ listeners.delete(listener);
880
+ };
881
+ },
882
+ restore,
883
+ login,
884
+ signup,
885
+ recoverPassword,
886
+ startSignIn,
887
+ signIn,
888
+ getAccessToken,
889
+ renewAccessToken,
890
+ customerAccountRequest,
891
+ refresh,
892
+ updateProfile,
893
+ logout,
894
+ };
895
+ }
896
+ //# sourceMappingURL=session.js.map