UNPKG

@jahands/notion-client

Version:
408 lines 14 kB
import { appendBlockChildren, createComment, createDatabase, createPage, deleteBlock, getBlock, getDatabase, getPage, getPageProperty, getSelf, getUser, listBlockChildren, listComments, listDatabases, listUsers, oauthToken, queryDatabase, search, updateBlock, updateDatabase, updatePage, } from './api-endpoints'; import { buildRequestError, isHTTPResponseError, isNotionClientError } from './errors'; import { LogLevel, logLevelSeverity, makeConsoleLogger } from './logging'; import { PACKAGE_NAME, PACKAGE_VERSION } from './package'; import { pick } from './utils'; export default class Client { #auth; #logLevel; #logger; #prefixUrl; #notionVersion; #userAgent; static defaultNotionVersion = '2022-06-28'; constructor(options) { this.#auth = options?.auth; this.#logLevel = options?.logLevel ?? LogLevel.WARN; this.#logger = options?.logger ?? makeConsoleLogger(PACKAGE_NAME); this.#prefixUrl = (options?.baseUrl ?? 'https://api.notion.com') + '/v1/'; this.#notionVersion = options?.notionVersion ?? Client.defaultNotionVersion; this.#userAgent = `notionhq-client/${PACKAGE_VERSION}`; } /** * Sends a request. * * @param path * @param method * @param query * @param body * @returns */ async request({ path, method, query, body, auth, }) { this.log(LogLevel.INFO, 'request start', { method, path }); // If the body is empty, don't send the body in the HTTP request const bodyAsJsonString = !body || Object.entries(body).length === 0 ? undefined : JSON.stringify(body); const url = new URL(`${this.#prefixUrl}${path}`); if (query) { for (const [key, value] of Object.entries(query)) { if (value !== undefined) { if (Array.isArray(value)) { value.forEach((val) => url.searchParams.append(key, decodeURIComponent(val))); } else { url.searchParams.append(key, String(value)); } } } } // Allow both client ID / client secret based auth as well as token based auth. let authorizationHeader; if (typeof auth === 'object') { // Client ID and secret based auth is **ONLY** supported when using the // `/oauth/token` endpoint. If this is the case, handle formatting the // authorization header as required by `Basic` auth. const unencodedCredential = `${auth.client_id}:${auth.client_secret}`; const encodedCredential = Buffer.from(unencodedCredential).toString('base64'); authorizationHeader = { authorization: `Basic ${encodedCredential}` }; } else { // Otherwise format authorization header as `Bearer` token auth. authorizationHeader = this.authAsHeaders(auth); } const headers = { ...authorizationHeader, 'Notion-Version': this.#notionVersion, 'user-agent': this.#userAgent, }; if (bodyAsJsonString !== undefined) { headers['content-type'] = 'application/json'; } try { const response = await fetch(url.toString(), { method: method.toUpperCase(), headers, body: bodyAsJsonString, signal: AbortSignal.timeout(60_000), }); const responseText = await response.text(); if (!response.ok) { throw buildRequestError(response, responseText); } const responseJson = JSON.parse(responseText); this.log(LogLevel.INFO, `request success`, { method, path }); return responseJson; } catch (error) { if (!isNotionClientError(error)) { throw error; } // Log the error if it's one of our known error types this.log(LogLevel.WARN, `request fail`, { code: error.code, message: error.message, }); if (isHTTPResponseError(error)) { // The response body may contain sensitive information so it is logged separately at the DEBUG level this.log(LogLevel.DEBUG, `failed response body`, { body: error.body, }); } throw error; } } /* * Notion API endpoints */ blocks = { /** * Retrieve block */ retrieve: (args) => { return this.request({ path: getBlock.path(args), method: getBlock.method, query: pick(args, getBlock.queryParams), body: pick(args, getBlock.bodyParams), auth: args?.auth, }); }, /** * Update block */ update: (args) => { return this.request({ path: updateBlock.path(args), method: updateBlock.method, query: pick(args, updateBlock.queryParams), body: pick(args, updateBlock.bodyParams), auth: args?.auth, }); }, /** * Delete block */ delete: (args) => { return this.request({ path: deleteBlock.path(args), method: deleteBlock.method, query: pick(args, deleteBlock.queryParams), body: pick(args, deleteBlock.bodyParams), auth: args?.auth, }); }, children: { /** * Append block children */ append: (args) => { return this.request({ path: appendBlockChildren.path(args), method: appendBlockChildren.method, query: pick(args, appendBlockChildren.queryParams), body: pick(args, appendBlockChildren.bodyParams), auth: args?.auth, }); }, /** * Retrieve block children */ list: (args) => { return this.request({ path: listBlockChildren.path(args), method: listBlockChildren.method, query: pick(args, listBlockChildren.queryParams), body: pick(args, listBlockChildren.bodyParams), auth: args?.auth, }); }, }, }; databases = { /** * List databases * * @deprecated Please use `search` */ list: (args) => { return this.request({ path: listDatabases.path(), method: listDatabases.method, query: pick(args, listDatabases.queryParams), body: pick(args, listDatabases.bodyParams), auth: args?.auth, }); }, /** * Retrieve a database */ retrieve: (args) => { return this.request({ path: getDatabase.path(args), method: getDatabase.method, query: pick(args, getDatabase.queryParams), body: pick(args, getDatabase.bodyParams), auth: args?.auth, }); }, /** * Query a database */ query: (args) => { return this.request({ path: queryDatabase.path(args), method: queryDatabase.method, query: pick(args, queryDatabase.queryParams), body: pick(args, queryDatabase.bodyParams), auth: args?.auth, }); }, /** * Create a database */ create: (args) => { return this.request({ path: createDatabase.path(), method: createDatabase.method, query: pick(args, createDatabase.queryParams), body: pick(args, createDatabase.bodyParams), auth: args?.auth, }); }, /** * Update a database */ update: (args) => { return this.request({ path: updateDatabase.path(args), method: updateDatabase.method, query: pick(args, updateDatabase.queryParams), body: pick(args, updateDatabase.bodyParams), auth: args?.auth, }); }, }; pages = { /** * Create a page */ create: (args) => { return this.request({ path: createPage.path(), method: createPage.method, query: pick(args, createPage.queryParams), body: pick(args, createPage.bodyParams), auth: args?.auth, }); }, /** * Retrieve a page */ retrieve: (args) => { return this.request({ path: getPage.path(args), method: getPage.method, query: pick(args, getPage.queryParams), body: pick(args, getPage.bodyParams), auth: args?.auth, }); }, /** * Update page properties */ update: (args) => { return this.request({ path: updatePage.path(args), method: updatePage.method, query: pick(args, updatePage.queryParams), body: pick(args, updatePage.bodyParams), auth: args?.auth, }); }, properties: { /** * Retrieve page property */ retrieve: (args) => { return this.request({ path: getPageProperty.path(args), method: getPageProperty.method, query: pick(args, getPageProperty.queryParams), body: pick(args, getPageProperty.bodyParams), auth: args?.auth, }); }, }, }; users = { /** * Retrieve a user */ retrieve: (args) => { return this.request({ path: getUser.path(args), method: getUser.method, query: pick(args, getUser.queryParams), body: pick(args, getUser.bodyParams), auth: args?.auth, }); }, /** * List all users */ list: (args) => { return this.request({ path: listUsers.path(), method: listUsers.method, query: pick(args, listUsers.queryParams), body: pick(args, listUsers.bodyParams), auth: args?.auth, }); }, /** * Get details about bot */ me: (args) => { return this.request({ path: getSelf.path(), method: getSelf.method, query: pick(args, getSelf.queryParams), body: pick(args, getSelf.bodyParams), auth: args?.auth, }); }, }; comments = { /** * Create a comment */ create: (args) => { return this.request({ path: createComment.path(), method: createComment.method, query: pick(args, createComment.queryParams), body: pick(args, createComment.bodyParams), auth: args?.auth, }); }, /** * List comments */ list: (args) => { return this.request({ path: listComments.path(), method: listComments.method, query: pick(args, listComments.queryParams), body: pick(args, listComments.bodyParams), auth: args?.auth, }); }, }; /** * Search */ search = (args) => { return this.request({ path: search.path(), method: search.method, query: pick(args, search.queryParams), body: pick(args, search.bodyParams), auth: args?.auth, }); }; oauth = { /** * Get token */ token: (args) => { return this.request({ path: oauthToken.path(), method: oauthToken.method, query: pick(args, oauthToken.queryParams), body: pick(args, oauthToken.bodyParams), auth: { client_id: args.client_id, client_secret: args.client_secret, }, }); }, }; /** * Emits a log message to the console. * * @param level The level for this message * @param args Arguments to send to the console */ log(level, message, extraInfo) { if (logLevelSeverity(level) >= logLevelSeverity(this.#logLevel)) { this.#logger(level, message, extraInfo); } } /** * Transforms an API key or access token into a headers object suitable for an HTTP request. * * This method uses the instance's value as the default when the input is undefined. If neither are defined, it returns * an empty object * * @param auth API key or access token * @returns headers key-value object */ authAsHeaders(auth) { const headers = {}; const authHeaderValue = auth ?? this.#auth; if (authHeaderValue !== undefined) { headers['authorization'] = `Bearer ${authHeaderValue}`; } return headers; } } //# sourceMappingURL=Client.js.map