/**
* @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
Source