@agnostack/next-shopify 4.0.0-canary.7 → 4.0.0-canary.8

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.
@@ -1,3 +1,4 @@
1
1
  export * from '../shared/index.js';
2
2
  export * from './handlers/index.js';
3
+ export * from '../storage/index.js';
3
4
  export { getShopifyHelpers, prepareHeaders } from './utils/index.js';
@@ -1,6 +1,5 @@
1
1
  export * from './billing.js';
2
2
  export * from './crypto.js';
3
- export * from './firebase.js';
4
3
  export * from './headers.js';
5
4
  export * from './json.js';
6
5
  export * from './session.js';
@@ -12,10 +12,10 @@ var __rest = (this && this.__rest) || function (s, e) {
12
12
  import { Base64 } from 'js-base64';
13
13
  import { HttpResponseError, RequestedTokenType } from '@shopify/shopify-api';
14
14
  import { isTrue, isString, isNumericOnly, ensureString, ensureArray, cleanObject, objectEmpty, stringEmpty, ensureObject, isTokenExpired, stringNotEmpty, objectNotEmpty, stringEmptyOnly, removeLeadingSlash, ShopifyAuthenticationError, ShopifySessionAuthenticationError, ShopifySessionMissingError, ShopifySessionInvalidError, SESSION_TOKEN_PARAM, APP_QUERY, TRANSFORM, } from '../../shared/index.js';
15
- import { replaceHeaders } from './headers.js';
15
+ import { ensureStorage } from '../../storage/index.js';
16
16
  import { ensureBilling } from './billing.js';
17
+ import { replaceHeaders } from './headers.js';
17
18
  import { getInitializedShopify } from './shopify.js';
18
- import { loadState, storeState, deleteState, loadCallback, updateCallback, findSessionIdsByShop, deleteSessionsCallback, FirebaseSessionHandler, } from './firebase.js';
19
19
  import { CryptoHandler } from './crypto.js';
20
20
  // TODO!!!: keep in sync between next-shopify and lib-core
21
21
  /** @param {any} [shopifyData] */
@@ -61,7 +61,39 @@ export const normalizeShopifyId = (shopifyData) => {
61
61
  * @param {any} [config]
62
62
  * @returns {Promise<import('./types.js').ShopifyHelpers>}
63
63
  */
64
- export const getShopifyHelpers = async (serverRuntimeConfig, config) => {
64
+ // What may be persisted, decided HERE rather than in an implementation. Both write callbacks in
65
+ // the retired Firestore handler carried this rule, so every third-party store would have had to
66
+ // reimplement it - and any that missed it would quietly persist customer data.
67
+ //
68
+ // ⚠️ `associated_user` is destructured out for DPA compliance. The JSON round-trip strips
69
+ // prototypes and undefined values that a store may not accept.
70
+ /** @param {any} session */
71
+ const prepareSessionPayload = (session) => {
72
+ const _a = JSON.parse(JSON.stringify(session)), { onlineAccessInfo: _onlineAccessInfo } = _a, sessionData = __rest(_a, ["onlineAccessInfo"]);
73
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
74
+ const _b = _onlineAccessInfo !== null && _onlineAccessInfo !== void 0 ? _onlineAccessInfo : {}, { associated_user } = _b, onlineAccessInfo = __rest(_b, ["associated_user"]);
75
+ return Object.assign({ onlineAccessInfo }, sessionData);
76
+ };
77
+ // NOTE: imported lazily so an app that supplies its own storage never loads firebase-admin,
78
+ // even though it is still the default. PR 4 removes this branch and the peer with it.
79
+ /**
80
+ * @param {any} firebaseConfig
81
+ * @param {any} options
82
+ */
83
+ const createDefaultStorage = async (firebaseConfig, options) => {
84
+ const { createFirestoreStorage } = await import('../../storage/firestore/index.js');
85
+ return createFirestoreStorage(firebaseConfig, options);
86
+ };
87
+ // TODO: move to curry??
88
+ /**
89
+ * @param {any} [serverRuntimeConfig]
90
+ * @param {any} [config]
91
+ * @param {{ storage?: import('../../storage/index.js').SessionStorage }} [options]
92
+ * `storage` defaults to the Firestore implementation, which is the only reason this package
93
+ * still declares firebase-admin.
94
+ * @returns {Promise<import('./types.js').ShopifyHelpers>}
95
+ */
96
+ export const getShopifyHelpers = async (serverRuntimeConfig, config, { storage: _storage } = {}) => {
65
97
  var _a, _b;
66
98
  const shopify = getInitializedShopify(serverRuntimeConfig);
67
99
  const { APP_ID, FB_CONFIG, FB_ROOT_COLLECTION, SHOPIFY_RUN_MODE, SESSION_CONFIG, PAYMENT_CONFIG, } = serverRuntimeConfig;
@@ -71,10 +103,10 @@ export const getShopifyHelpers = async (serverRuntimeConfig, config) => {
71
103
  // TODO: should this use our helper instead (and/or as a fallback)??
72
104
  const shopId = (_b = (_a = shopify === null || shopify === void 0 ? void 0 : shopify.utils) === null || _a === void 0 ? void 0 : _a.sanitizeShop) === null || _b === void 0 ? void 0 : _b.call(_a, shop);
73
105
  const cryptoHandler = CryptoHandler.getInstance(SESSION_CONFIG);
74
- const sessionHandler = FirebaseSessionHandler.getInstance(FB_CONFIG, {
106
+ const storage = ensureStorage(_storage !== null && _storage !== void 0 ? _storage : await createDefaultStorage(FB_CONFIG, {
75
107
  collection: FB_ROOT_COLLECTION,
76
108
  configurationId: APP_ID,
77
- });
109
+ }));
78
110
  // NOTE: this should handle running in next to test non-shopify functionality
79
111
  if (!shopify) {
80
112
  return {
@@ -174,7 +206,7 @@ export const getShopifyHelpers = async (serverRuntimeConfig, config) => {
174
206
  /** @param {any} [session] */
175
207
  const encryptUpdateSession = async (session) => {
176
208
  if (stringNotEmpty(session === null || session === void 0 ? void 0 : session.id)) {
177
- return updateCallback(sessionHandler, shopId, ensureEncryptedSession(session));
209
+ return storage.updateSession(shopId, prepareSessionPayload(ensureEncryptedSession(session)));
178
210
  }
179
211
  throw new ShopifySessionInvalidError('Invalid session');
180
212
  };
@@ -201,7 +233,7 @@ export const getShopifyHelpers = async (serverRuntimeConfig, config) => {
201
233
  rawResponse: res,
202
234
  });
203
235
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
204
- const _c = (_b = (await loadCallback(sessionHandler, shopId, sessionId))) !== null && _b !== void 0 ? _b : {}, { accessToken: encryptedOnlineAccessToken } = _c, storedOnlineSession = __rest(_c, ["accessToken"]);
236
+ const _c = (_b = (await storage.loadSession(shopId, sessionId))) !== null && _b !== void 0 ? _b : {}, { accessToken: encryptedOnlineAccessToken } = _c, storedOnlineSession = __rest(_c, ["accessToken"]);
205
237
  let session = storedOnlineSession;
206
238
  let accessToken = encryptedOnlineAccessToken;
207
239
  let isEncrypted = true; // TODO: explore removing (isEncryptyed)
@@ -227,7 +259,7 @@ export const getShopifyHelpers = async (serverRuntimeConfig, config) => {
227
259
  const verifyOfflineSession = async () => {
228
260
  var _a;
229
261
  const sessionId = shopify.session.getOfflineId(shop);
230
- const _b = (_a = (await loadCallback(sessionHandler, shopId, sessionId))) !== null && _a !== void 0 ? _a : {}, { accessToken: encryptedOfflineAccessToken } = _b, offlineSession = __rest(_b, ["accessToken"]);
262
+ const _b = (_a = (await storage.loadSession(shopId, sessionId))) !== null && _a !== void 0 ? _a : {}, { accessToken: encryptedOfflineAccessToken } = _b, offlineSession = __rest(_b, ["accessToken"]);
231
263
  return {
232
264
  sessionId,
233
265
  session: offlineSession,
@@ -378,19 +410,19 @@ export const getShopifyHelpers = async (serverRuntimeConfig, config) => {
378
410
  loadSession: decryptVerifySession,
379
411
  storeSession: encryptStoreSession,
380
412
  ensureBilling: handleEnsureBilling,
381
- loadState: async () => loadState(sessionHandler, shop),
413
+ loadState: async () => storage.loadState(shop),
382
414
  /** @param {any} state */
383
- storeState: async (state) => storeState(sessionHandler, state),
384
- deleteState: async () => deleteState(sessionHandler, shop),
415
+ storeState: async (state) => storage.storeState(shop, state),
416
+ deleteState: async () => storage.deleteState(shop),
385
417
  /**
386
418
  * @param {any} sessionId
387
419
  * @param {any} data
388
420
  */
389
421
  updateSession: async (sessionId, data) => encryptUpdateSession(Object.assign(Object.assign({}, data), { id: sessionId })),
390
422
  /** @param {any} sessionIds */
391
- deleteSessions: async (sessionIds) => deleteSessionsCallback(sessionHandler, shopId, sessionIds),
423
+ deleteSessions: async (sessionIds) => storage.deleteSessions(shopId, sessionIds),
392
424
  /** @param {any} _shop */
393
- findSessionIds: async (_shop) => { var _a, _b; return findSessionIdsByShop(sessionHandler, (_b = (_a = shopify === null || shopify === void 0 ? void 0 : shopify.utils) === null || _a === void 0 ? void 0 : _a.sanitizeShop) === null || _b === void 0 ? void 0 : _b.call(_a, _shop)); },
425
+ findSessionIds: async (_shop) => { var _a, _b; return storage.findSessionIds((_b = (_a = shopify === null || shopify === void 0 ? void 0 : shopify.utils) === null || _a === void 0 ? void 0 : _a.sanitizeShop) === null || _b === void 0 ? void 0 : _b.call(_a, _shop)); },
394
426
  };
395
427
  };
396
428
  /**
@@ -0,0 +1,77 @@
1
+ import { cert, getApp, getApps, initializeApp } from 'firebase-admin/app';
2
+ import { getFirestore } from 'firebase-admin/firestore';
3
+ import { ensureArray, stringEmpty, objectNotEmpty, COLLECTION_ROOT, STATE_PREFIX, } from '../../shared/index.js';
4
+ // firebase-admin allows one app per process, so an existing one is reused rather than replaced.
5
+ // NOTE: the retired handler also passed `databaseURL`, which configures the Realtime Database.
6
+ // Nothing here has ever used it - no `getDatabase`, no `firebase-admin/database` import.
7
+ /** @param {any} firebaseConfig */
8
+ const initializeFirebaseApp = (firebaseConfig) => (getApps().length
9
+ ? getApp()
10
+ : initializeApp({ credential: cert(firebaseConfig) }));
11
+ /**
12
+ * The Firestore implementation of the session-storage contract, kept here so the core never
13
+ * imports firebase-admin. Sessions and state share one collection per shop; state is a single
14
+ * document per shop, prefixed so it cannot collide with a session id.
15
+ *
16
+ * @param {any} firebaseConfig a firebase service-account config
17
+ * @param {{ collection?: string, configurationId: string }} options
18
+ * @returns {import('../index.js').SessionStorage}
19
+ */
20
+ export const createFirestoreStorage = (firebaseConfig, { collection = COLLECTION_ROOT, configurationId }) => {
21
+ if (!objectNotEmpty(firebaseConfig)) {
22
+ throw new Error('createFirestoreStorage requires a firebase service-account config');
23
+ }
24
+ const firestore = getFirestore(initializeFirebaseApp(firebaseConfig));
25
+ /** @param {any} shop */
26
+ const shopCollection = (shop) => (firestore
27
+ .collection(collection)
28
+ .doc(shop)
29
+ .collection(configurationId));
30
+ /** @param {any} shop */
31
+ const stateRef = (shop) => shopCollection(shop).doc(`${STATE_PREFIX}${shop}`);
32
+ /** @param {any} docRef */
33
+ const readDocument = async (docRef) => {
34
+ const document = await docRef.get();
35
+ return document.exists ? document.data() : undefined;
36
+ };
37
+ /** @type {import('../index.js').SessionStorage} */
38
+ const storage = {
39
+ loadSession: async (shopId, sessionId) => (stringEmpty(sessionId)
40
+ ? undefined
41
+ : readDocument(shopCollection(shopId).doc(`${sessionId}`))),
42
+ // NOTE: merge rather than replace. The core writes partial sessions - a billing status
43
+ // update carries no access token - and a replacing write would drop the rest.
44
+ updateSession: async (shopId, session) => {
45
+ const docRef = shopCollection(shopId).doc(session.id);
46
+ await docRef.set(session, { merge: true });
47
+ return readDocument(docRef);
48
+ },
49
+ deleteSessions: async (shopId, sessionIds) => {
50
+ const collectionRef = shopCollection(shopId);
51
+ return Promise.all(ensureArray(sessionIds).map((sessionId) => collectionRef.doc(sessionId).delete()));
52
+ },
53
+ findSessionIds: async (shopId) => {
54
+ // NOTE: empty rather than an error - this is the state on an initial install
55
+ if (stringEmpty(shopId)) {
56
+ return [];
57
+ }
58
+ const documents = await shopCollection(shopId).get();
59
+ return documents.docs.map((/** @type {any} */ { id }) => id);
60
+ },
61
+ loadState: async (shop) => (stringEmpty(shop)
62
+ ? undefined
63
+ : readDocument(stateRef(shop))),
64
+ storeState: async (shop, state) => {
65
+ if (stringEmpty(shop)) {
66
+ return undefined;
67
+ }
68
+ const docRef = stateRef(shop);
69
+ await docRef.set(state);
70
+ return readDocument(docRef);
71
+ },
72
+ deleteState: async (shop) => (stringEmpty(shop)
73
+ ? undefined
74
+ : stateRef(shop).delete()),
75
+ };
76
+ return storage;
77
+ };
@@ -0,0 +1,54 @@
1
+ import { ensureArray } from '../shared/index.js';
2
+ /**
3
+ * Shop and session ids are nullable: `sanitizeShop` returns null for anything it does not
4
+ * recognise, and the core forwards that rather than guarding.
5
+ *
6
+ * @typedef {string | null | undefined} ShopId
7
+ */
8
+ /**
9
+ * What the session layer needs from a store. Two entities, sessions and state, both scoped to a
10
+ * shop. Nothing here is Firestore-specific: `@agnostack/next-shopify/firestore` is one
11
+ * implementation, and an app can supply its own.
12
+ *
13
+ * ⚠️ Values arrive ALREADY ENCRYPTED and already stripped of anything that must not be persisted.
14
+ * An implementation stores and returns what it is handed - it must not inspect, reshape or
15
+ * re-encrypt, or the core's crypto and DPA guarantees stop holding.
16
+ *
17
+ * ⚠️ `findSessionIds` returns `[]` rather than erroring for an unknown shop - that is what an
18
+ * initial install looks like.
19
+ *
20
+ * @typedef {object} SessionStorage
21
+ * @property {(shopId: ShopId, sessionId: ShopId) => Promise<any>} loadSession
22
+ * @property {(shopId: ShopId, session: any) => Promise<any>} updateSession
23
+ * MERGES into the stored session. The core writes partial sessions, so a replacing write
24
+ * would silently drop fields it did not send.
25
+ * @property {(shopId: ShopId, sessionIds: any) => Promise<any>} deleteSessions
26
+ * @property {(shopId: ShopId) => Promise<string[]>} findSessionIds
27
+ * @property {(shop: ShopId) => Promise<any>} loadState
28
+ * @property {(shop: ShopId, state: any) => Promise<any>} storeState
29
+ * @property {(shop: ShopId) => Promise<any>} deleteState
30
+ */
31
+ export const STORAGE_METHODS = [
32
+ 'loadState',
33
+ 'storeState',
34
+ 'deleteState',
35
+ 'loadSession',
36
+ 'updateSession',
37
+ 'deleteSessions',
38
+ 'findSessionIds'
39
+ ];
40
+ /**
41
+ * Fails at composition rather than at the first OAuth round-trip. Nothing automated covers the
42
+ * Shopify auth flow, so a missing method would otherwise surface as a runtime TypeError in a
43
+ * callback that needs a real dev store to reach.
44
+ *
45
+ * @param {any} storage
46
+ * @returns {SessionStorage}
47
+ */
48
+ export const ensureStorage = (storage) => {
49
+ const missing = ensureArray(STORAGE_METHODS).filter((method) => (typeof (storage === null || storage === void 0 ? void 0 : storage[method]) !== 'function'));
50
+ if (missing.length) {
51
+ throw new Error(`Invalid session storage, missing: ${missing.join(', ')}`);
52
+ }
53
+ return storage;
54
+ };
@@ -1,3 +1,4 @@
1
1
  export * from '../shared/index.js';
2
2
  export * from './handlers/index.js';
3
+ export * from '../storage/index.js';
3
4
  export { getShopifyHelpers, prepareHeaders } from './utils/index.js';
package/dist/lib/index.js CHANGED
@@ -17,6 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.prepareHeaders = exports.getShopifyHelpers = void 0;
18
18
  __exportStar(require("../shared/index.js"), exports);
19
19
  __exportStar(require("./handlers/index.js"), exports);
20
+ __exportStar(require("../storage/index.js"), exports);
20
21
  var index_js_1 = require("./utils/index.js");
21
22
  Object.defineProperty(exports, "getShopifyHelpers", { enumerable: true, get: function () { return index_js_1.getShopifyHelpers; } });
22
23
  Object.defineProperty(exports, "prepareHeaders", { enumerable: true, get: function () { return index_js_1.prepareHeaders; } });
@@ -1,6 +1,5 @@
1
1
  export * from "./billing.js";
2
2
  export * from "./crypto.js";
3
- export * from "./firebase.js";
4
3
  export * from "./headers.js";
5
4
  export * from "./json.js";
6
5
  export * from "./session.js";
@@ -16,7 +16,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./billing.js"), exports);
18
18
  __exportStar(require("./crypto.js"), exports);
19
- __exportStar(require("./firebase.js"), exports);
20
19
  __exportStar(require("./headers.js"), exports);
21
20
  __exportStar(require("./json.js"), exports);
22
21
  __exportStar(require("./session.js"), exports);
@@ -13,5 +13,7 @@ export function normalizeShopifyId(shopifyData?: any): {
13
13
  entity_type?: string | undefined;
14
14
  id?: string | undefined;
15
15
  };
16
- export function getShopifyHelpers(serverRuntimeConfig?: any, config?: any): Promise<import("./types.js").ShopifyHelpers>;
16
+ export function getShopifyHelpers(serverRuntimeConfig?: any, config?: any, { storage: _storage }?: {
17
+ storage?: import("../../storage/index.js").SessionStorage;
18
+ }): Promise<import("./types.js").ShopifyHelpers>;
17
19
  export function prepareHeaders(serverRuntimeConfig?: any): ({ generateReplacements, appData, shop, appId, headers: _headers, }?: any) => Promise<any>;
@@ -15,10 +15,10 @@ exports.prepareHeaders = exports.getShopifyHelpers = exports.normalizeShopifyId
15
15
  const js_base64_1 = require("js-base64");
16
16
  const shopify_api_1 = require("@shopify/shopify-api");
17
17
  const index_js_1 = require("../../shared/index.js");
18
- const headers_js_1 = require("./headers.js");
18
+ const index_js_2 = require("../../storage/index.js");
19
19
  const billing_js_1 = require("./billing.js");
20
+ const headers_js_1 = require("./headers.js");
20
21
  const shopify_js_1 = require("./shopify.js");
21
- const firebase_js_1 = require("./firebase.js");
22
22
  const crypto_js_1 = require("./crypto.js");
23
23
  // TODO!!!: keep in sync between next-shopify and lib-core
24
24
  /** @param {any} [shopifyData] */
@@ -65,7 +65,39 @@ exports.normalizeShopifyId = normalizeShopifyId;
65
65
  * @param {any} [config]
66
66
  * @returns {Promise<import('./types.js').ShopifyHelpers>}
67
67
  */
68
- const getShopifyHelpers = async (serverRuntimeConfig, config) => {
68
+ // What may be persisted, decided HERE rather than in an implementation. Both write callbacks in
69
+ // the retired Firestore handler carried this rule, so every third-party store would have had to
70
+ // reimplement it - and any that missed it would quietly persist customer data.
71
+ //
72
+ // ⚠️ `associated_user` is destructured out for DPA compliance. The JSON round-trip strips
73
+ // prototypes and undefined values that a store may not accept.
74
+ /** @param {any} session */
75
+ const prepareSessionPayload = (session) => {
76
+ const _a = JSON.parse(JSON.stringify(session)), { onlineAccessInfo: _onlineAccessInfo } = _a, sessionData = __rest(_a, ["onlineAccessInfo"]);
77
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
78
+ const _b = _onlineAccessInfo !== null && _onlineAccessInfo !== void 0 ? _onlineAccessInfo : {}, { associated_user } = _b, onlineAccessInfo = __rest(_b, ["associated_user"]);
79
+ return Object.assign({ onlineAccessInfo }, sessionData);
80
+ };
81
+ // NOTE: imported lazily so an app that supplies its own storage never loads firebase-admin,
82
+ // even though it is still the default. PR 4 removes this branch and the peer with it.
83
+ /**
84
+ * @param {any} firebaseConfig
85
+ * @param {any} options
86
+ */
87
+ const createDefaultStorage = async (firebaseConfig, options) => {
88
+ const { createFirestoreStorage } = await import('../../storage/firestore/index.js');
89
+ return createFirestoreStorage(firebaseConfig, options);
90
+ };
91
+ // TODO: move to curry??
92
+ /**
93
+ * @param {any} [serverRuntimeConfig]
94
+ * @param {any} [config]
95
+ * @param {{ storage?: import('../../storage/index.js').SessionStorage }} [options]
96
+ * `storage` defaults to the Firestore implementation, which is the only reason this package
97
+ * still declares firebase-admin.
98
+ * @returns {Promise<import('./types.js').ShopifyHelpers>}
99
+ */
100
+ const getShopifyHelpers = async (serverRuntimeConfig, config, { storage: _storage } = {}) => {
69
101
  var _a, _b;
70
102
  const shopify = (0, shopify_js_1.getInitializedShopify)(serverRuntimeConfig);
71
103
  const { APP_ID, FB_CONFIG, FB_ROOT_COLLECTION, SHOPIFY_RUN_MODE, SESSION_CONFIG, PAYMENT_CONFIG, } = serverRuntimeConfig;
@@ -75,10 +107,10 @@ const getShopifyHelpers = async (serverRuntimeConfig, config) => {
75
107
  // TODO: should this use our helper instead (and/or as a fallback)??
76
108
  const shopId = (_b = (_a = shopify === null || shopify === void 0 ? void 0 : shopify.utils) === null || _a === void 0 ? void 0 : _a.sanitizeShop) === null || _b === void 0 ? void 0 : _b.call(_a, shop);
77
109
  const cryptoHandler = crypto_js_1.CryptoHandler.getInstance(SESSION_CONFIG);
78
- const sessionHandler = firebase_js_1.FirebaseSessionHandler.getInstance(FB_CONFIG, {
110
+ const storage = (0, index_js_2.ensureStorage)(_storage !== null && _storage !== void 0 ? _storage : await createDefaultStorage(FB_CONFIG, {
79
111
  collection: FB_ROOT_COLLECTION,
80
112
  configurationId: APP_ID,
81
- });
113
+ }));
82
114
  // NOTE: this should handle running in next to test non-shopify functionality
83
115
  if (!shopify) {
84
116
  return {
@@ -178,7 +210,7 @@ const getShopifyHelpers = async (serverRuntimeConfig, config) => {
178
210
  /** @param {any} [session] */
179
211
  const encryptUpdateSession = async (session) => {
180
212
  if ((0, index_js_1.stringNotEmpty)(session === null || session === void 0 ? void 0 : session.id)) {
181
- return (0, firebase_js_1.updateCallback)(sessionHandler, shopId, ensureEncryptedSession(session));
213
+ return storage.updateSession(shopId, prepareSessionPayload(ensureEncryptedSession(session)));
182
214
  }
183
215
  throw new index_js_1.ShopifySessionInvalidError('Invalid session');
184
216
  };
@@ -205,7 +237,7 @@ const getShopifyHelpers = async (serverRuntimeConfig, config) => {
205
237
  rawResponse: res,
206
238
  });
207
239
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
208
- const _c = (_b = (await (0, firebase_js_1.loadCallback)(sessionHandler, shopId, sessionId))) !== null && _b !== void 0 ? _b : {}, { accessToken: encryptedOnlineAccessToken } = _c, storedOnlineSession = __rest(_c, ["accessToken"]);
240
+ const _c = (_b = (await storage.loadSession(shopId, sessionId))) !== null && _b !== void 0 ? _b : {}, { accessToken: encryptedOnlineAccessToken } = _c, storedOnlineSession = __rest(_c, ["accessToken"]);
209
241
  let session = storedOnlineSession;
210
242
  let accessToken = encryptedOnlineAccessToken;
211
243
  let isEncrypted = true; // TODO: explore removing (isEncryptyed)
@@ -231,7 +263,7 @@ const getShopifyHelpers = async (serverRuntimeConfig, config) => {
231
263
  const verifyOfflineSession = async () => {
232
264
  var _a;
233
265
  const sessionId = shopify.session.getOfflineId(shop);
234
- const _b = (_a = (await (0, firebase_js_1.loadCallback)(sessionHandler, shopId, sessionId))) !== null && _a !== void 0 ? _a : {}, { accessToken: encryptedOfflineAccessToken } = _b, offlineSession = __rest(_b, ["accessToken"]);
266
+ const _b = (_a = (await storage.loadSession(shopId, sessionId))) !== null && _a !== void 0 ? _a : {}, { accessToken: encryptedOfflineAccessToken } = _b, offlineSession = __rest(_b, ["accessToken"]);
235
267
  return {
236
268
  sessionId,
237
269
  session: offlineSession,
@@ -382,19 +414,19 @@ const getShopifyHelpers = async (serverRuntimeConfig, config) => {
382
414
  loadSession: decryptVerifySession,
383
415
  storeSession: encryptStoreSession,
384
416
  ensureBilling: handleEnsureBilling,
385
- loadState: async () => (0, firebase_js_1.loadState)(sessionHandler, shop),
417
+ loadState: async () => storage.loadState(shop),
386
418
  /** @param {any} state */
387
- storeState: async (state) => (0, firebase_js_1.storeState)(sessionHandler, state),
388
- deleteState: async () => (0, firebase_js_1.deleteState)(sessionHandler, shop),
419
+ storeState: async (state) => storage.storeState(shop, state),
420
+ deleteState: async () => storage.deleteState(shop),
389
421
  /**
390
422
  * @param {any} sessionId
391
423
  * @param {any} data
392
424
  */
393
425
  updateSession: async (sessionId, data) => encryptUpdateSession(Object.assign(Object.assign({}, data), { id: sessionId })),
394
426
  /** @param {any} sessionIds */
395
- deleteSessions: async (sessionIds) => (0, firebase_js_1.deleteSessionsCallback)(sessionHandler, shopId, sessionIds),
427
+ deleteSessions: async (sessionIds) => storage.deleteSessions(shopId, sessionIds),
396
428
  /** @param {any} _shop */
397
- findSessionIds: async (_shop) => { var _a, _b; return (0, firebase_js_1.findSessionIdsByShop)(sessionHandler, (_b = (_a = shopify === null || shopify === void 0 ? void 0 : shopify.utils) === null || _a === void 0 ? void 0 : _a.sanitizeShop) === null || _b === void 0 ? void 0 : _b.call(_a, _shop)); },
429
+ findSessionIds: async (_shop) => { var _a, _b; return storage.findSessionIds((_b = (_a = shopify === null || shopify === void 0 ? void 0 : shopify.utils) === null || _a === void 0 ? void 0 : _a.sanitizeShop) === null || _b === void 0 ? void 0 : _b.call(_a, _shop)); },
398
430
  };
399
431
  };
400
432
  exports.getShopifyHelpers = getShopifyHelpers;
@@ -0,0 +1,4 @@
1
+ export function createFirestoreStorage(firebaseConfig: any, { collection, configurationId }: {
2
+ collection?: string;
3
+ configurationId: string;
4
+ }): import("../index.js").SessionStorage;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createFirestoreStorage = void 0;
4
+ const app_1 = require("firebase-admin/app");
5
+ const firestore_1 = require("firebase-admin/firestore");
6
+ const index_js_1 = require("../../shared/index.js");
7
+ // firebase-admin allows one app per process, so an existing one is reused rather than replaced.
8
+ // NOTE: the retired handler also passed `databaseURL`, which configures the Realtime Database.
9
+ // Nothing here has ever used it - no `getDatabase`, no `firebase-admin/database` import.
10
+ /** @param {any} firebaseConfig */
11
+ const initializeFirebaseApp = (firebaseConfig) => ((0, app_1.getApps)().length
12
+ ? (0, app_1.getApp)()
13
+ : (0, app_1.initializeApp)({ credential: (0, app_1.cert)(firebaseConfig) }));
14
+ /**
15
+ * The Firestore implementation of the session-storage contract, kept here so the core never
16
+ * imports firebase-admin. Sessions and state share one collection per shop; state is a single
17
+ * document per shop, prefixed so it cannot collide with a session id.
18
+ *
19
+ * @param {any} firebaseConfig a firebase service-account config
20
+ * @param {{ collection?: string, configurationId: string }} options
21
+ * @returns {import('../index.js').SessionStorage}
22
+ */
23
+ const createFirestoreStorage = (firebaseConfig, { collection = index_js_1.COLLECTION_ROOT, configurationId }) => {
24
+ if (!(0, index_js_1.objectNotEmpty)(firebaseConfig)) {
25
+ throw new Error('createFirestoreStorage requires a firebase service-account config');
26
+ }
27
+ const firestore = (0, firestore_1.getFirestore)(initializeFirebaseApp(firebaseConfig));
28
+ /** @param {any} shop */
29
+ const shopCollection = (shop) => (firestore
30
+ .collection(collection)
31
+ .doc(shop)
32
+ .collection(configurationId));
33
+ /** @param {any} shop */
34
+ const stateRef = (shop) => shopCollection(shop).doc(`${index_js_1.STATE_PREFIX}${shop}`);
35
+ /** @param {any} docRef */
36
+ const readDocument = async (docRef) => {
37
+ const document = await docRef.get();
38
+ return document.exists ? document.data() : undefined;
39
+ };
40
+ /** @type {import('../index.js').SessionStorage} */
41
+ const storage = {
42
+ loadSession: async (shopId, sessionId) => ((0, index_js_1.stringEmpty)(sessionId)
43
+ ? undefined
44
+ : readDocument(shopCollection(shopId).doc(`${sessionId}`))),
45
+ // NOTE: merge rather than replace. The core writes partial sessions - a billing status
46
+ // update carries no access token - and a replacing write would drop the rest.
47
+ updateSession: async (shopId, session) => {
48
+ const docRef = shopCollection(shopId).doc(session.id);
49
+ await docRef.set(session, { merge: true });
50
+ return readDocument(docRef);
51
+ },
52
+ deleteSessions: async (shopId, sessionIds) => {
53
+ const collectionRef = shopCollection(shopId);
54
+ return Promise.all((0, index_js_1.ensureArray)(sessionIds).map((sessionId) => collectionRef.doc(sessionId).delete()));
55
+ },
56
+ findSessionIds: async (shopId) => {
57
+ // NOTE: empty rather than an error - this is the state on an initial install
58
+ if ((0, index_js_1.stringEmpty)(shopId)) {
59
+ return [];
60
+ }
61
+ const documents = await shopCollection(shopId).get();
62
+ return documents.docs.map((/** @type {any} */ { id }) => id);
63
+ },
64
+ loadState: async (shop) => ((0, index_js_1.stringEmpty)(shop)
65
+ ? undefined
66
+ : readDocument(stateRef(shop))),
67
+ storeState: async (shop, state) => {
68
+ if ((0, index_js_1.stringEmpty)(shop)) {
69
+ return undefined;
70
+ }
71
+ const docRef = stateRef(shop);
72
+ await docRef.set(state);
73
+ return readDocument(docRef);
74
+ },
75
+ deleteState: async (shop) => ((0, index_js_1.stringEmpty)(shop)
76
+ ? undefined
77
+ : stateRef(shop).delete()),
78
+ };
79
+ return storage;
80
+ };
81
+ exports.createFirestoreStorage = createFirestoreStorage;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Shop and session ids are nullable: `sanitizeShop` returns null for anything it does not
3
+ * recognise, and the core forwards that rather than guarding.
4
+ *
5
+ * @typedef {string | null | undefined} ShopId
6
+ */
7
+ /**
8
+ * What the session layer needs from a store. Two entities, sessions and state, both scoped to a
9
+ * shop. Nothing here is Firestore-specific: `@agnostack/next-shopify/firestore` is one
10
+ * implementation, and an app can supply its own.
11
+ *
12
+ * ⚠️ Values arrive ALREADY ENCRYPTED and already stripped of anything that must not be persisted.
13
+ * An implementation stores and returns what it is handed - it must not inspect, reshape or
14
+ * re-encrypt, or the core's crypto and DPA guarantees stop holding.
15
+ *
16
+ * ⚠️ `findSessionIds` returns `[]` rather than erroring for an unknown shop - that is what an
17
+ * initial install looks like.
18
+ *
19
+ * @typedef {object} SessionStorage
20
+ * @property {(shopId: ShopId, sessionId: ShopId) => Promise<any>} loadSession
21
+ * @property {(shopId: ShopId, session: any) => Promise<any>} updateSession
22
+ * MERGES into the stored session. The core writes partial sessions, so a replacing write
23
+ * would silently drop fields it did not send.
24
+ * @property {(shopId: ShopId, sessionIds: any) => Promise<any>} deleteSessions
25
+ * @property {(shopId: ShopId) => Promise<string[]>} findSessionIds
26
+ * @property {(shop: ShopId) => Promise<any>} loadState
27
+ * @property {(shop: ShopId, state: any) => Promise<any>} storeState
28
+ * @property {(shop: ShopId) => Promise<any>} deleteState
29
+ */
30
+ export const STORAGE_METHODS: string[];
31
+ export function ensureStorage(storage: any): SessionStorage;
32
+ /**
33
+ * Shop and session ids are nullable: `sanitizeShop` returns null for anything it does not
34
+ * recognise, and the core forwards that rather than guarding.
35
+ */
36
+ export type ShopId = string | null | undefined;
37
+ /**
38
+ * What the session layer needs from a store. Two entities, sessions and state, both scoped to a
39
+ * shop. Nothing here is Firestore-specific: `@agnostack/next-shopify/firestore` is one
40
+ * implementation, and an app can supply its own.
41
+ *
42
+ * ⚠️ Values arrive ALREADY ENCRYPTED and already stripped of anything that must not be persisted.
43
+ * An implementation stores and returns what it is handed - it must not inspect, reshape or
44
+ * re-encrypt, or the core's crypto and DPA guarantees stop holding.
45
+ *
46
+ * ⚠️ `findSessionIds` returns `[]` rather than erroring for an unknown shop - that is what an
47
+ * initial install looks like.
48
+ */
49
+ export type SessionStorage = {
50
+ loadSession: (shopId: ShopId, sessionId: ShopId) => Promise<any>;
51
+ /**
52
+ * MERGES into the stored session. The core writes partial sessions, so a replacing write
53
+ * would silently drop fields it did not send.
54
+ */
55
+ updateSession: (shopId: ShopId, session: any) => Promise<any>;
56
+ deleteSessions: (shopId: ShopId, sessionIds: any) => Promise<any>;
57
+ findSessionIds: (shopId: ShopId) => Promise<string[]>;
58
+ loadState: (shop: ShopId) => Promise<any>;
59
+ storeState: (shop: ShopId, state: any) => Promise<any>;
60
+ deleteState: (shop: ShopId) => Promise<any>;
61
+ };
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureStorage = exports.STORAGE_METHODS = void 0;
4
+ const index_js_1 = require("../shared/index.js");
5
+ /**
6
+ * Shop and session ids are nullable: `sanitizeShop` returns null for anything it does not
7
+ * recognise, and the core forwards that rather than guarding.
8
+ *
9
+ * @typedef {string | null | undefined} ShopId
10
+ */
11
+ /**
12
+ * What the session layer needs from a store. Two entities, sessions and state, both scoped to a
13
+ * shop. Nothing here is Firestore-specific: `@agnostack/next-shopify/firestore` is one
14
+ * implementation, and an app can supply its own.
15
+ *
16
+ * ⚠️ Values arrive ALREADY ENCRYPTED and already stripped of anything that must not be persisted.
17
+ * An implementation stores and returns what it is handed - it must not inspect, reshape or
18
+ * re-encrypt, or the core's crypto and DPA guarantees stop holding.
19
+ *
20
+ * ⚠️ `findSessionIds` returns `[]` rather than erroring for an unknown shop - that is what an
21
+ * initial install looks like.
22
+ *
23
+ * @typedef {object} SessionStorage
24
+ * @property {(shopId: ShopId, sessionId: ShopId) => Promise<any>} loadSession
25
+ * @property {(shopId: ShopId, session: any) => Promise<any>} updateSession
26
+ * MERGES into the stored session. The core writes partial sessions, so a replacing write
27
+ * would silently drop fields it did not send.
28
+ * @property {(shopId: ShopId, sessionIds: any) => Promise<any>} deleteSessions
29
+ * @property {(shopId: ShopId) => Promise<string[]>} findSessionIds
30
+ * @property {(shop: ShopId) => Promise<any>} loadState
31
+ * @property {(shop: ShopId, state: any) => Promise<any>} storeState
32
+ * @property {(shop: ShopId) => Promise<any>} deleteState
33
+ */
34
+ exports.STORAGE_METHODS = [
35
+ 'loadState',
36
+ 'storeState',
37
+ 'deleteState',
38
+ 'loadSession',
39
+ 'updateSession',
40
+ 'deleteSessions',
41
+ 'findSessionIds'
42
+ ];
43
+ /**
44
+ * Fails at composition rather than at the first OAuth round-trip. Nothing automated covers the
45
+ * Shopify auth flow, so a missing method would otherwise surface as a runtime TypeError in a
46
+ * callback that needs a real dev store to reach.
47
+ *
48
+ * @param {any} storage
49
+ * @returns {SessionStorage}
50
+ */
51
+ const ensureStorage = (storage) => {
52
+ const missing = (0, index_js_1.ensureArray)(exports.STORAGE_METHODS).filter((method) => (typeof (storage === null || storage === void 0 ? void 0 : storage[method]) !== 'function'));
53
+ if (missing.length) {
54
+ throw new Error(`Invalid session storage, missing: ${missing.join(', ')}`);
55
+ }
56
+ return storage;
57
+ };
58
+ exports.ensureStorage = ensureStorage;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agnostack/next-shopify",
3
- "version": "4.0.0-canary.7",
3
+ "version": "4.0.0-canary.8",
4
4
  "author": "agnoStack Dev <developers@agnostack.com> (https://agnostack.com)",
5
5
  "owner": "agnoStack",
6
6
  "description": "Please contact agnoStack via info@agnostack.com for any questions",
@@ -39,6 +39,28 @@
39
39
  "import": "./dist/esm/lib/index.js",
40
40
  "default": "./dist/esm/lib/index.js"
41
41
  },
42
+ "./storage": {
43
+ "types": "./dist/storage/index.d.ts",
44
+ "require": "./dist/storage/index.js",
45
+ "import": "./dist/storage/index.js",
46
+ "default": "./dist/storage/index.js"
47
+ },
48
+ "./storage/esm": {
49
+ "types": "./dist/storage/index.d.ts",
50
+ "import": "./dist/esm/storage/index.js",
51
+ "default": "./dist/esm/storage/index.js"
52
+ },
53
+ "./storage/firestore": {
54
+ "types": "./dist/storage/firestore/index.d.ts",
55
+ "require": "./dist/storage/firestore/index.js",
56
+ "import": "./dist/storage/firestore/index.js",
57
+ "default": "./dist/storage/firestore/index.js"
58
+ },
59
+ "./storage/firestore/esm": {
60
+ "types": "./dist/storage/firestore/index.d.ts",
61
+ "import": "./dist/esm/storage/firestore/index.js",
62
+ "default": "./dist/esm/storage/firestore/index.js"
63
+ },
42
64
  "./runtime": {
43
65
  "types": "./dist/runtime/index.d.ts",
44
66
  "require": "./dist/runtime/index.js",
@@ -153,6 +175,9 @@
153
175
  "prompts": ">=2.4.0"
154
176
  },
155
177
  "peerDependenciesMeta": {
178
+ "firebase-admin": {
179
+ "optional": true
180
+ },
156
181
  "next": {
157
182
  "optional": true
158
183
  },
@@ -1,265 +0,0 @@
1
- var __rest = (this && this.__rest) || function (s, e) {
2
- var t = {};
3
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
- t[p] = s[p];
5
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
- t[p[i]] = s[p[i]];
9
- }
10
- return t;
11
- };
12
- import { cert, getApp, getApps, initializeApp } from 'firebase-admin/app';
13
- import { getFirestore } from 'firebase-admin/firestore';
14
- import { ensureArray, stringEmpty, objectNotEmpty, COLLECTION_ROOT, STATE_PREFIX, } from '../../shared/index.js';
15
- const singleton = Symbol('singleton');
16
- const singletonEnforcer = Symbol('singletonEnforcer');
17
- export class FirebaseSessionHandler {
18
- /**
19
- * @param {any} enforcer
20
- * @param {any} _param1
21
- */
22
- constructor(enforcer, { configurationId, collection = COLLECTION_ROOT }) {
23
- if (enforcer !== singletonEnforcer) {
24
- throw new Error('Cannot instantiate FirebaseSessionHandler directly, please use getInstance()');
25
- }
26
- this.type = 'FirebaseSessionHandler';
27
- this.collection = collection;
28
- this.configurationId = configurationId;
29
- }
30
- get isInitialized() {
31
- return (this.firestore != undefined);
32
- }
33
- /** @param {any} [firebaseConfig] */
34
- initializeFireStore(firebaseConfig) {
35
- if (objectNotEmpty(firebaseConfig)) {
36
- const project = getApps().length
37
- ? getApp()
38
- : initializeApp({
39
- credential: cert(firebaseConfig),
40
- databaseURL: `https://${firebaseConfig.project_id}.firebaseio.com`,
41
- });
42
- this.firestore = getFirestore(project);
43
- }
44
- }
45
- /**
46
- * @param {any} firebaseConfig
47
- * @param {any} _param1
48
- * @this {any}
49
- */
50
- static getInstance(firebaseConfig, { collection, configurationId }) {
51
- if (!this[singleton]) {
52
- this[singleton] = new FirebaseSessionHandler(singletonEnforcer, { collection, configurationId });
53
- }
54
- if (!this[singleton].isInitialized) {
55
- this[singleton].initializeFireStore(firebaseConfig);
56
- }
57
- return this[singleton];
58
- }
59
- }
60
- /**
61
- * @param {any} [instance]
62
- * @param {any} [shopId]
63
- * @param {any} [session]
64
- */
65
- export const storeCallback = async (instance, shopId, session) => {
66
- try {
67
- const docRef = instance.firestore
68
- .collection(instance.collection)
69
- .doc(shopId)
70
- .collection(instance.configurationId)
71
- .doc(session.id);
72
- // HMMMM why the freeze here?
73
- // TODO: do we need to remove/can we remove session.id here??
74
- const _a = JSON.parse(JSON.stringify(session)), { onlineAccessInfo: _onlineAccessInfo } = _a, docData = __rest(_a, ["onlineAccessInfo"]);
75
- // NOTE: destructuring out associated_user for DPA compliance
76
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
77
- const _b = _onlineAccessInfo !== null && _onlineAccessInfo !== void 0 ? _onlineAccessInfo : {}, { associated_user } = _b, onlineAccessInfo = __rest(_b, ["associated_user"]);
78
- const payload = Object.assign({ onlineAccessInfo }, docData);
79
- await docRef.set(payload);
80
- return (await docRef.get()).data();
81
- }
82
- catch (error) {
83
- console.error('FirebaseSessionHandler.storeCallback error', error);
84
- throw error;
85
- }
86
- };
87
- /**
88
- * @param {any} [instance]
89
- * @param {any} [shopId]
90
- * @param {any} [session]
91
- */
92
- export const updateCallback = async (instance, shopId, session) => {
93
- try {
94
- const docRef = instance.firestore
95
- .collection(instance.collection)
96
- .doc(shopId)
97
- .collection(instance.configurationId)
98
- .doc(session.id);
99
- // HMMMM why the freeze here?
100
- // TODO: do we need to remove/can we remove session.id here??
101
- const _a = JSON.parse(JSON.stringify(session)), { onlineAccessInfo: _onlineAccessInfo } = _a, docData = __rest(_a, ["onlineAccessInfo"]);
102
- // NOTE: destructuring out associated_user for DPA compliance
103
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
104
- const _b = _onlineAccessInfo !== null && _onlineAccessInfo !== void 0 ? _onlineAccessInfo : {}, { associated_user } = _b, onlineAccessInfo = __rest(_b, ["associated_user"]);
105
- const payload = Object.assign({ onlineAccessInfo }, docData);
106
- await docRef.set(payload, { merge: true });
107
- return (await docRef.get()).data();
108
- }
109
- catch (error) {
110
- console.error('FirebaseSessionHandler.updateCallback error', error);
111
- throw error;
112
- }
113
- };
114
- /**
115
- * @param {any} [instance]
116
- * @param {any} [shopId]
117
- * @param {any} [sessionId]
118
- */
119
- export const loadCallback = async (instance, shopId, sessionId) => {
120
- if (stringEmpty(sessionId)) {
121
- return undefined;
122
- }
123
- try {
124
- const document = await instance.firestore
125
- .collection(instance.collection)
126
- .doc(shopId)
127
- .collection(instance.configurationId)
128
- .doc(sessionId)
129
- .get();
130
- if (document.exists) {
131
- return document.data();
132
- }
133
- return undefined;
134
- }
135
- catch (error) {
136
- console.error('FirebaseSessionHandler.loadCallback error', error);
137
- throw error;
138
- }
139
- };
140
- /**
141
- * @param {any} [instance]
142
- * @param {any} [shop]
143
- */
144
- export const loadState = async (instance, shop) => {
145
- if (stringEmpty(shop)) {
146
- return undefined;
147
- }
148
- try {
149
- const document = await instance.firestore
150
- .collection(instance.collection)
151
- .doc(shop)
152
- .collection(instance.configurationId)
153
- .doc(`${STATE_PREFIX}${shop}`)
154
- .get();
155
- if (document.exists) {
156
- return document.data();
157
- }
158
- return undefined;
159
- }
160
- catch (error) {
161
- console.error('FirebaseSessionHandler.loadState error', error);
162
- throw error;
163
- }
164
- };
165
- /**
166
- * @param {any} [instance]
167
- * @param {any} [state]
168
- */
169
- export const storeState = async (instance, state) => {
170
- if (stringEmpty(state === null || state === void 0 ? void 0 : state.shop)) {
171
- return undefined;
172
- }
173
- try {
174
- const docRef = instance.firestore
175
- .collection(instance.collection)
176
- .doc(state.shop)
177
- .collection(instance.configurationId)
178
- .doc(`${STATE_PREFIX}${state.shop}`);
179
- await docRef.set(state);
180
- return (await docRef.get()).data();
181
- }
182
- catch (error) {
183
- console.error('FirebaseSessionHandler.storeState error', error);
184
- throw error;
185
- }
186
- };
187
- /**
188
- * @param {any} [instance]
189
- * @param {any} [shop]
190
- */
191
- export const deleteState = async (instance, shop) => {
192
- if (stringEmpty(shop)) {
193
- return undefined;
194
- }
195
- try {
196
- const docRef = instance.firestore
197
- .collection(instance.collection)
198
- .doc(shop)
199
- .collection(instance.configurationId)
200
- .doc(`${STATE_PREFIX}${shop}`);
201
- return docRef.delete();
202
- }
203
- catch (error) {
204
- console.error('FirebaseSessionHandler.deleteState error', error);
205
- throw error;
206
- }
207
- };
208
- /**
209
- * @param {any} [instance]
210
- * @param {any} [shopId]
211
- * @param {any} [sessionIds]
212
- */
213
- export const deleteSessionsCallback = async (instance, shopId, sessionIds) => {
214
- /**
215
- * @param {any} [_collectionRef]
216
- * @param {any} [_sessionId]
217
- */
218
- const deleteSessionCallback = async (_collectionRef, _sessionId) => {
219
- try {
220
- return _collectionRef.doc(_sessionId).delete();
221
- }
222
- catch (error) {
223
- console.error('FirebaseSessionHandler.deleteSessionCallback error', error);
224
- throw error;
225
- }
226
- };
227
- try {
228
- const collectionRef = instance.firestore
229
- .collection(instance.collection)
230
- .doc(shopId)
231
- .collection(instance.configurationId);
232
- return Promise.all(ensureArray(sessionIds).map((sessionId) => deleteSessionCallback(collectionRef, sessionId)));
233
- }
234
- catch (error) {
235
- console.error('FirebaseSessionHandler.deleteSessionsCallback error', error);
236
- throw error;
237
- }
238
- };
239
- /**
240
- * @param {any} [instance]
241
- * @param {any} [shopId]
242
- */
243
- export const findSessionIdsByShop = async (instance, shopId) => {
244
- if (stringEmpty(shopId)) {
245
- // NOTE: this happens on initial install
246
- return [];
247
- }
248
- try {
249
- const collection = await instance.firestore
250
- .collection(instance.collection)
251
- .doc(shopId)
252
- .collection(instance.configurationId)
253
- .get();
254
- /** @type {string[]} */
255
- const sessionIds = [];
256
- collection.forEach((/** @type {any} */ _doc) => {
257
- sessionIds.push(_doc.id);
258
- });
259
- return sessionIds;
260
- }
261
- catch (error) {
262
- console.error('FirebaseSessionHandler.findSessionIdsByShop error', error);
263
- throw error;
264
- }
265
- };
@@ -1,31 +0,0 @@
1
- export class FirebaseSessionHandler {
2
- /**
3
- * @param {any} firebaseConfig
4
- * @param {any} _param1
5
- * @this {any}
6
- */
7
- static getInstance(this: any, firebaseConfig: any, { collection, configurationId }: any): any;
8
- /**
9
- * @param {any} enforcer
10
- * @param {any} _param1
11
- */
12
- constructor(enforcer: any, { configurationId, collection }: any);
13
- type: string;
14
- collection: any;
15
- configurationId: any;
16
- get isInitialized(): boolean;
17
- /** @param {any} [firebaseConfig] */
18
- initializeFireStore(firebaseConfig?: any): void;
19
- firestore: FirebaseFirestore.Firestore | undefined;
20
- [singleton]: FirebaseSessionHandler | undefined;
21
- }
22
- export function storeCallback(instance?: any, shopId?: any, session?: any): Promise<any>;
23
- export function updateCallback(instance?: any, shopId?: any, session?: any): Promise<any>;
24
- export function loadCallback(instance?: any, shopId?: any, sessionId?: any): Promise<any>;
25
- export function loadState(instance?: any, shop?: any): Promise<any>;
26
- export function storeState(instance?: any, state?: any): Promise<any>;
27
- export function deleteState(instance?: any, shop?: any): Promise<any>;
28
- export function deleteSessionsCallback(instance?: any, shopId?: any, sessionIds?: any): Promise<any[]>;
29
- export function findSessionIdsByShop(instance?: any, shopId?: any): Promise<string[]>;
30
- declare const singleton: unique symbol;
31
- export {};
@@ -1,277 +0,0 @@
1
- "use strict";
2
- var __rest = (this && this.__rest) || function (s, e) {
3
- var t = {};
4
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
- t[p] = s[p];
6
- if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
- for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
- if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
- t[p[i]] = s[p[i]];
10
- }
11
- return t;
12
- };
13
- Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.findSessionIdsByShop = exports.deleteSessionsCallback = exports.deleteState = exports.storeState = exports.loadState = exports.loadCallback = exports.updateCallback = exports.storeCallback = exports.FirebaseSessionHandler = void 0;
15
- const app_1 = require("firebase-admin/app");
16
- const firestore_1 = require("firebase-admin/firestore");
17
- const index_js_1 = require("../../shared/index.js");
18
- const singleton = Symbol('singleton');
19
- const singletonEnforcer = Symbol('singletonEnforcer');
20
- class FirebaseSessionHandler {
21
- /**
22
- * @param {any} enforcer
23
- * @param {any} _param1
24
- */
25
- constructor(enforcer, { configurationId, collection = index_js_1.COLLECTION_ROOT }) {
26
- if (enforcer !== singletonEnforcer) {
27
- throw new Error('Cannot instantiate FirebaseSessionHandler directly, please use getInstance()');
28
- }
29
- this.type = 'FirebaseSessionHandler';
30
- this.collection = collection;
31
- this.configurationId = configurationId;
32
- }
33
- get isInitialized() {
34
- return (this.firestore != undefined);
35
- }
36
- /** @param {any} [firebaseConfig] */
37
- initializeFireStore(firebaseConfig) {
38
- if ((0, index_js_1.objectNotEmpty)(firebaseConfig)) {
39
- const project = (0, app_1.getApps)().length
40
- ? (0, app_1.getApp)()
41
- : (0, app_1.initializeApp)({
42
- credential: (0, app_1.cert)(firebaseConfig),
43
- databaseURL: `https://${firebaseConfig.project_id}.firebaseio.com`,
44
- });
45
- this.firestore = (0, firestore_1.getFirestore)(project);
46
- }
47
- }
48
- /**
49
- * @param {any} firebaseConfig
50
- * @param {any} _param1
51
- * @this {any}
52
- */
53
- static getInstance(firebaseConfig, { collection, configurationId }) {
54
- if (!this[singleton]) {
55
- this[singleton] = new FirebaseSessionHandler(singletonEnforcer, { collection, configurationId });
56
- }
57
- if (!this[singleton].isInitialized) {
58
- this[singleton].initializeFireStore(firebaseConfig);
59
- }
60
- return this[singleton];
61
- }
62
- }
63
- exports.FirebaseSessionHandler = FirebaseSessionHandler;
64
- /**
65
- * @param {any} [instance]
66
- * @param {any} [shopId]
67
- * @param {any} [session]
68
- */
69
- const storeCallback = async (instance, shopId, session) => {
70
- try {
71
- const docRef = instance.firestore
72
- .collection(instance.collection)
73
- .doc(shopId)
74
- .collection(instance.configurationId)
75
- .doc(session.id);
76
- // HMMMM why the freeze here?
77
- // TODO: do we need to remove/can we remove session.id here??
78
- const _a = JSON.parse(JSON.stringify(session)), { onlineAccessInfo: _onlineAccessInfo } = _a, docData = __rest(_a, ["onlineAccessInfo"]);
79
- // NOTE: destructuring out associated_user for DPA compliance
80
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
81
- const _b = _onlineAccessInfo !== null && _onlineAccessInfo !== void 0 ? _onlineAccessInfo : {}, { associated_user } = _b, onlineAccessInfo = __rest(_b, ["associated_user"]);
82
- const payload = Object.assign({ onlineAccessInfo }, docData);
83
- await docRef.set(payload);
84
- return (await docRef.get()).data();
85
- }
86
- catch (error) {
87
- console.error('FirebaseSessionHandler.storeCallback error', error);
88
- throw error;
89
- }
90
- };
91
- exports.storeCallback = storeCallback;
92
- /**
93
- * @param {any} [instance]
94
- * @param {any} [shopId]
95
- * @param {any} [session]
96
- */
97
- const updateCallback = async (instance, shopId, session) => {
98
- try {
99
- const docRef = instance.firestore
100
- .collection(instance.collection)
101
- .doc(shopId)
102
- .collection(instance.configurationId)
103
- .doc(session.id);
104
- // HMMMM why the freeze here?
105
- // TODO: do we need to remove/can we remove session.id here??
106
- const _a = JSON.parse(JSON.stringify(session)), { onlineAccessInfo: _onlineAccessInfo } = _a, docData = __rest(_a, ["onlineAccessInfo"]);
107
- // NOTE: destructuring out associated_user for DPA compliance
108
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
109
- const _b = _onlineAccessInfo !== null && _onlineAccessInfo !== void 0 ? _onlineAccessInfo : {}, { associated_user } = _b, onlineAccessInfo = __rest(_b, ["associated_user"]);
110
- const payload = Object.assign({ onlineAccessInfo }, docData);
111
- await docRef.set(payload, { merge: true });
112
- return (await docRef.get()).data();
113
- }
114
- catch (error) {
115
- console.error('FirebaseSessionHandler.updateCallback error', error);
116
- throw error;
117
- }
118
- };
119
- exports.updateCallback = updateCallback;
120
- /**
121
- * @param {any} [instance]
122
- * @param {any} [shopId]
123
- * @param {any} [sessionId]
124
- */
125
- const loadCallback = async (instance, shopId, sessionId) => {
126
- if ((0, index_js_1.stringEmpty)(sessionId)) {
127
- return undefined;
128
- }
129
- try {
130
- const document = await instance.firestore
131
- .collection(instance.collection)
132
- .doc(shopId)
133
- .collection(instance.configurationId)
134
- .doc(sessionId)
135
- .get();
136
- if (document.exists) {
137
- return document.data();
138
- }
139
- return undefined;
140
- }
141
- catch (error) {
142
- console.error('FirebaseSessionHandler.loadCallback error', error);
143
- throw error;
144
- }
145
- };
146
- exports.loadCallback = loadCallback;
147
- /**
148
- * @param {any} [instance]
149
- * @param {any} [shop]
150
- */
151
- const loadState = async (instance, shop) => {
152
- if ((0, index_js_1.stringEmpty)(shop)) {
153
- return undefined;
154
- }
155
- try {
156
- const document = await instance.firestore
157
- .collection(instance.collection)
158
- .doc(shop)
159
- .collection(instance.configurationId)
160
- .doc(`${index_js_1.STATE_PREFIX}${shop}`)
161
- .get();
162
- if (document.exists) {
163
- return document.data();
164
- }
165
- return undefined;
166
- }
167
- catch (error) {
168
- console.error('FirebaseSessionHandler.loadState error', error);
169
- throw error;
170
- }
171
- };
172
- exports.loadState = loadState;
173
- /**
174
- * @param {any} [instance]
175
- * @param {any} [state]
176
- */
177
- const storeState = async (instance, state) => {
178
- if ((0, index_js_1.stringEmpty)(state === null || state === void 0 ? void 0 : state.shop)) {
179
- return undefined;
180
- }
181
- try {
182
- const docRef = instance.firestore
183
- .collection(instance.collection)
184
- .doc(state.shop)
185
- .collection(instance.configurationId)
186
- .doc(`${index_js_1.STATE_PREFIX}${state.shop}`);
187
- await docRef.set(state);
188
- return (await docRef.get()).data();
189
- }
190
- catch (error) {
191
- console.error('FirebaseSessionHandler.storeState error', error);
192
- throw error;
193
- }
194
- };
195
- exports.storeState = storeState;
196
- /**
197
- * @param {any} [instance]
198
- * @param {any} [shop]
199
- */
200
- const deleteState = async (instance, shop) => {
201
- if ((0, index_js_1.stringEmpty)(shop)) {
202
- return undefined;
203
- }
204
- try {
205
- const docRef = instance.firestore
206
- .collection(instance.collection)
207
- .doc(shop)
208
- .collection(instance.configurationId)
209
- .doc(`${index_js_1.STATE_PREFIX}${shop}`);
210
- return docRef.delete();
211
- }
212
- catch (error) {
213
- console.error('FirebaseSessionHandler.deleteState error', error);
214
- throw error;
215
- }
216
- };
217
- exports.deleteState = deleteState;
218
- /**
219
- * @param {any} [instance]
220
- * @param {any} [shopId]
221
- * @param {any} [sessionIds]
222
- */
223
- const deleteSessionsCallback = async (instance, shopId, sessionIds) => {
224
- /**
225
- * @param {any} [_collectionRef]
226
- * @param {any} [_sessionId]
227
- */
228
- const deleteSessionCallback = async (_collectionRef, _sessionId) => {
229
- try {
230
- return _collectionRef.doc(_sessionId).delete();
231
- }
232
- catch (error) {
233
- console.error('FirebaseSessionHandler.deleteSessionCallback error', error);
234
- throw error;
235
- }
236
- };
237
- try {
238
- const collectionRef = instance.firestore
239
- .collection(instance.collection)
240
- .doc(shopId)
241
- .collection(instance.configurationId);
242
- return Promise.all((0, index_js_1.ensureArray)(sessionIds).map((sessionId) => deleteSessionCallback(collectionRef, sessionId)));
243
- }
244
- catch (error) {
245
- console.error('FirebaseSessionHandler.deleteSessionsCallback error', error);
246
- throw error;
247
- }
248
- };
249
- exports.deleteSessionsCallback = deleteSessionsCallback;
250
- /**
251
- * @param {any} [instance]
252
- * @param {any} [shopId]
253
- */
254
- const findSessionIdsByShop = async (instance, shopId) => {
255
- if ((0, index_js_1.stringEmpty)(shopId)) {
256
- // NOTE: this happens on initial install
257
- return [];
258
- }
259
- try {
260
- const collection = await instance.firestore
261
- .collection(instance.collection)
262
- .doc(shopId)
263
- .collection(instance.configurationId)
264
- .get();
265
- /** @type {string[]} */
266
- const sessionIds = [];
267
- collection.forEach((/** @type {any} */ _doc) => {
268
- sessionIds.push(_doc.id);
269
- });
270
- return sessionIds;
271
- }
272
- catch (error) {
273
- console.error('FirebaseSessionHandler.findSessionIdsByShop error', error);
274
- throw error;
275
- }
276
- };
277
- exports.findSessionIdsByShop = findSessionIdsByShop;