Source

index.js

/**
 * @module scenid-cloud-sdk
 *
 * @example
 * import sdk, { initScenid } from '@scenid/cloud-sdk'
 *
 * initScenid(serviceKey)
 *
 * // Admin access
 * const insights = sdk.asAdmin().Insights()
 *
 * // User access
 * const profile = sdk.asUser(idToken).Profile()
 */

import debug from 'debug'

import Auth, {
  AuthenticationError,
  checkIsUser,
  checkIsOwner,
  checkIsAdmin,
  checkIsService,
  checkHasClaim,
  checkHasClaims,
  checkIsProductUser,
  checkIsProductAdmin,
  oneOf,
  hasResourcePermission,
  checkHasResourcePermission
} from './auth.js'
import AuthService from './services/AuthService.class.js'
import CustomersService from './services/CustomersService.class.js'
import FilesService from './services/FilesService.class.js'
import InsightsService from './services/InsightsService.class.js'
import Profile from './services/Profile.class.js'
import { SERVICES, resolveServiceUrl } from './services.js'

export {
  SERVICES,
  resolveServiceUrl,
  AuthenticationError,
  checkIsUser,
  checkIsOwner,
  checkIsAdmin,
  checkIsService,
  checkHasClaim,
  checkHasClaims,
  checkIsProductUser,
  checkIsProductAdmin,
  oneOf,
  hasResourcePermission,
  checkHasResourcePermission
}

export { fromDisk, fromBase64, fromBlob, fromMultiPart } from './services/FilesService.sources.js'

const dbg = debug('scenid:sdk')

let _auth = null
let _serviceUrls = null

/**
 * Initialises the SDK with your service key and optional settings.
 *
 * Must be called once before any `sdk.*` method. Subsequent calls
 * reinitialise the SDK (useful in tests; avoid in production).
 *
 * The base domain for service URLs is derived from `serviceKey.issuer`
 * unless `serviceKey.cloud_stack` is set explicitly.
 *
 * @param {ServiceKey} serviceKey - Your Scenid service key object.
 * @param {Object} [options]
 * @throws {Error} When any of `client_id`, `client_secret`, or `token_uri` are missing.
 *
 * @example
 * import sdk, { initScenid } from '@scenid/cloud-sdk'
 * import serviceKey from './service-key.json' assert { type: 'json' }
 *
 * // Enable debug output: DEBUG=scenid:* node your-app.js
 * initScenid(serviceKey)
 */
export const initScenid = (serviceKey, options = {}) => {
  if (!serviceKey?.client_id) throw new Error('initScenid: client_id is required')
  if (!serviceKey?.client_secret) throw new Error('initScenid: client_secret is required')
  if (!serviceKey?.token_uri) throw new Error('initScenid: token_uri is required')

  const protocol = serviceKey.ssl === false ? 'http' : 'https'
  const baseDomain = serviceKey.cloud_stack
    ? `${protocol}://${serviceKey.cloud_stack}`
    : new URL(serviceKey.issuer).origin

  _auth = new Auth(serviceKey)
  _serviceUrls = {
    auth: resolveServiceUrl(baseDomain, SERVICES.AUTH),
    customers: resolveServiceUrl(baseDomain, SERVICES.CUSTOMERS),
    files: resolveServiceUrl(baseDomain, SERVICES.FILES),
    insights: resolveServiceUrl(baseDomain, SERVICES.INSIGHTS)
  }

  dbg('initScenid client_id=%s issuer=%s auth=%s customers=%s insights=%s files=%s',
    serviceKey.client_id,
    serviceKey.issuer,
    _serviceUrls.auth,
    _serviceUrls.customers,
    _serviceUrls.insights,
    _serviceUrls.files
  )
}

const assertInit = () => {
  if (!_auth) throw new Error('sdk: call initScenid() before using the SDK')
}

/**
 * @typedef {Object} AdminContext
 * @property {function(): AuthService} Auth
 * @property {function(): CustomersService} Customers
 * @property {function(): FilesService} Files
 * @property {function(): InsightsService} Insights
 */

/**
 * @typedef {Object} UserContext
 * @property {function(): CustomersService} Customers
 * @property {function(): FilesService} Files
 * @property {function(): InsightsService} Insights
 * @property {function(): Profile} Profile
 */

/**
 * The main SDK object. Use `sdk.Auth()`, `sdk.asAdmin()`, or `sdk.asUser()` to
 * access SDK capabilities. All methods throw if `initScenid` has not been called.
 *
 * @namespace sdk
 */
const sdk = {
  /**
   * Returns the `Auth` instance created during `initScenid`.
   *
   * Use this to run OAuth flows (`beginFlow`, `completeFlow`), verify incoming
   * tokens, and manage the exchange-token cache.
   *
   * @memberof sdk
   * @returns {Auth} The shared `Auth` instance.
   * @throws {Error} `sdk: call initScenid() before using the SDK` — if called before `initScenid`.
   *
   * @example
   * const url = sdk.Auth().beginFlow({ redirectUri: 'https://myapp.com/callback' })
   * res.redirect(url)
   */
  Auth: () => {
    assertInit()
    return _auth
  },

  /**
   * Returns a factory that creates service clients authenticated with a
   * machine-to-machine service token (client credentials grant).
   *
   * Use this in backend processes, cron jobs, and server-side middleware that
   * act on behalf of the platform rather than a specific end user.
   *
   * @memberof sdk
   * @returns {AdminContext}
   * @throws {Error} `sdk: call initScenid() before using the SDK`
   *
   * @example
   * const { result } = await sdk.asAdmin().Insights().Source('my-source').get()
   * const { result: ou } = await sdk.asAdmin().Customers().OU.create({ name: 'Eng' }, { return: true })
   */
  asAdmin: () => {
    assertInit()

    const getToken = () => _auth.getServiceToken()

    return {
      Auth: () => new AuthService(getToken, _serviceUrls.auth),
      Customers: () => new CustomersService(getToken, _serviceUrls.customers),
      Files: () => new FilesService(getToken, _serviceUrls.files),
      Insights: () => new InsightsService(getToken, _serviceUrls.insights)
    }
  },

  /**
   * Returns a factory that creates service clients authenticated on behalf of
   * a specific end user via a token exchange.
   *
   * Pass the OIDC ID token obtained from `sdk.Auth().completeFlow()`. The token
   * is exchanged for a service-scoped access token and cached until near expiry.
   *
   * `Profile` is only available on the user context (not admin).
   * `Auth` (admin token management) is not available here — use `sdk.asAdmin().Auth()`.
   *
   * @memberof sdk
   * @param {string} idToken - The user's OIDC ID token from `completeFlow`.
   * @returns {UserContext}
   * @throws {Error} `sdk/as-user-missing-token` — if `idToken` is falsy.
   * @throws {Error} `sdk: call initScenid() before using the SDK`
   *
   * @example
   * // In an authenticated request handler
   * const idToken = req.session.idToken
   * const profile = sdk.asUser(idToken).Profile()
   * const { result } = await profile.get()
   */
  asApp: () => {
    assertInit()

    const getToken = () => _auth.getServiceToken()

    return {
      Auth: () => new AuthService(getToken, _serviceUrls.auth),
      Customers: () => new CustomersService(getToken, _serviceUrls.customers),
      Files: () => new FilesService(getToken, _serviceUrls.files),
      Insights: () => new InsightsService(getToken, _serviceUrls.insights)
    }
  },

  asUser: idToken => {
    assertInit()

    if (!idToken) throw new Error('sdk/as-user-missing-token')
    const getToken = () => _auth.exchangeToken(idToken)

    return {
      Customers: () => new CustomersService(getToken, _serviceUrls.customers),
      Files: () => new FilesService(getToken, _serviceUrls.files),
      Insights: () => new InsightsService(getToken, _serviceUrls.insights),
      Profile: () => new Profile(getToken, _serviceUrls.auth)
    }
  }
}

export default sdk