From 22e3c8eeaa1a8b70a60b509493652176ced4b5d3 Mon Sep 17 00:00:00 2001 From: Nathan Gendron Date: Wed, 18 Sep 2024 14:34:30 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20Fix=20base64=20encoding=20in=20b?= =?UTF-8?q?rowser?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++++----- src/base64.test.ts | 12 ++++++++++++ src/base64.ts | 11 +++++++++++ src/cloud/client.ts | 12 +++++++++++- src/index.ts | 1 + src/server/client.ts | 12 ++++++++++++ tests/cloud/client.ts | 17 +++++++---------- tests/env.ts | 15 +++++++++++---- tests/server/client.ts | 17 +++++++---------- 9 files changed, 77 insertions(+), 30 deletions(-) create mode 100644 src/base64.test.ts create mode 100644 src/base64.ts diff --git a/README.md b/README.md index 778d5a6..03ecd58 100644 --- a/README.md +++ b/README.md @@ -18,19 +18,19 @@ pnpm i @coderabbitai/bitbucket ### Cloud ```ts -import { createBitbucketCloudClient } from "@coderabbitai/bitbucket" +import { createBitbucketCloudClient, toBase64 } from "@coderabbitai/bitbucket" import { BITBUCKET_CLOUD_APP_PASSWORD, BITBUCKET_CLOUD_URL, BITBUCKET_CLOUD_USERNAME, } from "./env.js" -const basic = Buffer.from( +const basic = toBase64( BITBUCKET_CLOUD_USERNAME + ":" + BITBUCKET_CLOUD_APP_PASSWORD, -).toString("base64") +) -export const cloud = createBitbucketCloudClient({ - baseUrl: BITBUCKET_CLOUD_URL, +export const client = createBitbucketCloudClient({ + baseUrl: BITBUCKET_CLOUD_URL.toString(), headers: { Accept: "application/json", Authorization: `Basic ${basic}` }, }) ``` diff --git a/src/base64.test.ts b/src/base64.test.ts new file mode 100644 index 0000000..fd91cba --- /dev/null +++ b/src/base64.test.ts @@ -0,0 +1,12 @@ +import { test } from "vitest" +import { fromBase64, toBase64 } from "./base64.js" + +test("toBase64", ({ expect }) => { + const based = toBase64("Copyright © 2024 CodeRabbit") + expect(based).toBe("Q29weXJpZ2h0IMKpIDIwMjQgQ29kZVJhYmJpdA==") +}) + +test("fromBase64", ({ expect }) => { + const debased = fromBase64("Q29weXJpZ2h0IMKpIDIwMjQgQ29kZVJhYmJpdA==") + expect(debased).toBe("Copyright © 2024 CodeRabbit") +}) diff --git a/src/base64.ts b/src/base64.ts new file mode 100644 index 0000000..45b0941 --- /dev/null +++ b/src/base64.ts @@ -0,0 +1,11 @@ +export function toBase64(input: string): string { + const bytes = new TextEncoder().encode(input) + const string = String.fromCodePoint(...bytes) + return btoa(string) +} + +export function fromBase64(base64: string): Buffer | Uint8Array | string { + const string = atob(base64) + const bytes = Uint8Array.from(string, v => v.codePointAt(0) ?? 0) + return new TextDecoder().decode(bytes) +} diff --git a/src/cloud/client.ts b/src/cloud/client.ts index e0f5205..60763da 100644 --- a/src/cloud/client.ts +++ b/src/cloud/client.ts @@ -1,6 +1,16 @@ -import createClient, { type Client, type ClientOptions } from "openapi-fetch" +import type { Client, ClientOptions } from "openapi-fetch" +import createClient from "openapi-fetch" import type { paths } from "./openapi/index.js" +/** + * Creates an `openapi-fetch` client using {@link createClient}. + * + * @example + * export client = createBitbucketCloudClient({ + * baseUrl: "https://api.bitbucket.org/2.0", + * headers: { Accept: "application/json", Authorization: `Basic ${basic}` }, + * }) + */ export const createBitbucketCloudClient: ( clientOptions?: ClientOptions, ) => Client = createClient diff --git a/src/index.ts b/src/index.ts index 9da4557..ce4bf6b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,3 @@ +export * from "./base64.js" export * from "./cloud/index.js" export * from "./server/index.js" diff --git a/src/server/client.ts b/src/server/client.ts index e02475d..ba4d305 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -2,6 +2,18 @@ import type { Client, ClientOptions } from "openapi-fetch" import createClient from "openapi-fetch" import type { paths } from "./openapi/index.js" +/** + * Creates an `openapi-fetch` client using {@link createClient}. + * + * @example + * export const client = createBitbucketServerClient({ + * baseUrl: "https://example.org/rest", + * headers: { + * Accept: "application/json", + * Authorization: `Bearer ${BITBUCKET_SERVER_TOKEN}`, + * }, + * }) + */ export const createBitbucketServerClient: ( clientOptions?: ClientOptions, ) => Client = createClient diff --git a/tests/cloud/client.ts b/tests/cloud/client.ts index 9050971..baffd3b 100644 --- a/tests/cloud/client.ts +++ b/tests/cloud/client.ts @@ -1,18 +1,15 @@ -import type { Client } from "openapi-fetch" -import type { paths } from "../../src/cloud/openapi/openapi-typescript.js" -import { createBitbucketCloudClient } from "../../src/index.js" +import { createBitbucketCloudClient, toBase64 } from "../../src/index.js" import { BITBUCKET_CLOUD_APP_PASSWORD, BITBUCKET_CLOUD_URL, BITBUCKET_CLOUD_USERNAME, } from "../env.js" -const basic = Buffer.from( +const basic = toBase64( BITBUCKET_CLOUD_USERNAME + ":" + BITBUCKET_CLOUD_APP_PASSWORD, -).toString("base64") +) -export const cloud: Client = - createBitbucketCloudClient({ - baseUrl: BITBUCKET_CLOUD_URL, - headers: { Accept: "application/json", Authorization: `Basic ${basic}` }, - }) +export const cloud = createBitbucketCloudClient({ + baseUrl: BITBUCKET_CLOUD_URL.toString(), + headers: { Accept: "application/json", Authorization: `Basic ${basic}` }, +}) diff --git a/tests/env.ts b/tests/env.ts index c6f862a..3dd6ab6 100644 --- a/tests/env.ts +++ b/tests/env.ts @@ -18,6 +18,15 @@ function envString(key: string) { return value } +function envUrl(key: string) { + const str = envString(key) + try { + return new URL(str) + } catch (error) { + throw new Error(`$${key} is not a URL: ${str}`, { cause: error }) + } +} + export function isNodeEnv(value: unknown): value is NodeEnv { return Object.values(nodeEnvs).includes(value) } @@ -72,12 +81,10 @@ const nodeEnvs = { test: "test", } as const const parsed = loadEnv() - -export const BITBUCKET_CLOUD_URL = envString("BITBUCKET_CLOUD_URL") +export const BITBUCKET_CLOUD_URL = envUrl("BITBUCKET_CLOUD_URL") export const BITBUCKET_CLOUD_USERNAME = envString("BITBUCKET_CLOUD_USERNAME") export const BITBUCKET_CLOUD_APP_PASSWORD = envString( "BITBUCKET_CLOUD_APP_PASSWORD", ) - -export const BITBUCKET_SERVER_URL = envString("BITBUCKET_SERVER_URL") +export const BITBUCKET_SERVER_URL = envUrl("BITBUCKET_SERVER_URL") export const BITBUCKET_SERVER_TOKEN = envString("BITBUCKET_SERVER_TOKEN") diff --git a/tests/server/client.ts b/tests/server/client.ts index 959eb02..def3086 100644 --- a/tests/server/client.ts +++ b/tests/server/client.ts @@ -1,13 +1,10 @@ -import type { Client } from "openapi-fetch" import { createBitbucketServerClient } from "../../src/index.js" -import type { paths } from "../../src/server/openapi/openapi-typescript.js" import { BITBUCKET_SERVER_TOKEN, BITBUCKET_SERVER_URL } from "../env.js" -export const server: Client = - createBitbucketServerClient({ - baseUrl: BITBUCKET_SERVER_URL, - headers: { - Accept: "application/json", - Authorization: `Bearer ${BITBUCKET_SERVER_TOKEN}`, - }, - }) +export const server = createBitbucketServerClient({ + baseUrl: BITBUCKET_SERVER_URL.toString(), + headers: { + Accept: "application/json", + Authorization: `Bearer ${BITBUCKET_SERVER_TOKEN}`, + }, +})