Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 95 additions & 49 deletions src/Client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Agent } from "http"
import type { Agent } from "node:http"
import {
Logger,
type Logger,
LogLevel,
logLevelSeverity,
makeConsoleLogger,
Expand All @@ -13,76 +13,82 @@ import {
} from "./errors"
import { pick } from "./utils"
import {
GetBlockParameters,
GetBlockResponse,
type GetBlockParameters,
type GetBlockResponse,
getBlock,
UpdateBlockParameters,
UpdateBlockResponse,
type UpdateBlockParameters,
type UpdateBlockResponse,
updateBlock,
DeleteBlockParameters,
DeleteBlockResponse,
type DeleteBlockParameters,
type DeleteBlockResponse,
deleteBlock,
AppendBlockChildrenParameters,
AppendBlockChildrenResponse,
type AppendBlockChildrenParameters,
type AppendBlockChildrenResponse,
appendBlockChildren,
ListBlockChildrenParameters,
ListBlockChildrenResponse,
type ListBlockChildrenParameters,
type ListBlockChildrenResponse,
listBlockChildren,
ListDatabasesParameters,
ListDatabasesResponse,
type ListDatabasesParameters,
type ListDatabasesResponse,
listDatabases,
GetDatabaseParameters,
GetDatabaseResponse,
type GetDatabaseParameters,
type GetDatabaseResponse,
getDatabase,
QueryDatabaseParameters,
QueryDatabaseResponse,
type QueryDatabaseParameters,
type QueryDatabaseResponse,
queryDatabase,
CreateDatabaseParameters,
CreateDatabaseResponse,
type CreateDatabaseParameters,
type CreateDatabaseResponse,
createDatabase,
UpdateDatabaseParameters,
UpdateDatabaseResponse,
type UpdateDatabaseParameters,
type UpdateDatabaseResponse,
updateDatabase,
CreatePageParameters,
CreatePageResponse,
type CreatePageParameters,
type CreatePageResponse,
createPage,
GetPageParameters,
GetPageResponse,
type GetPageParameters,
type GetPageResponse,
getPage,
UpdatePageParameters,
UpdatePageResponse,
type UpdatePageParameters,
type UpdatePageResponse,
updatePage,
GetUserParameters,
GetUserResponse,
type GetUserParameters,
type GetUserResponse,
getUser,
ListUsersParameters,
ListUsersResponse,
type ListUsersParameters,
type ListUsersResponse,
listUsers,
SearchParameters,
SearchResponse,
type SearchParameters,
type SearchResponse,
search,
GetSelfParameters,
GetSelfResponse,
type GetSelfParameters,
type GetSelfResponse,
getSelf,
GetPagePropertyParameters,
GetPagePropertyResponse,
type GetPagePropertyParameters,
type GetPagePropertyResponse,
getPageProperty,
CreateCommentParameters,
CreateCommentResponse,
type CreateCommentParameters,
type CreateCommentResponse,
createComment,
ListCommentsParameters,
ListCommentsResponse,
type ListCommentsParameters,
type ListCommentsResponse,
listComments,
OauthTokenResponse,
OauthTokenParameters,
type OauthTokenResponse,
type OauthTokenParameters,
oauthToken,
type OauthIntrospectResponse,
type OauthIntrospectParameters,
oauthIntrospect,
type OauthRevokeResponse,
type OauthRevokeParameters,
oauthRevoke,
} from "./api-endpoints"
import nodeFetch from "node-fetch"
import {
version as PACKAGE_VERSION,
name as PACKAGE_NAME,
} from "../package.json"
import { SupportedFetch } from "./fetch-types"
import type { SupportedFetch } from "./fetch-types"

export interface ClientOptions {
auth?: string
Expand Down Expand Up @@ -131,7 +137,7 @@ export default class Client {
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.#prefixUrl = `${options?.baseUrl ?? "https://api.notion.com"}/v1/`
this.#timeoutMs = options?.timeoutMs ?? 60_000
this.#notionVersion = options?.notionVersion ?? Client.defaultNotionVersion
this.#fetch = options?.fetch ?? nodeFetch
Expand Down Expand Up @@ -219,22 +225,22 @@ export default class Client {
}

const responseJson: ResponseBody = JSON.parse(responseText)
this.log(LogLevel.INFO, `request success`, { method, path })
this.log(LogLevel.INFO, "request success", { method, path })
return responseJson
} catch (error: unknown) {
if (!isNotionClientError(error)) {
throw error
}

// Log the error if it's one of our known error types
this.log(LogLevel.WARN, `request fail`, {
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`, {
this.log(LogLevel.DEBUG, "failed response body", {
body: error.body,
})
}
Expand Down Expand Up @@ -574,6 +580,46 @@ export default class Client {
},
})
},
/**
* Introspect token
*/
introspect: (
args: OauthIntrospectParameters & {
client_id: string
client_secret: string
}
): Promise<OauthIntrospectResponse> => {
return this.request<OauthIntrospectResponse>({
path: oauthIntrospect.path(),
method: oauthIntrospect.method,
query: pick(args, oauthIntrospect.queryParams),
body: pick(args, oauthIntrospect.bodyParams),
auth: {
client_id: args.client_id,
client_secret: args.client_secret,
},
})
},
/**
* Revoke token
*/
revoke: (
args: OauthRevokeParameters & {
client_id: string
client_secret: string
}
): Promise<OauthRevokeResponse> => {
return this.request<OauthRevokeResponse>({
path: oauthRevoke.path(),
method: oauthRevoke.method,
query: pick(args, oauthRevoke.queryParams),
body: pick(args, oauthRevoke.bodyParams),
auth: {
client_id: args.client_id,
client_secret: args.client_secret,
},
})
},
}

/**
Expand Down
32 changes: 32 additions & 0 deletions src/api-endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11768,3 +11768,35 @@ export const oauthToken = {
bodyParams: ["grant_type", "code", "redirect_uri", "external_account"],
path: (): string => `oauth/token`,
} as const

type OauthRevokeBodyParameters = { token: string }

export type OauthRevokeParameters = OauthRevokeBodyParameters

export type OauthRevokeResponse = Record<string, never>

export const oauthRevoke = {
method: "post",
pathParams: [],
queryParams: [],
bodyParams: ["token"],
path: (): string => `oauth/revoke`,
} as const

type OauthIntrospectBodyParameters = { token: string }

export type OauthIntrospectParameters = OauthIntrospectBodyParameters

export type OauthIntrospectResponse = {
active: boolean
scope?: string
iat?: number
}

export const oauthIntrospect = {
method: "post",
pathParams: [],
queryParams: [],
bodyParams: ["token"],
path: (): string => `oauth/introspect`,
} as const
32 changes: 32 additions & 0 deletions test/Client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,36 @@ describe("Notion SDK Client", () => {
it("Constructs without throwing", () => {
new Client({ auth: "foo" })
})

it("calls revoke API with basic auth", async () => {
const mockFetch = jest.fn()
mockFetch.mockResolvedValue({
ok: true,
text: () => "{}",
headers: {},
status: 200,
})

const notion = new Client({ fetch: mockFetch })

await notion.oauth.revoke({
client_id: "client_id",
client_secret: "client_secret",
token: "token",
})

expect(mockFetch).toHaveBeenCalledWith(
"https://api.notion.com/v1/oauth/revoke",
expect.objectContaining({
method: "POST",
headers: expect.objectContaining({
"Notion-Version": "2022-06-28",
"user-agent": expect.stringContaining("notionhq-client"),
authorization: `Basic ${Buffer.from(
"client_id:client_secret"
).toString("base64")}`,
}),
})
)
})
})