{"version":3,"file":"perform-token-exchange.mjs","sources":["../../../../src/middlewares/perform-token-exchange.ts"],"sourcesContent":["import {\n  HttpResponseError,\n  InvalidJwtError,\n  RequestedTokenType,\n  Session,\n  Shopify,\n} from '@shopify/shopify-api';\nimport {Request, Response, NextFunction} from 'express';\n\nimport {AppConfigInterface} from '../config-types';\nimport {WITHIN_MILLISECONDS_OF_EXPIRY} from '../helpers/ensure-offline-token-is-not-expired';\nimport {respondToInvalidSessionToken} from '../helpers/respond-to-invalid-session-token';\nimport {invalidateAccessToken} from '../helpers/invalidate-access-token';\n\ninterface PerformTokenExchangeParams {\n  req: Request;\n  res: Response;\n  next: NextFunction;\n  api: Shopify;\n  config: AppConfigInterface;\n  sessionToken: string;\n}\n\nasync function exchangeToken(\n  api: Shopify,\n  config: AppConfigInterface,\n  sessionToken: string,\n  shop: string,\n  requestedTokenType: RequestedTokenType,\n): Promise<Session> {\n  const {session} = await api.auth.tokenExchange({\n    sessionToken,\n    shop,\n    requestedTokenType,\n    expiring: config.future?.expiringOfflineAccessTokens,\n  });\n  return session;\n}\n\nasync function callAfterAuthHook(\n  config: AppConfigInterface,\n  session: Session,\n  sessionToken: string,\n): Promise<void> {\n  await config.idempotentPromiseHandler.handlePromise({\n    promiseFunction: async () => {\n      await config.hooks?.afterAuth?.({session});\n    },\n    identifier: sessionToken,\n  });\n}\n\nexport async function performTokenExchange({\n  req,\n  res,\n  next,\n  api,\n  config,\n  sessionToken,\n}: PerformTokenExchangeParams): Promise<void> {\n  const logger = config.logger;\n  // Hoisted so the outer catch can invalidate a stale access token if needed.\n  let sessionToInvalidate: Session | undefined;\n\n  try {\n    const payload = await api.session.decodeSessionToken(sessionToken);\n    const shop = new URL(payload.dest).hostname;\n    const sub = payload.sub;\n\n    const sessionId = config.useOnlineTokens\n      ? api.session.getJwtSessionId(shop, sub)\n      : api.session.getOfflineId(shop);\n\n    let session: Session | undefined;\n    try {\n      session = await config.sessionStorage.loadSession(sessionId);\n      sessionToInvalidate = session;\n    } catch (error) {\n      logger.error(`Error when loading session from storage: ${error}`);\n      res.status(500).send('Internal Server Error');\n      return;\n    }\n\n    if (session && session.isActive(undefined, WITHIN_MILLISECONDS_OF_EXPIRY)) {\n      logger.debug('Request is valid, session is active', {shop: session.shop});\n      res.locals.shopify = {...res.locals.shopify, session};\n      next();\n      return;\n    }\n\n    logger.info('No valid session found', {shop});\n    logger.info('Requesting offline access token', {shop});\n\n    const offlineSession = await exchangeToken(\n      api,\n      config,\n      sessionToken,\n      shop,\n      RequestedTokenType.OfflineAccessToken,\n    );\n    await config.sessionStorage.storeSession(offlineSession);\n\n    let newSession = offlineSession;\n\n    if (config.useOnlineTokens) {\n      logger.info('Requesting online access token', {shop});\n      const onlineSession = await exchangeToken(\n        api,\n        config,\n        sessionToken,\n        shop,\n        RequestedTokenType.OnlineAccessToken,\n      );\n      await config.sessionStorage.storeSession(onlineSession);\n      newSession = onlineSession;\n    }\n\n    logger.debug('Request is valid, loaded session from session token', {\n      shop: newSession.shop,\n      isOnline: newSession.isOnline,\n    });\n\n    try {\n      await callAfterAuthHook(config, newSession, sessionToken);\n    } catch (error) {\n      logger.error(`Error in afterAuth hook: ${error}`);\n      res.status(500).send('Internal Server Error');\n      return;\n    }\n\n    res.locals.shopify = {...res.locals.shopify, session: newSession};\n    next();\n  } catch (error) {\n    if (\n      error instanceof InvalidJwtError ||\n      (error instanceof HttpResponseError &&\n        error.response.code === 400 &&\n        error.response.body?.error === 'invalid_subject_token')\n    ) {\n      respondToInvalidSessionToken({\n        api,\n        req,\n        res,\n        message: error.message,\n        retryRequest: true,\n      });\n      return;\n    }\n\n    if (error instanceof HttpResponseError && error.response.code === 401) {\n      if (sessionToInvalidate?.accessToken) {\n        await invalidateAccessToken(sessionToInvalidate, config);\n      }\n      respondToInvalidSessionToken({\n        api,\n        req,\n        res,\n        message: error.message,\n      });\n      return;\n    }\n\n    logger.error(`Unexpected error during token exchange: ${error}`);\n    res.status(500).send('Internal Server Error');\n  }\n}\n"],"names":[],"mappings":"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"}