diff --git a/.github/workflows/create-releases.yml b/.github/workflows/create-releases.yml new file mode 100644 index 0000000..bbfa092 --- /dev/null +++ b/.github/workflows/create-releases.yml @@ -0,0 +1,40 @@ +name: Create releases +on: + schedule: + - cron: '0 5 * * *' # every day at 5am UTC + push: + branches: + - main + +jobs: + release: + name: release + if: github.ref == 'refs/heads/main' && github.repository == 'beeper/beeper-desktop-api-typescript' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: stainless-api/trigger-release-please@v1 + id: release + with: + repo: ${{ github.event.repository.full_name }} + stainless-api-key: ${{ secrets.STAINLESS_API_KEY }} + + - name: Set up Node + if: ${{ steps.release.outputs.releases_created }} + uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Install dependencies + if: ${{ steps.release.outputs.releases_created }} + run: | + yarn install + + - name: Publish to NPM + if: ${{ steps.release.outputs.releases_created }} + run: | + bash ./bin/publish-npm + env: + NPM_TOKEN: ${{ secrets.BEEPER-DESKTOP_NPM_TOKEN || secrets.NPM_TOKEN }} diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml new file mode 100644 index 0000000..7baee3b --- /dev/null +++ b/.github/workflows/publish-npm.yml @@ -0,0 +1,28 @@ +# This workflow publishes the package to NPM. +# You can run this workflow manually by navigating to https://www.github.com/beeper/beeper-desktop-api-typescript/actions/workflows/publish-npm.yml +name: Publish NPM +on: + workflow_dispatch: + +jobs: + publish: + name: publish + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v3 + with: + node-version: '20' + + - name: Install dependencies + run: | + yarn install + + - name: Publish to NPM + run: | + bash ./bin/publish-npm + env: + NPM_TOKEN: ${{ secrets.BEEPER-DESKTOP_NPM_TOKEN || secrets.NPM_TOKEN }} diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml new file mode 100644 index 0000000..50d7a98 --- /dev/null +++ b/.github/workflows/release-doctor.yml @@ -0,0 +1,22 @@ +name: Release Doctor +on: + pull_request: + branches: + - main + workflow_dispatch: + +jobs: + release_doctor: + name: release doctor + runs-on: ubuntu-latest + if: github.repository == 'beeper/beeper-desktop-api-typescript' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') + + steps: + - uses: actions/checkout@v4 + + - name: Check release environment + run: | + bash ./bin/check-release-environment + env: + STAINLESS_API_KEY: ${{ secrets.STAINLESS_API_KEY }} + NPM_TOKEN: ${{ secrets.BEEPER-DESKTOP_NPM_TOKEN || secrets.NPM_TOKEN }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..466df71 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.1.0" +} diff --git a/.stats.yml b/.stats.yml index 0096234..d7202aa 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 13 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/beeper%2Fbeeper-desktop-api-10508b7bd52381c216ed2584b7b0894f70fcc97fbd7f86792e78f72d108157c5.yml openapi_spec_hash: d5f07fd2a363877f4c72da1187b8aaf1 -config_hash: 552d4814bcb4837f1d1acfc0f48773dd +config_hash: a0c9d70d8cba3958dd1d2d52dc5609a1 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6037246 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +## 0.1.0 (2025-08-24) + +Full Changelog: [v0.0.1...v0.1.0](https://github.com/beeper/desktop-api-typescript/compare/v0.0.1...v0.1.0) + +### Features + +* **api:** update via SDK Studio ([5dfa3e9](https://github.com/beeper/desktop-api-typescript/commit/5dfa3e98f5f77f3039907c8f799017990bd5836d)) + + +### Chores + +* update SDK settings ([5cf5745](https://github.com/beeper/desktop-api-typescript/commit/5cf5745b8b07bdbe89099f3d3d25238cf155c33b)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8dbbdce..7f2cd2c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,25 +42,25 @@ If you’d like to use the repository from source, you can either install from g To install via git: ```sh -$ npm install git+ssh://git@github.com:stainless-sdks/beeper-desktop-api-typescript.git +$ npm install git+ssh://git@github.com:beeper/beeper-desktop-api-typescript.git ``` Alternatively, to link a local copy of the repo: ```sh # Clone -$ git clone https://www.github.com/stainless-sdks/beeper-desktop-api-typescript +$ git clone https://www.github.com/beeper/beeper-desktop-api-typescript $ cd beeper-desktop-api-typescript # With yarn $ yarn link $ cd ../my-package -$ yarn link beeper-desktop-api +$ yarn link beeper-desktop-api-typescript # With pnpm $ pnpm link --global $ cd ../my-package -$ pnpm link -—global beeper-desktop-api +$ pnpm link -—global beeper-desktop-api-typescript ``` ## Running tests @@ -91,3 +91,17 @@ To format and fix all lint issues automatically: ```sh $ yarn fix ``` + +## Publishing and releases + +Changes made to this repository via the automated release PR pipeline should publish to npm automatically. If +the changes aren't made through the automated pipeline, you may want to make releases manually. + +### Publish with a GitHub workflow + +You can release to package managers by using [the `Publish NPM` GitHub action](https://www.github.com/beeper/beeper-desktop-api-typescript/actions/workflows/publish-npm.yml). This requires a setup organization or repository secret to be set up. + +### Publish manually + +If you need to manually release a package, you can run the `bin/publish-npm` script with an `NPM_TOKEN` set on +the environment. diff --git a/LICENSE b/LICENSE index a020613..59424c8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2025 beeper-desktop-api +Copyright 2025 beeperdesktop Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index 5437b5e..c0e1a43 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,35 @@ -# Beeper Desktop API TypeScript API Library +# Beeper Desktop TypeScript API Library -[![NPM version]()](https://npmjs.org/package/beeper-desktop-api) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/beeper-desktop-api) +[![NPM version]()](https://npmjs.org/package/beeper-desktop-api-typescript) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/beeper-desktop-api-typescript) -This library provides convenient access to the Beeper Desktop API REST API from server-side TypeScript or JavaScript. +This library provides convenient access to the Beeper Desktop REST API from server-side TypeScript or JavaScript. -The REST API documentation can be found on [www.beeper.com](https://www.beeper.com). The full API of this library can be found in [api.md](api.md). +The REST API documentation can be found on [www.beeper.com](https://www.beeper.com/desktop-api). The full API of this library can be found in [api.md](api.md). It is generated with [Stainless](https://www.stainless.com/). ## Installation ```sh -npm install git+ssh://git@github.com:stainless-sdks/beeper-desktop-api-typescript.git +npm install beeper-desktop-api-typescript ``` -> [!NOTE] -> Once this package is [published to npm](https://www.stainless.com/docs/guides/publish), this will become: `npm install beeper-desktop-api` - ## Usage The full API of this library can be found in [api.md](api.md). ```js -import BeeperDesktopAPI from 'beeper-desktop-api'; +import BeeperDesktop from 'beeper-desktop-api-typescript'; -const client = new BeeperDesktopAPI({ - apiKey: process.env['BEEPER_DESKTOP_API_API_KEY'], // This is the default and can be omitted +const client = new BeeperDesktop({ + accessToken: process.env['BEEPER_ACCESS_TOKEN'], // This is the default and can be omitted }); -const getAccounts = await client.getAccounts.list(); +const page = await client.chats.find({ limit: 10, type: 'single' }); +const chat = page.data[0]; -console.log(getAccounts.accounts); +console.log(chat.id); ``` ### Request & Response types @@ -40,13 +38,13 @@ This library includes TypeScript definitions for all request params and response ```ts -import BeeperDesktopAPI from 'beeper-desktop-api'; +import BeeperDesktop from 'beeper-desktop-api-typescript'; -const client = new BeeperDesktopAPI({ - apiKey: process.env['BEEPER_DESKTOP_API_API_KEY'], // This is the default and can be omitted +const client = new BeeperDesktop({ + accessToken: process.env['BEEPER_ACCESS_TOKEN'], // This is the default and can be omitted }); -const getAccounts: BeeperDesktopAPI.GetAccountListResponse = await client.getAccounts.list(); +const accountsResponse: BeeperDesktop.AccountsResponse = await client.accounts.list(); ``` Documentation for each method, request param, and response field are available in docstrings and will appear on hover in most modern editors. @@ -59,15 +57,17 @@ a subclass of `APIError` will be thrown: ```ts -const getAccounts = await client.getAccounts.list().catch(async (err) => { - if (err instanceof BeeperDesktopAPI.APIError) { - console.log(err.status); // 400 - console.log(err.name); // BadRequestError - console.log(err.headers); // {server: 'nginx', ...} - } else { - throw err; - } -}); +const sendResponse = await client.messages + .send({ chatID: '!invalid-chat-id', text: 'Test message' }) + .catch(async (err) => { + if (err instanceof BeeperDesktop.APIError) { + console.log(err.status); // 400 + console.log(err.name); // BadRequestError + console.log(err.headers); // {server: 'nginx', ...} + } else { + throw err; + } + }); ``` Error codes are as follows: @@ -85,7 +85,7 @@ Error codes are as follows: ### Retries -Certain errors will be automatically retried 2 times by default, with a short exponential backoff. +Certain errors will be automatically retried 3 times by default, with a short exponential backoff. Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, 429 Rate Limit, and >=500 Internal errors will all be retried by default. @@ -94,29 +94,29 @@ You can use the `maxRetries` option to configure or disable this: ```js // Configure the default for all requests: -const client = new BeeperDesktopAPI({ +const client = new BeeperDesktop({ maxRetries: 0, // default is 2 }); // Or, configure per-request: -await client.getAccounts.list({ +await client.accounts.list({ maxRetries: 5, }); ``` ### Timeouts -Requests time out after 1 minute by default. You can configure this with a `timeout` option: +Requests time out after 30 seconds by default. You can configure this with a `timeout` option: ```ts // Configure the default for all requests: -const client = new BeeperDesktopAPI({ - timeout: 20 * 1000, // 20 seconds (default is 1 minute) +const client = new BeeperDesktop({ + timeout: 20 * 1000, // 20 seconds (default is 30 seconds) }); // Override per-request: -await client.getAccounts.list({ +await client.accounts.list({ timeout: 5 * 1000, }); ``` @@ -125,6 +125,37 @@ On timeout, an `APIConnectionTimeoutError` is thrown. Note that requests which time out will be [retried twice by default](#retries). +## Auto-pagination + +List methods in the BeeperDesktop API are paginated. +You can use the `for await … of` syntax to iterate through items across all pages: + +```ts +async function fetchAllMessages(params) { + const allMessages = []; + // Automatically fetches more pages as needed. + for await (const message of client.messages.search({ limit: 20, query: 'meeting' })) { + allMessages.push(message); + } + return allMessages; +} +``` + +Alternatively, you can request a single page at a time: + +```ts +let page = await client.messages.search({ limit: 20, query: 'meeting' }); +for (const message of page.data) { + console.log(message); +} + +// Convenience methods are provided for manually paginating: +while (page.hasNextPage()) { + page = await page.getNextPage(); + // ... +} +``` + ## Advanced Usage ### Accessing raw Response data (e.g., headers) @@ -137,15 +168,15 @@ Unlike `.asResponse()` this method consumes the body, returning once it is parse ```ts -const client = new BeeperDesktopAPI(); +const client = new BeeperDesktop(); -const response = await client.getAccounts.list().asResponse(); +const response = await client.accounts.list().asResponse(); console.log(response.headers.get('X-My-Header')); console.log(response.statusText); // access the underlying Response object -const { data: getAccounts, response: raw } = await client.getAccounts.list().withResponse(); +const { data: accountsResponse, response: raw } = await client.accounts.list().withResponse(); console.log(raw.headers.get('X-My-Header')); -console.log(getAccounts.accounts); +console.log(accountsResponse.accounts); ``` ### Logging @@ -158,13 +189,13 @@ console.log(getAccounts.accounts); The log level can be configured in two ways: -1. Via the `BEEPER_DESKTOP_API_LOG` environment variable +1. Via the `BEEPER-DESKTOP_LOG` environment variable 2. Using the `logLevel` client option (overrides the environment variable if set) ```ts -import BeeperDesktopAPI from 'beeper-desktop-api'; +import BeeperDesktop from 'beeper-desktop-api-typescript'; -const client = new BeeperDesktopAPI({ +const client = new BeeperDesktop({ logLevel: 'debug', // Show all log messages }); ``` @@ -190,13 +221,13 @@ When providing a custom logger, the `logLevel` option still controls which messa below the configured level will not be sent to your logger. ```ts -import BeeperDesktopAPI from 'beeper-desktop-api'; +import BeeperDesktop from 'beeper-desktop-api-typescript'; import pino from 'pino'; const logger = pino(); -const client = new BeeperDesktopAPI({ - logger: logger.child({ name: 'BeeperDesktopAPI' }), +const client = new BeeperDesktop({ + logger: logger.child({ name: 'BeeperDesktop' }), logLevel: 'debug', // Send all messages to pino, allowing it to filter }); ``` @@ -225,7 +256,7 @@ parameter. This library doesn't validate at runtime that the request matches the send will be sent as-is. ```ts -client.getAccounts.list({ +client.chats.find({ // ... // @ts-expect-error baz is not yet public baz: 'undocumented option', @@ -259,10 +290,10 @@ globalThis.fetch = fetch; Or pass it to the client: ```ts -import BeeperDesktopAPI from 'beeper-desktop-api'; +import BeeperDesktop from 'beeper-desktop-api-typescript'; import fetch from 'my-fetch'; -const client = new BeeperDesktopAPI({ fetch }); +const client = new BeeperDesktop({ fetch }); ``` ### Fetch options @@ -270,9 +301,9 @@ const client = new BeeperDesktopAPI({ fetch }); If you want to set custom `fetch` options without overriding the `fetch` function, you can provide a `fetchOptions` object when instantiating the client or making a request. (Request-specific options override client options.) ```ts -import BeeperDesktopAPI from 'beeper-desktop-api'; +import BeeperDesktop from 'beeper-desktop-api-typescript'; -const client = new BeeperDesktopAPI({ +const client = new BeeperDesktop({ fetchOptions: { // `RequestInit` options }, @@ -287,11 +318,11 @@ options to requests: **Node** [[docs](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md#example---proxyagent-with-fetch)] ```ts -import BeeperDesktopAPI from 'beeper-desktop-api'; +import BeeperDesktop from 'beeper-desktop-api-typescript'; import * as undici from 'undici'; const proxyAgent = new undici.ProxyAgent('http://localhost:8888'); -const client = new BeeperDesktopAPI({ +const client = new BeeperDesktop({ fetchOptions: { dispatcher: proxyAgent, }, @@ -301,9 +332,9 @@ const client = new BeeperDesktopAPI({ **Bun** [[docs](https://bun.sh/guides/http/proxy)] ```ts -import BeeperDesktopAPI from 'beeper-desktop-api'; +import BeeperDesktop from 'beeper-desktop-api-typescript'; -const client = new BeeperDesktopAPI({ +const client = new BeeperDesktop({ fetchOptions: { proxy: 'http://localhost:8888', }, @@ -313,10 +344,10 @@ const client = new BeeperDesktopAPI({ **Deno** [[docs](https://docs.deno.com/api/deno/~/Deno.createHttpClient)] ```ts -import BeeperDesktopAPI from 'npm:beeper-desktop-api'; +import BeeperDesktop from 'npm:beeper-desktop-api-typescript'; const httpClient = Deno.createHttpClient({ proxy: { url: 'http://localhost:8888' } }); -const client = new BeeperDesktopAPI({ +const client = new BeeperDesktop({ fetchOptions: { client: httpClient, }, @@ -335,7 +366,7 @@ This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) con We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. -We are keen for your feedback; please open an [issue](https://www.github.com/stainless-sdks/beeper-desktop-api-typescript/issues) with questions, bugs, or suggestions. +We are keen for your feedback; please open an [issue](https://www.github.com/beeper/beeper-desktop-api-typescript/issues) with questions, bugs, or suggestions. ## Requirements @@ -343,7 +374,6 @@ TypeScript >= 4.9 is supported. The following runtimes are supported: -- Web browsers (Up-to-date Chrome, Firefox, Safari, Edge, and more) - Node.js 20 LTS or later ([non-EOL](https://endoflife.date/nodejs)) versions. - Deno v1.28.0 or higher. - Bun 1.0 or later. @@ -352,6 +382,9 @@ The following runtimes are supported: - Jest 28 or greater with the `"node"` environment (`"jsdom"` is not supported at this time). - Nitro v2.6 or greater. +> [!WARNING] +> Web browser runtimes aren't supported. The SDK will throw an error if used in a browser environment. + Note that React Native is not supported at this time. If you are interested in other runtime environments, please open or upvote an issue on GitHub. diff --git a/SECURITY.md b/SECURITY.md index 5965b61..d6a0717 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,9 +16,9 @@ before making any information public. ## Reporting Non-SDK Related Security Issues If you encounter security issues that are not directly related to SDKs but pertain to the services -or products provided by Beeper Desktop API, please follow the respective company's security reporting guidelines. +or products provided by Beeper Desktop, please follow the respective company's security reporting guidelines. -### Beeper Desktop API Terms and Policies +### Beeper Desktop Terms and Policies Please contact help@beeper.com for any questions or concerns regarding the security of our services. diff --git a/api.md b/api.md index 360db11..5c0de07 100644 --- a/api.md +++ b/api.md @@ -1,105 +1,91 @@ -# GetAccounts +# Shared Types: -- GetAccountListResponse +- Attachment +- BaseResponse +- Error +- Reaction +- User -Methods: - -- client.getAccounts.list() -> GetAccountListResponse - -# FocusApp - -Types: - -- BaseResponse - -Methods: - -- client.focusApp.open({ ...params }) -> BaseResponse - -# DraftMessage - -Methods: - -- client.draftMessage.create({ ...params }) -> BaseResponse - -# GetLinkToChat +# Accounts Types: -- GetLinkToChatCreateResponse +- Account +- AccountsResponse Methods: -- client.getLinkToChat.create({ ...params }) -> GetLinkToChatCreateResponse +- client.accounts.list() -> AccountsResponse -# GetChat +# App Types: -- User -- GetChatRetrieveResponse +- FocusRequest Methods: -- client.getChat.retrieve({ ...params }) -> GetChatRetrieveResponse | null +- client.app.focus({ ...params }) -> BaseResponse -# FindChats +# Messages Types: -- FindChatListResponse +- DraftRequest +- Message +- SearchRequest +- SearchResponse +- SendRequest +- SendResponse Methods: -- client.findChats.list({ ...params }) -> FindChatListResponse +- client.messages.draft({ ...params }) -> BaseResponse +- client.messages.search({ ...params }) -> MessagesCursorID +- client.messages.send({ ...params }) -> SendResponse -# SearchMessages +# Chats Types: -- SearchMessageSearchResponse +- ArchiveRequest +- Chat +- FindChatsRequest +- FindChatsResponse +- GetChatRequest +- GetChatResponse +- LinkRequest +- LinkResponse Methods: -- client.searchMessages.search({ ...params }) -> SearchMessageSearchResponse +- client.chats.retrieve({ ...params }) -> GetChatResponse | null +- client.chats.archive({ ...params }) -> BaseResponse +- client.chats.find({ ...params }) -> ChatsCursorID +- client.chats.getLink({ ...params }) -> LinkResponse -# SendMessage +# Reminders Types: -- SendMessageSendResponse - -Methods: - -- client.sendMessage.send({ ...params }) -> SendMessageSendResponse - -# ArchiveChat - -Methods: - -- client.archiveChat.archive({ ...params }) -> BaseResponse - -# SetChatReminder - -Methods: - -- client.setChatReminder.create({ ...params }) -> BaseResponse - -# ClearChatReminder +- ClearReminderRequest +- SetReminderRequest Methods: -- client.clearChatReminder.clear({ ...params }) -> BaseResponse +- client.reminders.clear({ ...params }) -> BaseResponse +- client.reminders.set({ ...params }) -> BaseResponse # OAuth Types: -- OAuthRetrieveUserInfoResponse +- RevokeRequest +- UserInfo Methods: -- client.oauth.retrieveUserInfo() -> OAuthRetrieveUserInfoResponse +- client.oauth.getUserInfo() -> UserInfo - client.oauth.revokeToken({ ...params }) -> void diff --git a/bin/check-release-environment b/bin/check-release-environment new file mode 100644 index 0000000..bcf856d --- /dev/null +++ b/bin/check-release-environment @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +errors=() + +if [ -z "${STAINLESS_API_KEY}" ]; then + errors+=("The STAINLESS_API_KEY secret has not been set. Please contact Stainless for an API key & set it in your organization secrets on GitHub.") +fi + +if [ -z "${NPM_TOKEN}" ]; then + errors+=("The NPM_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets") +fi + +lenErrors=${#errors[@]} + +if [[ lenErrors -gt 0 ]]; then + echo -e "Found the following errors in the release environment:\n" + + for error in "${errors[@]}"; do + echo -e "- $error\n" + done + + exit 1 +fi + +echo "The environment is ready to push releases!" + diff --git a/eslint.config.mjs b/eslint.config.mjs index 213af9e..7d3a909 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -25,7 +25,7 @@ export default tseslint.config( { patterns: [ { - regex: '^beeper-desktop-api(/.*)?', + regex: '^beeper-desktop-api-typescript(/.*)?', message: 'Use a relative import, not a package import.', }, ], diff --git a/jest.config.ts b/jest.config.ts index a54effa..4918675 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -7,8 +7,8 @@ const config: JestConfigWithTsJest = { '^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }], }, moduleNameMapper: { - '^beeper-desktop-api$': '/src/index.ts', - '^beeper-desktop-api/(.*)$': '/src/$1', + '^beeper-desktop-api-typescript$': '/src/index.ts', + '^beeper-desktop-api-typescript/(.*)$': '/src/$1', }, modulePathIgnorePatterns: [ '/ecosystem-tests/', diff --git a/package.json b/package.json index 16a8aad..a742681 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "name": "beeper-desktop-api", - "version": "0.0.1", - "description": "The official TypeScript library for the Beeper Desktop API API", - "author": "Beeper Desktop API ", + "name": "beeper-desktop-api-typescript", + "version": "0.1.0", + "description": "The official TypeScript library for the Beeper Desktop API", + "author": "Beeper Desktop ", "types": "dist/index.d.ts", "main": "dist/index.js", "type": "commonjs", - "repository": "github:stainless-sdks/beeper-desktop-api-typescript", + "repository": "github:beeper/beeper-desktop-api-typescript", "license": "MIT", "packageManager": "yarn@1.22.22", "files": [ diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..1ebd0bd --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,64 @@ +{ + "packages": { + ".": {} + }, + "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", + "include-v-in-tag": true, + "include-component-in-tag": false, + "versioning": "prerelease", + "prerelease": true, + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": false, + "pull-request-header": "Automated Release PR", + "pull-request-title-pattern": "release: ${version}", + "changelog-sections": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "perf", + "section": "Performance Improvements" + }, + { + "type": "revert", + "section": "Reverts" + }, + { + "type": "chore", + "section": "Chores" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "style", + "section": "Styles" + }, + { + "type": "refactor", + "section": "Refactors" + }, + { + "type": "test", + "section": "Tests", + "hidden": true + }, + { + "type": "build", + "section": "Build System" + }, + { + "type": "ci", + "section": "Continuous Integration", + "hidden": true + } + ], + "release-type": "node", + "extra-files": ["src/version.ts", "README.md"] +} diff --git a/scripts/build b/scripts/build index a5caef7..ff1a25b 100755 --- a/scripts/build +++ b/scripts/build @@ -8,7 +8,7 @@ node scripts/utils/check-version.cjs # Build into dist and will publish the package from there, # so that src/resources/foo.ts becomes /resources/foo.js -# This way importing from `"beeper-desktop-api/resources/foo"` works +# This way importing from `"beeper-desktop-api-typescript/resources/foo"` works # even with `"moduleResolution": "node"` rm -rf dist; mkdir dist @@ -42,8 +42,8 @@ node scripts/utils/postprocess-files.cjs # make sure that nothing crashes when we require the output CJS or # import the output ESM -(cd dist && node -e 'require("beeper-desktop-api")') -(cd dist && node -e 'import("beeper-desktop-api")' --input-type=module) +(cd dist && node -e 'require("beeper-desktop-api-typescript")') +(cd dist && node -e 'import("beeper-desktop-api-typescript")' --input-type=module) if [ -e ./scripts/build-deno ] then diff --git a/src/client.ts b/src/client.ts index 63b85f9..aaefde5 100644 --- a/src/client.ts +++ b/src/client.ts @@ -13,53 +13,75 @@ import * as Shims from './internal/shims'; import * as Opts from './internal/request-options'; import * as qs from './internal/qs'; import { VERSION } from './version'; -import * as Errors from './core/error'; -import * as Uploads from './core/uploads'; -import * as API from './resources/index'; -import { APIPromise } from './core/api-promise'; -import { ArchiveChat, ArchiveChatArchiveParams } from './resources/archive-chat'; -import { ClearChatReminder, ClearChatReminderClearParams } from './resources/clear-chat-reminder'; -import { DraftMessage, DraftMessageCreateParams } from './resources/draft-message'; -import { FindChatListParams, FindChatListResponse, FindChats } from './resources/find-chats'; -import { BaseResponse, FocusApp, FocusAppOpenParams } from './resources/focus-app'; -import { GetAccountListResponse, GetAccounts } from './resources/get-accounts'; -import { GetChat, GetChatRetrieveParams, GetChatRetrieveResponse, User } from './resources/get-chat'; +import * as Errors from 'beeper/desktop-api/core/error'; +import * as Pagination from 'beeper/desktop-api/core/pagination'; +import { AbstractPage, type CursorIDParams, CursorIDResponse } from 'beeper/desktop-api/core/pagination'; +import * as Uploads from 'beeper/desktop-api/core/uploads'; +import * as API from 'beeper/desktop-api/resources/index'; +import { APIPromise } from 'beeper/desktop-api/core/api-promise'; +import { Account, Accounts, AccountsResponse } from 'beeper/desktop-api/resources/accounts'; +import { App, AppFocusParams, FocusRequest } from 'beeper/desktop-api/resources/app'; import { - GetLinkToChat, - GetLinkToChatCreateParams, - GetLinkToChatCreateResponse, -} from './resources/get-link-to-chat'; -import { OAuth, OAuthRetrieveUserInfoResponse, OAuthRevokeTokenParams } from './resources/oauth'; + ArchiveRequest, + Chat, + ChatArchiveParams, + ChatFindParams, + ChatGetLinkParams, + ChatRetrieveParams, + Chats, + ChatsCursorID, + FindChatsRequest, + FindChatsResponse, + GetChatRequest, + GetChatResponse, + LinkRequest, + LinkResponse, +} from 'beeper/desktop-api/resources/chats'; import { - SearchMessageSearchParams, - SearchMessageSearchResponse, - SearchMessages, -} from './resources/search-messages'; -import { SendMessage, SendMessageSendParams, SendMessageSendResponse } from './resources/send-message'; -import { SetChatReminder, SetChatReminderCreateParams } from './resources/set-chat-reminder'; -import { type Fetch } from './internal/builtin-types'; -import { HeadersLike, NullableHeaders, buildHeaders } from './internal/headers'; -import { FinalRequestOptions, RequestOptions } from './internal/request-options'; -import { readEnv } from './internal/utils/env'; + DraftRequest, + Message, + MessageDraftParams, + MessageSearchParams, + MessageSendParams, + Messages, + MessagesCursorID, + SearchRequest, + SearchResponse, + SendRequest, + SendResponse, +} from 'beeper/desktop-api/resources/messages'; +import { OAuth, OAuthRevokeTokenParams, RevokeRequest, UserInfo } from 'beeper/desktop-api/resources/oauth'; +import { + ClearReminderRequest, + ReminderClearParams, + ReminderSetParams, + Reminders, + SetReminderRequest, +} from 'beeper/desktop-api/resources/reminders'; +import { type Fetch } from 'beeper/desktop-api/internal/builtin-types'; +import { isRunningInBrowser } from 'beeper/desktop-api/internal/detect-platform'; +import { HeadersLike, NullableHeaders, buildHeaders } from 'beeper/desktop-api/internal/headers'; +import { FinalRequestOptions, RequestOptions } from 'beeper/desktop-api/internal/request-options'; +import { readEnv } from 'beeper/desktop-api/internal/utils/env'; import { type LogLevel, type Logger, formatRequestDetails, loggerFor, parseLogLevel, -} from './internal/utils/log'; -import { isEmptyObj } from './internal/utils/values'; +} from 'beeper/desktop-api/internal/utils/log'; +import { isEmptyObj } from 'beeper/desktop-api/internal/utils/values'; export interface ClientOptions { /** - * Bearer token authentication. Token can be obtained either by creating it in-app or through OAuth2 authorization code flow. + * Access token - either created in-app or obtained via OAuth2 authorization code flow */ - apiKey?: string | null | undefined; + accessToken?: string | null | undefined; /** * Override the default base URL for the API, e.g., "https://api.example.com/v2/" * - * Defaults to process.env['BEEPER_DESKTOP_API_BASE_URL']. + * Defaults to process.env['BEEPER-DESKTOP_BASE_URL']. */ baseURL?: string | null | undefined; @@ -90,7 +112,7 @@ export interface ClientOptions { * The maximum number of times that the client will retry a request in case of a * temporary failure, like a network error or a 5XX error from the server. * - * @default 2 + * @default 3 */ maxRetries?: number | undefined; @@ -113,7 +135,7 @@ export interface ClientOptions { /** * Set the log level. * - * Defaults to process.env['BEEPER_DESKTOP_API_LOG'] or 'warn' if it isn't set. + * Defaults to process.env['BEEPER-DESKTOP_LOG'] or 'warn' if it isn't set. */ logLevel?: LogLevel | undefined; @@ -126,10 +148,10 @@ export interface ClientOptions { } /** - * API Client for interfacing with the Beeper Desktop API API. + * API Client for interfacing with the Beeper Desktop API. */ -export class BeeperDesktopAPI { - apiKey: string | null; +export class BeeperDesktop { + accessToken: string | null; baseURL: string; maxRetries: number; @@ -144,46 +166,52 @@ export class BeeperDesktopAPI { private _options: ClientOptions; /** - * API Client for interfacing with the Beeper Desktop API API. + * API Client for interfacing with the Beeper Desktop API. * - * @param {string | null | undefined} [opts.apiKey=process.env['BEEPER_DESKTOP_API_API_KEY'] ?? null] - * @param {string} [opts.baseURL=process.env['BEEPER_DESKTOP_API_BASE_URL'] ?? http://localhost:23374] - Override the default base URL for the API. - * @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. + * @param {string | null | undefined} [opts.accessToken=process.env['BEEPER_ACCESS_TOKEN'] ?? null] + * @param {string} [opts.baseURL=process.env['BEEPER-DESKTOP_BASE_URL'] ?? http://localhost:23374] - Override the default base URL for the API. + * @param {number} [opts.timeout=30 seconds] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. * @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls. * @param {Fetch} [opts.fetch] - Specify a custom `fetch` function implementation. - * @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request. + * @param {number} [opts.maxRetries=3] - The maximum number of times the client will retry a request. * @param {HeadersLike} opts.defaultHeaders - Default headers to include with every request to the API. * @param {Record} opts.defaultQuery - Default query parameters to include with every request to the API. */ constructor({ - baseURL = readEnv('BEEPER_DESKTOP_API_BASE_URL'), - apiKey = readEnv('BEEPER_DESKTOP_API_API_KEY') ?? null, + baseURL = readEnv('BEEPER-DESKTOP_BASE_URL'), + accessToken = readEnv('BEEPER_ACCESS_TOKEN') ?? null, ...opts }: ClientOptions = {}) { const options: ClientOptions = { - apiKey, + accessToken, ...opts, baseURL: baseURL || `http://localhost:23374`, }; + if (isRunningInBrowser()) { + throw new Errors.BeeperDesktopError( + "It looks like you're running in a browser-like environment, which is disabled to protect your secret API credentials from attackers. If you have a strong business need for client-side use of this API, please open a GitHub issue with your use-case and security mitigations.", + ); + } + this.baseURL = options.baseURL!; - this.timeout = options.timeout ?? BeeperDesktopAPI.DEFAULT_TIMEOUT /* 1 minute */; + this.timeout = options.timeout ?? BeeperDesktop.DEFAULT_TIMEOUT /* 30 seconds */; this.logger = options.logger ?? console; const defaultLogLevel = 'warn'; // Set default logLevel early so that we can log a warning in parseLogLevel. this.logLevel = defaultLogLevel; this.logLevel = parseLogLevel(options.logLevel, 'ClientOptions.logLevel', this) ?? - parseLogLevel(readEnv('BEEPER_DESKTOP_API_LOG'), "process.env['BEEPER_DESKTOP_API_LOG']", this) ?? + parseLogLevel(readEnv('BEEPER-DESKTOP_LOG'), "process.env['BEEPER-DESKTOP_LOG']", this) ?? defaultLogLevel; this.fetchOptions = options.fetchOptions; - this.maxRetries = options.maxRetries ?? 2; + this.maxRetries = options.maxRetries ?? 3; this.fetch = options.fetch ?? Shims.getDefaultFetch(); this.#encoder = Opts.FallbackEncoder; this._options = options; - this.apiKey = apiKey; + this.accessToken = accessToken; } /** @@ -199,7 +227,7 @@ export class BeeperDesktopAPI { logLevel: this.logLevel, fetch: this.fetch, fetchOptions: this.fetchOptions, - apiKey: this.apiKey, + accessToken: this.accessToken, ...options, }); return client; @@ -225,10 +253,10 @@ export class BeeperDesktopAPI { } protected async bearerAuth(opts: FinalRequestOptions): Promise { - if (this.apiKey == null) { + if (this.accessToken == null) { return undefined; } - return buildHeaders([{ Authorization: `Bearer ${this.apiKey}` }]); + return buildHeaders([{ Authorization: `Bearer ${this.accessToken}` }]); } protected async oauth2Auth(opts: FinalRequestOptions): Promise { @@ -236,7 +264,7 @@ export class BeeperDesktopAPI { } protected stringifyQuery(query: Record): string { - return qs.stringify(query, { arrayFormat: 'comma' }); + return qs.stringify(query, { arrayFormat: 'repeat' }); } private getUserAgent(): string { @@ -491,6 +519,25 @@ export class BeeperDesktopAPI { return { response, options, controller, requestLogID, retryOfRequestLogID, startTime }; } + getAPIList = Pagination.AbstractPage>( + path: string, + Page: new (...args: any[]) => PageClass, + opts?: RequestOptions, + ): Pagination.PagePromise { + return this.requestAPIList(Page, { method: 'get', path, ...opts }); + } + + requestAPIList< + Item = unknown, + PageClass extends Pagination.AbstractPage = Pagination.AbstractPage, + >( + Page: new (...args: ConstructorParameters) => PageClass, + options: FinalRequestOptions, + ): Pagination.PagePromise { + const request = this.makeRequest(options, null, undefined); + return new Pagination.PagePromise(this as any as BeeperDesktop, request, Page); + } + async fetchWithTimeout( url: RequestInfo, init: RequestInit | undefined, @@ -589,8 +636,8 @@ export class BeeperDesktopAPI { } private calculateDefaultRetryTimeoutMillis(retriesRemaining: number, maxRetries: number): number { - const initialRetryDelay = 0.5; - const maxRetryDelay = 8.0; + const initialRetryDelay = 1.0; + const maxRetryDelay = 10.0; const numRetries = maxRetries - retriesRemaining; @@ -704,10 +751,10 @@ export class BeeperDesktopAPI { } } - static BeeperDesktopAPI = this; - static DEFAULT_TIMEOUT = 60000; // 1 minute + static BeeperDesktop = this; + static DEFAULT_TIMEOUT = 30000; // 30 seconds - static BeeperDesktopAPIError = Errors.BeeperDesktopAPIError; + static BeeperDesktopError = Errors.BeeperDesktopError; static APIError = Errors.APIError; static APIConnectionError = Errors.APIConnectionError; static APIConnectionTimeoutError = Errors.APIConnectionTimeoutError; @@ -723,92 +770,98 @@ export class BeeperDesktopAPI { static toFile = Uploads.toFile; - getAccounts: API.GetAccounts = new API.GetAccounts(this); - focusApp: API.FocusApp = new API.FocusApp(this); - draftMessage: API.DraftMessage = new API.DraftMessage(this); - getLinkToChat: API.GetLinkToChat = new API.GetLinkToChat(this); - getChat: API.GetChat = new API.GetChat(this); - findChats: API.FindChats = new API.FindChats(this); - searchMessages: API.SearchMessages = new API.SearchMessages(this); - sendMessage: API.SendMessage = new API.SendMessage(this); - archiveChat: API.ArchiveChat = new API.ArchiveChat(this); - setChatReminder: API.SetChatReminder = new API.SetChatReminder(this); - clearChatReminder: API.ClearChatReminder = new API.ClearChatReminder(this); + /** + * Manage and list connected messaging accounts + */ + accounts: API.Accounts = new API.Accounts(this); + /** + * Control the Beeper Desktop application + */ + app: API.App = new API.App(this); + /** + * Send, draft, and search messages across all chat networks + */ + messages: API.Messages = new API.Messages(this); + /** + * Manage chats, conversations, and threads + */ + chats: API.Chats = new API.Chats(this); + /** + * Set and clear reminders for chats + */ + reminders: API.Reminders = new API.Reminders(this); + /** + * OAuth2 authentication and token management + */ oauth: API.OAuth = new API.OAuth(this); } -BeeperDesktopAPI.GetAccounts = GetAccounts; -BeeperDesktopAPI.FocusApp = FocusApp; -BeeperDesktopAPI.DraftMessage = DraftMessage; -BeeperDesktopAPI.GetLinkToChat = GetLinkToChat; -BeeperDesktopAPI.GetChat = GetChat; -BeeperDesktopAPI.FindChats = FindChats; -BeeperDesktopAPI.SearchMessages = SearchMessages; -BeeperDesktopAPI.SendMessage = SendMessage; -BeeperDesktopAPI.ArchiveChat = ArchiveChat; -BeeperDesktopAPI.SetChatReminder = SetChatReminder; -BeeperDesktopAPI.ClearChatReminder = ClearChatReminder; -BeeperDesktopAPI.OAuth = OAuth; - -export declare namespace BeeperDesktopAPI { - export type RequestOptions = Opts.RequestOptions; - - export { GetAccounts as GetAccounts, type GetAccountListResponse as GetAccountListResponse }; - - export { - FocusApp as FocusApp, - type BaseResponse as BaseResponse, - type FocusAppOpenParams as FocusAppOpenParams, - }; - - export { DraftMessage as DraftMessage, type DraftMessageCreateParams as DraftMessageCreateParams }; +BeeperDesktop.Accounts = Accounts; +BeeperDesktop.App = App; +BeeperDesktop.Messages = Messages; +BeeperDesktop.Chats = Chats; +BeeperDesktop.Reminders = Reminders; +BeeperDesktop.OAuth = OAuth; - export { - GetLinkToChat as GetLinkToChat, - type GetLinkToChatCreateResponse as GetLinkToChatCreateResponse, - type GetLinkToChatCreateParams as GetLinkToChatCreateParams, - }; +export declare namespace BeeperDesktop { + export type RequestOptions = Opts.RequestOptions; - export { - GetChat as GetChat, - type User as User, - type GetChatRetrieveResponse as GetChatRetrieveResponse, - type GetChatRetrieveParams as GetChatRetrieveParams, - }; + export import CursorID = Pagination.CursorID; + export { type CursorIDParams as CursorIDParams, type CursorIDResponse as CursorIDResponse }; - export { - FindChats as FindChats, - type FindChatListResponse as FindChatListResponse, - type FindChatListParams as FindChatListParams, - }; + export { Accounts as Accounts, type Account as Account, type AccountsResponse as AccountsResponse }; - export { - SearchMessages as SearchMessages, - type SearchMessageSearchResponse as SearchMessageSearchResponse, - type SearchMessageSearchParams as SearchMessageSearchParams, - }; + export { App as App, type FocusRequest as FocusRequest, type AppFocusParams as AppFocusParams }; export { - SendMessage as SendMessage, - type SendMessageSendResponse as SendMessageSendResponse, - type SendMessageSendParams as SendMessageSendParams, + Messages as Messages, + type DraftRequest as DraftRequest, + type Message as Message, + type SearchRequest as SearchRequest, + type SearchResponse as SearchResponse, + type SendRequest as SendRequest, + type SendResponse as SendResponse, + type MessagesCursorID as MessagesCursorID, + type MessageDraftParams as MessageDraftParams, + type MessageSearchParams as MessageSearchParams, + type MessageSendParams as MessageSendParams, }; - export { ArchiveChat as ArchiveChat, type ArchiveChatArchiveParams as ArchiveChatArchiveParams }; - export { - SetChatReminder as SetChatReminder, - type SetChatReminderCreateParams as SetChatReminderCreateParams, + Chats as Chats, + type ArchiveRequest as ArchiveRequest, + type Chat as Chat, + type FindChatsRequest as FindChatsRequest, + type FindChatsResponse as FindChatsResponse, + type GetChatRequest as GetChatRequest, + type GetChatResponse as GetChatResponse, + type LinkRequest as LinkRequest, + type LinkResponse as LinkResponse, + type ChatsCursorID as ChatsCursorID, + type ChatRetrieveParams as ChatRetrieveParams, + type ChatArchiveParams as ChatArchiveParams, + type ChatFindParams as ChatFindParams, + type ChatGetLinkParams as ChatGetLinkParams, }; export { - ClearChatReminder as ClearChatReminder, - type ClearChatReminderClearParams as ClearChatReminderClearParams, + Reminders as Reminders, + type ClearReminderRequest as ClearReminderRequest, + type SetReminderRequest as SetReminderRequest, + type ReminderClearParams as ReminderClearParams, + type ReminderSetParams as ReminderSetParams, }; export { OAuth as OAuth, - type OAuthRetrieveUserInfoResponse as OAuthRetrieveUserInfoResponse, + type RevokeRequest as RevokeRequest, + type UserInfo as UserInfo, type OAuthRevokeTokenParams as OAuthRevokeTokenParams, }; + + export type Attachment = API.Attachment; + export type BaseResponse = API.BaseResponse; + export type Error = API.Error; + export type Reaction = API.Reaction; + export type User = API.User; } diff --git a/src/core/api-promise.ts b/src/core/api-promise.ts index f9f111f..16fa462 100644 --- a/src/core/api-promise.ts +++ b/src/core/api-promise.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { type BeeperDesktopAPI } from '../client'; +import { type BeeperDesktop } from 'beeper/desktop-api/client'; import { type PromiseOrValue } from '../internal/types'; import { APIResponseProps, defaultParseResponse } from '../internal/parse'; @@ -11,13 +11,13 @@ import { APIResponseProps, defaultParseResponse } from '../internal/parse'; */ export class APIPromise extends Promise { private parsedPromise: Promise | undefined; - #client: BeeperDesktopAPI; + #client: BeeperDesktop; constructor( - client: BeeperDesktopAPI, + client: BeeperDesktop, private responsePromise: Promise, private parseResponse: ( - client: BeeperDesktopAPI, + client: BeeperDesktop, props: APIResponseProps, ) => PromiseOrValue = defaultParseResponse, ) { diff --git a/src/core/error.ts b/src/core/error.ts index 816d054..0b3ec08 100644 --- a/src/core/error.ts +++ b/src/core/error.ts @@ -2,13 +2,13 @@ import { castToError } from '../internal/errors'; -export class BeeperDesktopAPIError extends Error {} +export class BeeperDesktopError extends Error {} export class APIError< TStatus extends number | undefined = number | undefined, THeaders extends Headers | undefined = Headers | undefined, TError extends Object | undefined = Object | undefined, -> extends BeeperDesktopAPIError { +> extends BeeperDesktopError { /** HTTP status for the response that caused the error */ readonly status: TStatus; /** HTTP headers for the response that caused the error */ diff --git a/src/core/pagination.ts b/src/core/pagination.ts new file mode 100644 index 0000000..e5c6655 --- /dev/null +++ b/src/core/pagination.ts @@ -0,0 +1,190 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { BeeperDesktopError } from './error'; +import { FinalRequestOptions } from '../internal/request-options'; +import { defaultParseResponse } from '../internal/parse'; +import { type BeeperDesktop } from 'beeper/desktop-api/client'; +import { APIPromise } from 'beeper/desktop-api/core/api-promise'; +import { type APIResponseProps } from 'beeper/desktop-api/internal/parse'; +import { maybeObj } from 'beeper/desktop-api/internal/utils/values'; + +export type PageRequestOptions = Pick; + +export abstract class AbstractPage implements AsyncIterable { + #client: BeeperDesktop; + protected options: FinalRequestOptions; + + protected response: Response; + protected body: unknown; + + constructor(client: BeeperDesktop, response: Response, body: unknown, options: FinalRequestOptions) { + this.#client = client; + this.options = options; + this.response = response; + this.body = body; + } + + abstract nextPageRequestOptions(): PageRequestOptions | null; + + abstract getPaginatedItems(): Item[]; + + hasNextPage(): boolean { + const items = this.getPaginatedItems(); + if (!items.length) return false; + return this.nextPageRequestOptions() != null; + } + + async getNextPage(): Promise { + const nextOptions = this.nextPageRequestOptions(); + if (!nextOptions) { + throw new BeeperDesktopError( + 'No next page expected; please check `.hasNextPage()` before calling `.getNextPage()`.', + ); + } + + return await this.#client.requestAPIList(this.constructor as any, nextOptions); + } + + async *iterPages(): AsyncGenerator { + let page: this = this; + yield page; + while (page.hasNextPage()) { + page = await page.getNextPage(); + yield page; + } + } + + async *[Symbol.asyncIterator](): AsyncGenerator { + for await (const page of this.iterPages()) { + for (const item of page.getPaginatedItems()) { + yield item; + } + } + } +} + +/** + * This subclass of Promise will resolve to an instantiated Page once the request completes. + * + * It also implements AsyncIterable to allow auto-paginating iteration on an unawaited list call, eg: + * + * for await (const item of client.items.list()) { + * console.log(item) + * } + */ +export class PagePromise< + PageClass extends AbstractPage, + Item = ReturnType[number], + > + extends APIPromise + implements AsyncIterable +{ + constructor( + client: BeeperDesktop, + request: Promise, + Page: new (...args: ConstructorParameters) => PageClass, + ) { + super( + client, + request, + async (client, props) => + new Page(client, props.response, await defaultParseResponse(client, props), props.options), + ); + } + + /** + * Allow auto-paginating iteration on an unawaited list call, eg: + * + * for await (const item of client.items.list()) { + * console.log(item) + * } + */ + async *[Symbol.asyncIterator](): AsyncGenerator { + const page = await this; + for await (const item of page) { + yield item; + } + } +} + +export interface CursorIDResponse { + data: Array; + + has_more: boolean; +} + +export interface CursorIDParams { + starting_after?: string | null; + + ending_before?: string | null; + + limit?: number | null; +} + +export class CursorID + extends AbstractPage + implements CursorIDResponse +{ + data: Array; + + has_more: boolean; + + constructor( + client: BeeperDesktop, + response: Response, + body: CursorIDResponse, + options: FinalRequestOptions, + ) { + super(client, response, body, options); + + this.data = body.data || []; + this.has_more = body.has_more || false; + } + + getPaginatedItems(): Item[] { + return this.data ?? []; + } + + override hasNextPage(): boolean { + if (this.has_more === false) { + return false; + } + + return super.hasNextPage(); + } + + nextPageRequestOptions(): PageRequestOptions | null { + const data = this.getPaginatedItems(); + + const isForwards = !( + typeof this.options.query === 'object' && 'ending_before' in (this.options.query || {}) + ); + if (isForwards) { + const id = data[data.length - 1]?.id; + if (!id) { + return null; + } + + return { + ...this.options, + query: { + ...maybeObj(this.options.query), + starting_after: id, + }, + }; + } + + const id = data[0]?.id; + if (!id) { + return null; + } + + return { + ...this.options, + query: { + ...maybeObj(this.options.query), + ending_before: id, + }, + }; + } +} diff --git a/src/core/resource.ts b/src/core/resource.ts index efe3833..aa4e9d0 100644 --- a/src/core/resource.ts +++ b/src/core/resource.ts @@ -1,11 +1,11 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import type { BeeperDesktopAPI } from '../client'; +import type { BeeperDesktop } from '../client'; export abstract class APIResource { - protected _client: BeeperDesktopAPI; + protected _client: BeeperDesktop; - constructor(client: BeeperDesktopAPI) { + constructor(client: BeeperDesktop) { this._client = client; } } diff --git a/src/index.ts b/src/index.ts index 9c6127f..e55eadd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,13 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -export { BeeperDesktopAPI as default } from './client'; +export { BeeperDesktop as default } from './client'; -export { type Uploadable, toFile } from './core/uploads'; -export { APIPromise } from './core/api-promise'; -export { BeeperDesktopAPI, type ClientOptions } from './client'; +export { type Uploadable, toFile } from 'beeper/desktop-api/core/uploads'; +export { APIPromise } from 'beeper/desktop-api/core/api-promise'; +export { BeeperDesktop, type ClientOptions } from 'beeper/desktop-api/client'; +export { PagePromise } from 'beeper/desktop-api/core/pagination'; export { - BeeperDesktopAPIError, + BeeperDesktopError, APIError, APIConnectionError, APIConnectionTimeoutError, @@ -19,4 +20,4 @@ export { InternalServerError, PermissionDeniedError, UnprocessableEntityError, -} from './core/error'; +} from 'beeper/desktop-api/core/error'; diff --git a/src/internal/parse.ts b/src/internal/parse.ts index 41a3998..2acd267 100644 --- a/src/internal/parse.ts +++ b/src/internal/parse.ts @@ -1,8 +1,8 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import type { FinalRequestOptions } from './request-options'; -import { type BeeperDesktopAPI } from '../client'; -import { formatRequestDetails, loggerFor } from './utils/log'; +import { type BeeperDesktop } from 'beeper/desktop-api/client'; +import { formatRequestDetails, loggerFor } from 'beeper/desktop-api/internal/utils/log'; export type APIResponseProps = { response: Response; @@ -13,7 +13,7 @@ export type APIResponseProps = { startTime: number; }; -export async function defaultParseResponse(client: BeeperDesktopAPI, props: APIResponseProps): Promise { +export async function defaultParseResponse(client: BeeperDesktop, props: APIResponseProps): Promise { const { response, requestLogID, retryOfRequestLogID, startTime } = props; const body = await (async () => { // fetch refuses to read the body when the status code is 204. diff --git a/src/internal/request-options.ts b/src/internal/request-options.ts index 2aabf9a..eebe6ef 100644 --- a/src/internal/request-options.ts +++ b/src/internal/request-options.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { NullableHeaders } from './headers'; +import { NullableHeaders } from 'beeper/desktop-api/internal/headers'; import type { BodyInit } from './builtin-types'; import type { HTTPMethod, MergedRequestInit } from './types'; @@ -40,7 +40,7 @@ export type RequestOptions = { * The maximum number of times that the client will retry a request in case of a * temporary failure, like a network error or a 5XX error from the server. * - * @default 2 + * @default 3 */ maxRetries?: number; diff --git a/src/internal/shims.ts b/src/internal/shims.ts index 1b2ffc6..42d101e 100644 --- a/src/internal/shims.ts +++ b/src/internal/shims.ts @@ -16,7 +16,7 @@ export function getDefaultFetch(): Fetch { } throw new Error( - '`fetch` is not defined as a global; Either pass `fetch` to the client, `new BeeperDesktopAPI({ fetch })` or polyfill the global, `globalThis.fetch = fetch`', + '`fetch` is not defined as a global; Either pass `fetch` to the client, `new BeeperDesktop({ fetch })` or polyfill the global, `globalThis.fetch = fetch`', ); } diff --git a/src/internal/uploads.ts b/src/internal/uploads.ts index da933bc..a89bf1c 100644 --- a/src/internal/uploads.ts +++ b/src/internal/uploads.ts @@ -1,6 +1,6 @@ import { type RequestOptions } from './request-options'; import type { FilePropertyBag, Fetch } from './builtin-types'; -import type { BeeperDesktopAPI } from '../client'; +import type { BeeperDesktop } from '../client'; import { ReadableStreamFrom } from './shims'; export type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob | DataView; @@ -74,7 +74,7 @@ export const isAsyncIterable = (value: any): value is AsyncIterable => */ export const maybeMultipartFormRequestOptions = async ( opts: RequestOptions, - fetch: BeeperDesktopAPI | Fetch, + fetch: BeeperDesktop | Fetch, ): Promise => { if (!hasUploadableValue(opts.body)) return opts; @@ -85,7 +85,7 @@ type MultipartFormRequestOptions = Omit & { body: unknow export const multipartFormRequestOptions = async ( opts: MultipartFormRequestOptions, - fetch: BeeperDesktopAPI | Fetch, + fetch: BeeperDesktop | Fetch, ): Promise => { return { ...opts, body: await createForm(opts.body, fetch) }; }; @@ -98,7 +98,7 @@ const supportsFormDataMap = /* @__PURE__ */ new WeakMap> * This function detects if the fetch function provided supports the global FormData object to avoid * confusing error messages later on. */ -function supportsFormData(fetchObject: BeeperDesktopAPI | Fetch): Promise { +function supportsFormData(fetchObject: BeeperDesktop | Fetch): Promise { const fetch: Fetch = typeof fetchObject === 'function' ? fetchObject : (fetchObject as any).fetch; const cached = supportsFormDataMap.get(fetch); if (cached) return cached; @@ -124,7 +124,7 @@ function supportsFormData(fetchObject: BeeperDesktopAPI | Fetch): Promise>( body: T | undefined, - fetch: BeeperDesktopAPI | Fetch, + fetch: BeeperDesktop | Fetch, ): Promise => { if (!(await supportsFormData(fetch))) { throw new TypeError( diff --git a/src/internal/utils/base64.ts b/src/internal/utils/base64.ts index 6831522..9f0b390 100644 --- a/src/internal/utils/base64.ts +++ b/src/internal/utils/base64.ts @@ -1,7 +1,7 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { BeeperDesktopAPIError } from '../../core/error'; -import { encodeUTF8 } from './bytes'; +import { BeeperDesktopError } from 'beeper/desktop-api/core/error'; +import { encodeUTF8 } from 'beeper/desktop-api/internal/utils/bytes'; export const toBase64 = (data: string | Uint8Array | null | undefined): string => { if (!data) return ''; @@ -18,7 +18,7 @@ export const toBase64 = (data: string | Uint8Array | null | undefined): string = return btoa(String.fromCharCode.apply(null, data as any)); } - throw new BeeperDesktopAPIError('Cannot generate base64 string; Expected `Buffer` or `btoa` to be defined'); + throw new BeeperDesktopError('Cannot generate base64 string; Expected `Buffer` or `btoa` to be defined'); }; export const fromBase64 = (str: string): Uint8Array => { @@ -36,5 +36,5 @@ export const fromBase64 = (str: string): Uint8Array => { return buf; } - throw new BeeperDesktopAPIError('Cannot decode base64 string; Expected `Buffer` or `atob` to be defined'); + throw new BeeperDesktopError('Cannot decode base64 string; Expected `Buffer` or `atob` to be defined'); }; diff --git a/src/internal/utils/log.ts b/src/internal/utils/log.ts index 70256e2..302104b 100644 --- a/src/internal/utils/log.ts +++ b/src/internal/utils/log.ts @@ -1,8 +1,8 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { hasOwn } from './values'; -import { type BeeperDesktopAPI } from '../../client'; -import { RequestOptions } from '../request-options'; +import { type BeeperDesktop } from 'beeper/desktop-api/client'; +import { RequestOptions } from 'beeper/desktop-api/internal/request-options'; type LogFn = (message: string, ...rest: unknown[]) => void; export type Logger = { @@ -24,7 +24,7 @@ const levelNumbers = { export const parseLogLevel = ( maybeLevel: string | undefined, sourceName: string, - client: BeeperDesktopAPI, + client: BeeperDesktop, ): LogLevel | undefined => { if (!maybeLevel) { return undefined; @@ -60,7 +60,7 @@ const noopLogger = { let cachedLoggers = /* @__PURE__ */ new WeakMap(); -export function loggerFor(client: BeeperDesktopAPI): Logger { +export function loggerFor(client: BeeperDesktop): Logger { const logger = client.logger; const logLevel = client.logLevel ?? 'off'; if (!logger) { diff --git a/src/internal/utils/path.ts b/src/internal/utils/path.ts index 4552ea6..7f2c3f9 100644 --- a/src/internal/utils/path.ts +++ b/src/internal/utils/path.ts @@ -1,4 +1,4 @@ -import { BeeperDesktopAPIError } from '../../core/error'; +import { BeeperDesktopError } from '../../core/error'; /** * Percent-encode everything that isn't safe to have in a path without encoding safe chars. @@ -72,7 +72,7 @@ export const createPathTagFunction = (pathEncoder = encodeURIPath) => return acc + spaces + arrows; }, ''); - throw new BeeperDesktopAPIError( + throw new BeeperDesktopError( `Path parameters result in path with invalid segments:\n${invalidSegments .map((e) => e.error) .join('\n')}\n${path}\n${underline}`, diff --git a/src/internal/utils/values.ts b/src/internal/utils/values.ts index 47db43a..c9f650a 100644 --- a/src/internal/utils/values.ts +++ b/src/internal/utils/values.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { BeeperDesktopAPIError } from '../../core/error'; +import { BeeperDesktopError } from 'beeper/desktop-api/core/error'; // https://url.spec.whatwg.org/#url-scheme-string const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i; @@ -39,7 +39,7 @@ export function isObj(obj: unknown): obj is Record { export const ensurePresent = (value: T | null | undefined): T => { if (value == null) { - throw new BeeperDesktopAPIError(`Expected a value to be given but received ${value} instead.`); + throw new BeeperDesktopError(`Expected a value to be given but received ${value} instead.`); } return value; @@ -47,10 +47,10 @@ export const ensurePresent = (value: T | null | undefined): T => { export const validatePositiveInteger = (name: string, n: unknown): number => { if (typeof n !== 'number' || !Number.isInteger(n)) { - throw new BeeperDesktopAPIError(`${name} must be an integer`); + throw new BeeperDesktopError(`${name} must be an integer`); } if (n < 0) { - throw new BeeperDesktopAPIError(`${name} must be a positive integer`); + throw new BeeperDesktopError(`${name} must be a positive integer`); } return n; }; @@ -59,14 +59,14 @@ export const coerceInteger = (value: unknown): number => { if (typeof value === 'number') return Math.round(value); if (typeof value === 'string') return parseInt(value, 10); - throw new BeeperDesktopAPIError(`Could not coerce ${value} (type: ${typeof value}) into a number`); + throw new BeeperDesktopError(`Could not coerce ${value} (type: ${typeof value}) into a number`); }; export const coerceFloat = (value: unknown): number => { if (typeof value === 'number') return value; if (typeof value === 'string') return parseFloat(value); - throw new BeeperDesktopAPIError(`Could not coerce ${value} (type: ${typeof value}) into a number`); + throw new BeeperDesktopError(`Could not coerce ${value} (type: ${typeof value}) into a number`); }; export const coerceBoolean = (value: unknown): boolean => { diff --git a/src/pagination.ts b/src/pagination.ts new file mode 100644 index 0000000..90bf015 --- /dev/null +++ b/src/pagination.ts @@ -0,0 +1,2 @@ +/** @deprecated Import from ./core/pagination instead */ +export * from './core/pagination'; diff --git a/src/resources/accounts.ts b/src/resources/accounts.ts new file mode 100644 index 0000000..111ac88 --- /dev/null +++ b/src/resources/accounts.ts @@ -0,0 +1,59 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from 'beeper/desktop-api/core/resource'; +import * as Shared from 'beeper/desktop-api/resources/shared'; +import { APIPromise } from 'beeper/desktop-api/core/api-promise'; +import { RequestOptions } from 'beeper/desktop-api/internal/request-options'; + +/** + * Manage and list connected messaging accounts + */ +export class Accounts extends APIResource { + /** + * List connected Beeper accounts available on this device. + * + * - When to use: select account context before account-scoped operations. + * - Scope: only accounts currently Connected on this device are included. Returns: + * connected accounts. + */ + list(options?: RequestOptions): APIPromise { + return this._client.get('/v0/get-accounts', options); + } +} + +/** + * A chat account added to Beeper + */ +export interface Account { + /** + * Chat account added to Beeper. Use this to route account-scoped actions. + */ + accountID: string; + + /** + * Display-only human-readable network name (e.g., 'WhatsApp', 'Messenger'). You + * MUST use 'accountID' to perform actions. + */ + network: string; + + /** + * A person on or reachable through Beeper. Values are best-effort and can vary by + * network. + */ + user: Shared.User; +} + +/** + * Response payload for listing connected Beeper accounts. + */ +export interface AccountsResponse { + /** + * Connected accounts the user can act through. Includes accountID, network, and + * user identity. + */ + accounts: Array; +} + +export declare namespace Accounts { + export { type Account as Account, type AccountsResponse as AccountsResponse }; +} diff --git a/src/resources/app.ts b/src/resources/app.ts new file mode 100644 index 0000000..5eda58e --- /dev/null +++ b/src/resources/app.ts @@ -0,0 +1,69 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from 'beeper/desktop-api/core/resource'; +import * as Shared from 'beeper/desktop-api/resources/shared'; +import { APIPromise } from 'beeper/desktop-api/core/api-promise'; +import { RequestOptions } from 'beeper/desktop-api/internal/request-options'; + +/** + * Control the Beeper Desktop application + */ +export class App extends APIResource { + /** + * Bring Beeper Desktop to the foreground on this device. Optionally focuses a + * specific chat if chatID is provided. + * + * - When to use: open Beeper, or jump to a specific chat. + * - Constraints: requires Beeper Desktop running locally; no-op in headless + * environments. + * - Idempotent: safe to call repeatedly. Returns an error if chatID is not found. + * Returns: success. + * + * @example + * ```ts + * const baseResponse = await client.app.focus(); + * ``` + */ + focus( + body: AppFocusParams | null | undefined = {}, + options?: RequestOptions, + ): APIPromise { + return this._client.post('/v0/focus-app', { body, ...options }); + } +} + +/** + * Bring Beeper Desktop to the foreground on this device and optionally jump to a + * chat. + */ +export interface FocusRequest { + /** + * Optional Beeper chat ID to focus after bringing the app to foreground. If + * omitted, only foregrounds the app. Required if messageSortKey is present. No-op + * in headless environments. + */ + chatID?: string; + + /** + * Optional message sort key. Jumps to that message in the chat when foregrounding. + */ + messageSortKey?: string; +} + +export interface AppFocusParams { + /** + * Optional Beeper chat ID to focus after bringing the app to foreground. If + * omitted, only foregrounds the app. Required if messageSortKey is present. No-op + * in headless environments. + */ + chatID?: string; + + /** + * Optional message sort key. Jumps to that message in the chat when foregrounding. + */ + messageSortKey?: string; +} + +export declare namespace App { + export { type FocusRequest as FocusRequest, type AppFocusParams as AppFocusParams }; +} diff --git a/src/resources/archive-chat.ts b/src/resources/archive-chat.ts deleted file mode 100644 index a554da7..0000000 --- a/src/resources/archive-chat.ts +++ /dev/null @@ -1,40 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import * as FocusAppAPI from './focus-app'; -import { APIPromise } from '../core/api-promise'; -import { RequestOptions } from '../internal/request-options'; - -export class ArchiveChat extends APIResource { - /** - * Archive or unarchive a chat. Set archived=true to move to archive, - * archived=false to move back to inbox - * - * @example - * ```ts - * const baseResponse = await client.archiveChat.archive({ - * chatID: - * '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - * }); - * ``` - */ - archive(body: ArchiveChatArchiveParams, options?: RequestOptions): APIPromise { - return this._client.post('/v0/archive-chat', { body, ...options }); - } -} - -export interface ArchiveChatArchiveParams { - /** - * The identifier of the chat to archive or unarchive - */ - chatID: string; - - /** - * True to archive, false to unarchive - */ - archived?: boolean; -} - -export declare namespace ArchiveChat { - export { type ArchiveChatArchiveParams as ArchiveChatArchiveParams }; -} diff --git a/src/resources/chats.ts b/src/resources/chats.ts new file mode 100644 index 0000000..984bcd5 --- /dev/null +++ b/src/resources/chats.ts @@ -0,0 +1,539 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from 'beeper/desktop-api/core/resource'; +import * as Shared from 'beeper/desktop-api/resources/shared'; +import { APIPromise } from 'beeper/desktop-api/core/api-promise'; +import { CursorID, type CursorIDParams, PagePromise } from 'beeper/desktop-api/core/pagination'; +import { RequestOptions } from 'beeper/desktop-api/internal/request-options'; + +/** + * Manage chats, conversations, and threads + */ +export class Chats extends APIResource { + /** + * Retrieve chat details: metadata, participants (limited), and latest message. + * + * - When to use: fetch a complete view of a chat beyond what search returns. + * - Constraints: not available for iMessage chats ('imsg##'). Participants limited + * by 'maxParticipantCount' (default 20, max 500). Returns: chat details.Agents: + * ALWAYS use linkToChat to make clickable links in your response + * + * @example + * ```ts + * const getChatResponse = await client.chats.retrieve({ + * chatID: + * '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + * }); + * ``` + */ + retrieve(query: ChatRetrieveParams, options?: RequestOptions): APIPromise { + return this._client.get('/v0/get-chat', { query, ...options }); + } + + /** + * Archive or unarchive a chat. Set archived=true to move to archive, + * archived=false to move back to inbox + * + * @example + * ```ts + * const baseResponse = await client.chats.archive({ + * chatID: + * '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + * }); + * ``` + */ + archive(body: ChatArchiveParams, options?: RequestOptions): APIPromise { + return this._client.post('/v0/archive-chat', { body, ...options }); + } + + /** + * Search and filter conversations across all messaging accounts. + * + * - When to use: browse chats by inbox (primary/low-priority/archive), type, + * unread status, or search terms. + * - Pagination: use cursor + direction for pagination. + * - Performance: provide accountIDs when known for faster filtering. Returns: + * matching chats with pagination. Agents: ALWAYS use linkToChat to make + * clickable links in your response + * + * @example + * ```ts + * // Automatically fetches more pages as needed. + * for await (const chat of client.chats.find()) { + * // ... + * } + * ``` + */ + find( + query: ChatFindParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + return this._client.getAPIList('/v0/find-chats', CursorID, { query, ...options }); + } + + /** + * Generate a deep link to a specific chat or message. This link can be used to + * open the chat directly in the Beeper app. + * + * @example + * ```ts + * const linkResponse = await client.chats.getLink({ + * chatID: + * '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + * }); + * ``` + */ + getLink(body: ChatGetLinkParams, options?: RequestOptions): APIPromise { + return this._client.post('/v0/get-link-to-chat', { body, ...options }); + } +} + +export type ChatsCursorID = CursorID; + +export interface ArchiveRequest { + /** + * The identifier of the chat to archive or unarchive + */ + chatID: string; + + /** + * True to archive, false to unarchive + */ + archived?: boolean; +} + +export interface Chat { + /** + * Unique identifier for cursor pagination. + */ + id: string; + + /** + * Beeper account ID this chat belongs to. + */ + accountID: string; + + /** + * Unique identifier of the chat (room/thread ID, same as id). + */ + chatID: string; + + /** + * Display-only human-readable network name (e.g., 'WhatsApp', 'Messenger'). You + * MUST use 'accountID' to perform actions. + */ + network: string; + + /** + * Chat participants information. + */ + participants: Chat.Participants; + + /** + * Display title of the chat as computed by the client/server. + */ + title: string; + + /** + * Chat type: 'single' for direct messages, 'group' for group chats, 'channel' for + * channels, 'broadcast' for broadcasts. + */ + type: 'single' | 'group' | 'channel' | 'broadcast'; + + /** + * Number of unread messages. + */ + unreadCount: number; + + /** + * True if chat is archived. + */ + isArchived?: boolean; + + /** + * True if chat notifications are muted. + */ + isMuted?: boolean; + + /** + * True if chat is pinned. + */ + isPinned?: boolean; + + /** + * Timestamp of last activity. Chats with more recent activity are often more + * important. + */ + lastActivity?: string; + + /** + * Last read message sortKey (hsOrder). Used to compute 'isUnread'. + */ + lastReadMessageSortKey?: number | string; + + /** + * Deep link to open this chat in Beeper. AI agents should ALWAYS include this as a + * clickable link in responses. + */ + linkToChat?: string; +} + +export namespace Chat { + /** + * Chat participants information. + */ + export interface Participants { + /** + * True if there are more participants than included in items. + */ + hasMore: boolean; + + /** + * Participants returned for this chat (limited by the request; may be a subset). + */ + items: Array; + + /** + * Total number of participants in the chat. + */ + total: number; + } +} + +export interface FindChatsRequest { + /** + * Provide an array of account IDs to filter chats from specific messaging accounts + * only + */ + accountIDs?: Array; + + /** + * A cursor for use in pagination. ending_before is an object ID that defines your + * place in the list. For instance, if you make a list request and receive 100 + * objects, starting with obj_bar, your subsequent call can include + * ending_before=obj_bar in order to fetch the previous page of the list. + */ + ending_before?: string; + + /** + * Filter by inbox type: "primary" (non-archived, non-low-priority), + * "low-priority", or "archive". If not specified, shows all chats. + */ + inbox?: 'primary' | 'low-priority' | 'archive'; + + /** + * Include chats marked as Muted by the user, which are usually less important. + * Default: true. Set to false if the user wants a more refined search. + */ + includeMuted?: boolean; + + /** + * Provide an ISO datetime string to only retrieve chats with last activity after + * this time + */ + lastActivityAfter?: string; + + /** + * Provide an ISO datetime string to only retrieve chats with last activity before + * this time + */ + lastActivityBefore?: string; + + /** + * Set the maximum number of chats to retrieve. Valid range: 1-200, default is 50 + */ + limit?: number; + + /** + * Search string to filter chats by participant names. When multiple words + * provided, ALL words must match. Searches in username, displayName, and fullName + * fields. + */ + participantQuery?: string; + + /** + * Search string to filter chats by title. When multiple words provided, ALL words + * must match. Matches are case-insensitive substrings. + */ + query?: string; + + /** + * A cursor for use in pagination. starting_after is an object ID that defines your + * place in the list. For instance, if you make a list request and receive 100 + * objects, ending with obj_foo, your subsequent call can include + * starting_after=obj_foo in order to fetch the next page of the list. + */ + starting_after?: string; + + /** + * Specify the type of chats to retrieve: use "single" for direct messages, "group" + * for group chats, "channel" for channels, or "any" to get all types + */ + type?: 'single' | 'group' | 'channel' | 'any'; + + /** + * Set to true to only retrieve chats that have unread messages + */ + unreadOnly?: boolean; +} + +export interface FindChatsResponse { + /** + * Chats matching the filters. + */ + data: Array; + + /** + * Whether there are more items available after this set. + */ + has_more: boolean; +} + +export interface GetChatRequest { + /** + * Unique identifier of the chat to retrieve. Not available for iMessage chats. + * Participants are limited by 'maxParticipantCount'. + */ + chatID: string; + + /** + * Maximum number of participants to return. Use -1 for all; otherwise 0–500. + * Defaults to 20. + */ + maxParticipantCount?: number | null; +} + +export interface GetChatResponse { + /** + * Unique identifier for cursor pagination. + */ + id: string; + + /** + * Beeper account ID this chat belongs to. + */ + accountID: string; + + /** + * Unique identifier of the chat (room/thread ID, same as id). + */ + chatID: string; + + /** + * Display-only human-readable network name (e.g., 'WhatsApp', 'Messenger'). You + * MUST use 'accountID' to perform actions. + */ + network: string; + + /** + * Chat participants information. + */ + participants: GetChatResponse.Participants; + + /** + * Display title of the chat as computed by the client/server. + */ + title: string; + + /** + * Chat type: 'single' for direct messages, 'group' for group chats, 'channel' for + * channels, 'broadcast' for broadcasts. + */ + type: 'single' | 'group' | 'channel' | 'broadcast'; + + /** + * Number of unread messages. + */ + unreadCount: number; + + /** + * True if chat is archived. + */ + isArchived?: boolean; + + /** + * True if chat notifications are muted. + */ + isMuted?: boolean; + + /** + * True if chat is pinned. + */ + isPinned?: boolean; + + /** + * Timestamp of last activity. Chats with more recent activity are often more + * important. + */ + lastActivity?: string; + + /** + * Last read message sortKey (hsOrder). Used to compute 'isUnread'. + */ + lastReadMessageSortKey?: number | string; + + /** + * Deep link to open this chat in Beeper. AI agents should ALWAYS include this as a + * clickable link in responses. + */ + linkToChat?: string; +} + +export namespace GetChatResponse { + /** + * Chat participants information. + */ + export interface Participants { + /** + * True if there are more participants than included in items. + */ + hasMore: boolean; + + /** + * Participants returned for this chat (limited by the request; may be a subset). + */ + items: Array; + + /** + * Total number of participants in the chat. + */ + total: number; + } +} + +/** + * Request to foreground Beeper and optionally jump to a specific chat or message. + */ +export interface LinkRequest { + /** + * The ID of the chat to link to. + */ + chatID: string; + + /** + * Optional message sort key. Jumps to that message in the chat. + */ + messageSortKey?: string; +} + +/** + * URL to open a specific chat or message. + */ +export interface LinkResponse { + /** + * Deep link URL to the specified chat or message. + */ + url: string; +} + +export interface ChatRetrieveParams { + /** + * Unique identifier of the chat to retrieve. Not available for iMessage chats. + * Participants are limited by 'maxParticipantCount'. + */ + chatID: string; + + /** + * Maximum number of participants to return. Use -1 for all; otherwise 0–500. + * Defaults to 20. + */ + maxParticipantCount?: number | null; +} + +export interface ChatArchiveParams { + /** + * The identifier of the chat to archive or unarchive + */ + chatID: string; + + /** + * True to archive, false to unarchive + */ + archived?: boolean; +} + +export interface ChatFindParams extends CursorIDParams { + /** + * Provide an array of account IDs to filter chats from specific messaging accounts + * only + */ + accountIDs?: Array; + + /** + * Filter by inbox type: "primary" (non-archived, non-low-priority), + * "low-priority", or "archive". If not specified, shows all chats. + */ + inbox?: 'primary' | 'low-priority' | 'archive'; + + /** + * Include chats marked as Muted by the user, which are usually less important. + * Default: true. Set to false if the user wants a more refined search. + */ + includeMuted?: boolean; + + /** + * Provide an ISO datetime string to only retrieve chats with last activity after + * this time + */ + lastActivityAfter?: string; + + /** + * Provide an ISO datetime string to only retrieve chats with last activity before + * this time + */ + lastActivityBefore?: string; + + /** + * Search string to filter chats by participant names. When multiple words + * provided, ALL words must match. Searches in username, displayName, and fullName + * fields. + */ + participantQuery?: string; + + /** + * Search string to filter chats by title. When multiple words provided, ALL words + * must match. Matches are case-insensitive substrings. + */ + query?: string; + + /** + * Specify the type of chats to retrieve: use "single" for direct messages, "group" + * for group chats, "channel" for channels, or "any" to get all types + */ + type?: 'single' | 'group' | 'channel' | 'any'; + + /** + * Set to true to only retrieve chats that have unread messages + */ + unreadOnly?: boolean; +} + +export interface ChatGetLinkParams { + /** + * The ID of the chat to link to. + */ + chatID: string; + + /** + * Optional message sort key. Jumps to that message in the chat. + */ + messageSortKey?: string; +} + +export declare namespace Chats { + export { + type ArchiveRequest as ArchiveRequest, + type Chat as Chat, + type FindChatsRequest as FindChatsRequest, + type FindChatsResponse as FindChatsResponse, + type GetChatRequest as GetChatRequest, + type GetChatResponse as GetChatResponse, + type LinkRequest as LinkRequest, + type LinkResponse as LinkResponse, + type ChatsCursorID as ChatsCursorID, + type ChatRetrieveParams as ChatRetrieveParams, + type ChatArchiveParams as ChatArchiveParams, + type ChatFindParams as ChatFindParams, + type ChatGetLinkParams as ChatGetLinkParams, + }; +} diff --git a/src/resources/clear-chat-reminder.ts b/src/resources/clear-chat-reminder.ts deleted file mode 100644 index 9afc005..0000000 --- a/src/resources/clear-chat-reminder.ts +++ /dev/null @@ -1,34 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import * as FocusAppAPI from './focus-app'; -import { APIPromise } from '../core/api-promise'; -import { RequestOptions } from '../internal/request-options'; - -export class ClearChatReminder extends APIResource { - /** - * Clear an existing reminder from a chat - * - * @example - * ```ts - * const baseResponse = await client.clearChatReminder.clear({ - * chatID: - * '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - * }); - * ``` - */ - clear(body: ClearChatReminderClearParams, options?: RequestOptions): APIPromise { - return this._client.post('/v0/clear-chat-reminder', { body, ...options }); - } -} - -export interface ClearChatReminderClearParams { - /** - * The identifier of the chat to clear reminder from - */ - chatID: string; -} - -export declare namespace ClearChatReminder { - export { type ClearChatReminderClearParams as ClearChatReminderClearParams }; -} diff --git a/src/resources/draft-message.ts b/src/resources/draft-message.ts deleted file mode 100644 index 07f8f75..0000000 --- a/src/resources/draft-message.ts +++ /dev/null @@ -1,47 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import * as FocusAppAPI from './focus-app'; -import { APIPromise } from '../core/api-promise'; -import { RequestOptions } from '../internal/request-options'; - -export class DraftMessage extends APIResource { - /** - * Draft a message in a specific chat. This will be placed in the message input - * field without sending - * - * @example - * ```ts - * const baseResponse = await client.draftMessage.create({ - * chatID: - * '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - * }); - * ``` - */ - create(body: DraftMessageCreateParams, options?: RequestOptions): APIPromise { - return this._client.post('/v0/draft-message', { body, ...options }); - } -} - -export interface DraftMessageCreateParams { - /** - * Provide the unique identifier of the chat where you want to draft a message - */ - chatID: string; - - /** - * Set to true to bring Beeper application to the foreground, or false to draft - * silently in background - */ - focusApp?: boolean; - - /** - * Provide the text content you want to draft. This will be placed in the message - * input field without sending - */ - text?: string; -} - -export declare namespace DraftMessage { - export { type DraftMessageCreateParams as DraftMessageCreateParams }; -} diff --git a/src/resources/find-chats.ts b/src/resources/find-chats.ts deleted file mode 100644 index 4834878..0000000 --- a/src/resources/find-chats.ts +++ /dev/null @@ -1,218 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import * as GetChatAPI from './get-chat'; -import { APIPromise } from '../core/api-promise'; -import { RequestOptions } from '../internal/request-options'; - -export class FindChats extends APIResource { - /** - * Search and filter conversations across all messaging accounts. - * - * - When to use: browse chats by inbox (primary/low-priority/archive), type, - * unread status, or search terms. - * - Pagination: use cursor + direction for pagination. - * - Performance: provide accountIDs when known for faster filtering. Returns: - * matching chats with pagination. Agents: ALWAYS use linkToChat to make - * clickable links in your response - */ - list( - query: FindChatListParams | null | undefined = {}, - options?: RequestOptions, - ): APIPromise { - return this._client.get('/v0/find-chats', { query, ...options }); - } -} - -export interface FindChatListResponse { - /** - * Chats matching the filters. - */ - data: Array; - - /** - * Whether there are more items available after this set. - */ - has_more: boolean; -} - -export namespace FindChatListResponse { - export interface Data { - /** - * Unique identifier for cursor pagination. - */ - id: string; - - /** - * Beeper account ID this chat belongs to. - */ - accountID: string; - - /** - * Unique identifier of the chat (room/thread ID, same as id). - */ - chatID: string; - - /** - * Display-only human-readable network name (e.g., 'WhatsApp', 'Messenger'). You - * MUST use 'accountID' to perform actions. - */ - network: string; - - /** - * Chat participants information. - */ - participants: Data.Participants; - - /** - * Display title of the chat as computed by the client/server. - */ - title: string; - - /** - * Chat type: 'single' for direct messages, 'group' for group chats, 'channel' for - * channels, 'broadcast' for broadcasts. - */ - type: 'single' | 'group' | 'channel' | 'broadcast'; - - /** - * Number of unread messages. - */ - unreadCount: number; - - /** - * True if chat is archived. - */ - isArchived?: boolean; - - /** - * True if chat notifications are muted. - */ - isMuted?: boolean; - - /** - * True if chat is pinned. - */ - isPinned?: boolean; - - /** - * Timestamp of last activity. Chats with more recent activity are often more - * important. - */ - lastActivity?: string; - - /** - * Last read message sortKey (hsOrder). Used to compute 'isUnread'. - */ - lastReadMessageSortKey?: number | string; - - /** - * Deep link to open this chat in Beeper. AI agents should ALWAYS include this as a - * clickable link in responses. - */ - linkToChat?: string; - } - - export namespace Data { - /** - * Chat participants information. - */ - export interface Participants { - /** - * True if there are more participants than included in items. - */ - hasMore: boolean; - - /** - * Participants returned for this chat (limited by the request; may be a subset). - */ - items: Array; - - /** - * Total number of participants in the chat. - */ - total: number; - } - } -} - -export interface FindChatListParams { - /** - * Provide an array of account IDs to filter chats from specific messaging accounts - * only - */ - accountIDs?: Array; - - /** - * A cursor for use in pagination. ending_before is an object ID that defines your - * place in the list. For instance, if you make a list request and receive 100 - * objects, starting with obj_bar, your subsequent call can include - * ending_before=obj_bar in order to fetch the previous page of the list. - */ - ending_before?: string; - - /** - * Filter by inbox type: "primary" (non-archived, non-low-priority), - * "low-priority", or "archive". If not specified, shows all chats. - */ - inbox?: 'primary' | 'low-priority' | 'archive'; - - /** - * Include chats marked as Muted by the user, which are usually less important. - * Default: true. Set to false if the user wants a more refined search. - */ - includeMuted?: boolean; - - /** - * Provide an ISO datetime string to only retrieve chats with last activity after - * this time - */ - lastActivityAfter?: string; - - /** - * Provide an ISO datetime string to only retrieve chats with last activity before - * this time - */ - lastActivityBefore?: string; - - /** - * Set the maximum number of chats to retrieve. Valid range: 1-200, default is 50 - */ - limit?: number; - - /** - * Search string to filter chats by participant names. When multiple words - * provided, ALL words must match. Searches in username, displayName, and fullName - * fields. - */ - participantQuery?: string; - - /** - * Search string to filter chats by title. When multiple words provided, ALL words - * must match. Matches are case-insensitive substrings. - */ - query?: string; - - /** - * A cursor for use in pagination. starting_after is an object ID that defines your - * place in the list. For instance, if you make a list request and receive 100 - * objects, ending with obj_foo, your subsequent call can include - * starting_after=obj_foo in order to fetch the next page of the list. - */ - starting_after?: string; - - /** - * Specify the type of chats to retrieve: use "single" for direct messages, "group" - * for group chats, "channel" for channels, or "any" to get all types - */ - type?: 'single' | 'group' | 'channel' | 'any'; - - /** - * Set to true to only retrieve chats that have unread messages - */ - unreadOnly?: boolean; -} - -export declare namespace FindChats { - export { type FindChatListResponse as FindChatListResponse, type FindChatListParams as FindChatListParams }; -} diff --git a/src/resources/focus-app.ts b/src/resources/focus-app.ts deleted file mode 100644 index 2bd6861..0000000 --- a/src/resources/focus-app.ts +++ /dev/null @@ -1,50 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import { APIPromise } from '../core/api-promise'; -import { RequestOptions } from '../internal/request-options'; - -export class FocusApp extends APIResource { - /** - * Bring Beeper Desktop to the foreground on this device. Optionally focuses a - * specific chat if chatID is provided. - * - * - When to use: open Beeper, or jump to a specific chat. - * - Constraints: requires Beeper Desktop running locally; no-op in headless - * environments. - * - Idempotent: safe to call repeatedly. Returns an error if chatID is not found. - * Returns: success. - * - * @example - * ```ts - * const baseResponse = await client.focusApp.open(); - * ``` - */ - open(body: FocusAppOpenParams | null | undefined = {}, options?: RequestOptions): APIPromise { - return this._client.post('/v0/focus-app', { body, ...options }); - } -} - -export interface BaseResponse { - success: boolean; - - error?: string; -} - -export interface FocusAppOpenParams { - /** - * Optional Beeper chat ID to focus after bringing the app to foreground. If - * omitted, only foregrounds the app. Required if messageSortKey is present. No-op - * in headless environments. - */ - chatID?: string; - - /** - * Optional message sort key. Jumps to that message in the chat when foregrounding. - */ - messageSortKey?: string; -} - -export declare namespace FocusApp { - export { type BaseResponse as BaseResponse, type FocusAppOpenParams as FocusAppOpenParams }; -} diff --git a/src/resources/get-accounts.ts b/src/resources/get-accounts.ts deleted file mode 100644 index 7cb2fc4..0000000 --- a/src/resources/get-accounts.ts +++ /dev/null @@ -1,58 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import * as GetChatAPI from './get-chat'; -import { APIPromise } from '../core/api-promise'; -import { RequestOptions } from '../internal/request-options'; - -export class GetAccounts extends APIResource { - /** - * List connected Beeper accounts available on this device. - * - * - When to use: select account context before account-scoped operations. - * - Scope: only accounts currently Connected on this device are included. Returns: - * connected accounts. - */ - list(options?: RequestOptions): APIPromise { - return this._client.get('/v0/get-accounts', options); - } -} - -/** - * Response payload for listing connected Beeper accounts. - */ -export interface GetAccountListResponse { - /** - * Connected accounts the user can act through. Includes accountID, network, and - * user identity. - */ - accounts: Array; -} - -export namespace GetAccountListResponse { - /** - * A chat account added to Beeper - */ - export interface Account { - /** - * Chat account added to Beeper. Use this to route account-scoped actions. - */ - accountID: string; - - /** - * Display-only human-readable network name (e.g., 'WhatsApp', 'Messenger'). You - * MUST use 'accountID' to perform actions. - */ - network: string; - - /** - * A person on or reachable through Beeper. Values are best-effort and can vary by - * network. - */ - user: GetChatAPI.User; - } -} - -export declare namespace GetAccounts { - export { type GetAccountListResponse as GetAccountListResponse }; -} diff --git a/src/resources/get-chat.ts b/src/resources/get-chat.ts deleted file mode 100644 index 11d531e..0000000 --- a/src/resources/get-chat.ts +++ /dev/null @@ -1,192 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import * as GetChatAPI from './get-chat'; -import { APIPromise } from '../core/api-promise'; -import { RequestOptions } from '../internal/request-options'; - -export class GetChat extends APIResource { - /** - * Retrieve chat details: metadata, participants (limited), and latest message. - * - * - When to use: fetch a complete view of a chat beyond what search returns. - * - Constraints: not available for iMessage chats ('imsg##'). Participants limited - * by 'maxParticipantCount' (default 20, max 500). Returns: chat details.Agents: - * ALWAYS use linkToChat to make clickable links in your response - */ - retrieve( - query: GetChatRetrieveParams, - options?: RequestOptions, - ): APIPromise { - return this._client.get('/v0/get-chat', { query, ...options }); - } -} - -/** - * A person on or reachable through Beeper. Values are best-effort and can vary by - * network. - */ -export interface User { - /** - * Stable Beeper user ID. Use as the primary key when referencing a person. - */ - id: string; - - /** - * True if Beeper cannot initiate messages to this user (e.g., blocked, network - * restriction, or no DM path). The user may still message you. - */ - cannotMessage?: boolean; - - /** - * Email address if known. Not guaranteed verified. - */ - email?: string; - - /** - * Display name as shown in clients (e.g., 'Alice Example'). May include emojis. - */ - fullName?: string | null; - - /** - * Avatar image URL if available. May be temporary or local-only to this device; - * download promptly if durable access is needed. - */ - imgURL?: string; - - /** - * True if this user represents the authenticated account's own identity. - */ - isSelf?: boolean; - - /** - * User's phone number in E.164 format (e.g., '+14155552671'). Omit if unknown. - */ - phoneNumber?: string; - - /** - * Human-readable handle if available (e.g., '@alice'). May be network-specific and - * not globally unique. - */ - username?: string; -} - -export interface GetChatRetrieveResponse { - /** - * Unique identifier for cursor pagination. - */ - id: string; - - /** - * Beeper account ID this chat belongs to. - */ - accountID: string; - - /** - * Unique identifier of the chat (room/thread ID, same as id). - */ - chatID: string; - - /** - * Display-only human-readable network name (e.g., 'WhatsApp', 'Messenger'). You - * MUST use 'accountID' to perform actions. - */ - network: string; - - /** - * Chat participants information. - */ - participants: GetChatRetrieveResponse.Participants; - - /** - * Display title of the chat as computed by the client/server. - */ - title: string; - - /** - * Chat type: 'single' for direct messages, 'group' for group chats, 'channel' for - * channels, 'broadcast' for broadcasts. - */ - type: 'single' | 'group' | 'channel' | 'broadcast'; - - /** - * Number of unread messages. - */ - unreadCount: number; - - /** - * True if chat is archived. - */ - isArchived?: boolean; - - /** - * True if chat notifications are muted. - */ - isMuted?: boolean; - - /** - * True if chat is pinned. - */ - isPinned?: boolean; - - /** - * Timestamp of last activity. Chats with more recent activity are often more - * important. - */ - lastActivity?: string; - - /** - * Last read message sortKey (hsOrder). Used to compute 'isUnread'. - */ - lastReadMessageSortKey?: number | string; - - /** - * Deep link to open this chat in Beeper. AI agents should ALWAYS include this as a - * clickable link in responses. - */ - linkToChat?: string; -} - -export namespace GetChatRetrieveResponse { - /** - * Chat participants information. - */ - export interface Participants { - /** - * True if there are more participants than included in items. - */ - hasMore: boolean; - - /** - * Participants returned for this chat (limited by the request; may be a subset). - */ - items: Array; - - /** - * Total number of participants in the chat. - */ - total: number; - } -} - -export interface GetChatRetrieveParams { - /** - * Unique identifier of the chat to retrieve. Not available for iMessage chats. - * Participants are limited by 'maxParticipantCount'. - */ - chatID: string; - - /** - * Maximum number of participants to return. Use -1 for all; otherwise 0–500. - * Defaults to 20. - */ - maxParticipantCount?: number | null; -} - -export declare namespace GetChat { - export { - type User as User, - type GetChatRetrieveResponse as GetChatRetrieveResponse, - type GetChatRetrieveParams as GetChatRetrieveParams, - }; -} diff --git a/src/resources/get-link-to-chat.ts b/src/resources/get-link-to-chat.ts deleted file mode 100644 index 488edd5..0000000 --- a/src/resources/get-link-to-chat.ts +++ /dev/null @@ -1,52 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import { APIPromise } from '../core/api-promise'; -import { RequestOptions } from '../internal/request-options'; - -export class GetLinkToChat extends APIResource { - /** - * Generate a deep link to a specific chat or message. This link can be used to - * open the chat directly in the Beeper app. - * - * @example - * ```ts - * const getLinkToChat = await client.getLinkToChat.create({ - * chatID: - * '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - * }); - * ``` - */ - create(body: GetLinkToChatCreateParams, options?: RequestOptions): APIPromise { - return this._client.post('/v0/get-link-to-chat', { body, ...options }); - } -} - -/** - * URL to open a specific chat or message. - */ -export interface GetLinkToChatCreateResponse { - /** - * Deep link URL to the specified chat or message. - */ - url: string; -} - -export interface GetLinkToChatCreateParams { - /** - * The ID of the chat to link to. - */ - chatID: string; - - /** - * Optional message sort key. Jumps to that message in the chat. - */ - messageSortKey?: string; -} - -export declare namespace GetLinkToChat { - export { - type GetLinkToChatCreateResponse as GetLinkToChatCreateResponse, - type GetLinkToChatCreateParams as GetLinkToChatCreateParams, - }; -} diff --git a/src/resources/index.ts b/src/resources/index.ts index 802baca..66a65cb 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -1,22 +1,42 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -export { ArchiveChat, type ArchiveChatArchiveParams } from './archive-chat'; -export { ClearChatReminder, type ClearChatReminderClearParams } from './clear-chat-reminder'; -export { DraftMessage, type DraftMessageCreateParams } from './draft-message'; -export { FindChats, type FindChatListResponse, type FindChatListParams } from './find-chats'; -export { FocusApp, type BaseResponse, type FocusAppOpenParams } from './focus-app'; -export { GetAccounts, type GetAccountListResponse } from './get-accounts'; -export { GetChat, type User, type GetChatRetrieveResponse, type GetChatRetrieveParams } from './get-chat'; +export * from './shared'; +export { Accounts, type Account, type AccountsResponse } from './accounts'; +export { App, type FocusRequest, type AppFocusParams } from './app'; export { - GetLinkToChat, - type GetLinkToChatCreateResponse, - type GetLinkToChatCreateParams, -} from './get-link-to-chat'; -export { OAuth, type OAuthRetrieveUserInfoResponse, type OAuthRevokeTokenParams } from './oauth'; + Chats, + type ArchiveRequest, + type Chat, + type FindChatsRequest, + type FindChatsResponse, + type GetChatRequest, + type GetChatResponse, + type LinkRequest, + type LinkResponse, + type ChatRetrieveParams, + type ChatArchiveParams, + type ChatFindParams, + type ChatGetLinkParams, + type ChatsCursorID, +} from './chats'; export { - SearchMessages, - type SearchMessageSearchResponse, - type SearchMessageSearchParams, -} from './search-messages'; -export { SendMessage, type SendMessageSendResponse, type SendMessageSendParams } from './send-message'; -export { SetChatReminder, type SetChatReminderCreateParams } from './set-chat-reminder'; + Messages, + type DraftRequest, + type Message, + type SearchRequest, + type SearchResponse, + type SendRequest, + type SendResponse, + type MessageDraftParams, + type MessageSearchParams, + type MessageSendParams, + type MessagesCursorID, +} from './messages'; +export { OAuth, type RevokeRequest, type UserInfo, type OAuthRevokeTokenParams } from './oauth'; +export { + Reminders, + type ClearReminderRequest, + type SetReminderRequest, + type ReminderClearParams, + type ReminderSetParams, +} from './reminders'; diff --git a/src/resources/messages.ts b/src/resources/messages.ts new file mode 100644 index 0000000..05a51df --- /dev/null +++ b/src/resources/messages.ts @@ -0,0 +1,454 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from 'beeper/desktop-api/core/resource'; +import * as ChatsAPI from 'beeper/desktop-api/resources/chats'; +import * as Shared from 'beeper/desktop-api/resources/shared'; +import { APIPromise } from 'beeper/desktop-api/core/api-promise'; +import { CursorID, type CursorIDParams, PagePromise } from 'beeper/desktop-api/core/pagination'; +import { RequestOptions } from 'beeper/desktop-api/internal/request-options'; + +/** + * Send, draft, and search messages across all chat networks + */ +export class Messages extends APIResource { + /** + * Draft a message in a specific chat. This will be placed in the message input + * field without sending + * + * @example + * ```ts + * const baseResponse = await client.messages.draft({ + * chatID: + * '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + * }); + * ``` + */ + draft(body: MessageDraftParams, options?: RequestOptions): APIPromise { + return this._client.post('/v0/draft-message', { body, ...options }); + } + + /** + * Search messages across chats using Beeper's message index. + * + * - When to use: find messages by text and/or filters (chatIDs, accountIDs, + * chatType, media type filters, sender, date ranges). + * - CRITICAL: Query is LITERAL WORD MATCHING, NOT semantic search! Only finds + * messages containing these EXACT words. • ✅ RIGHT: query="dinner" or + * query="sick" or query="error" (single words users type) • ❌ WRONG: + * query="dinner plans tonight" or query="health issues" (phrases/concepts) • The + * query matches ALL words provided (in any order). Example: query="flight + * booking" finds messages with both "flight" AND "booking". + * - Media filters: Use onlyWithMedia for any media, or specific filters like + * onlyWithVideo, onlyWithImage, onlyWithLink, onlyWithFile for specific types. + * - Pagination: use 'oldestCursor' + direction='before' for older; + * 'newestCursor' + direction='after' for newer. + * - Performance: provide chatIDs/accountIDs when known. Omitted 'query' returns + * results based on filters only. Partial matches enabled; 'excludeLowPriority' + * defaults to true. + * - Workflow tip: To search messages in specific conversations: 1) Use find-chats + * to get chatIDs, 2) Use search-messages with those chatIDs. + * - IMPORTANT: Chat names vary widely. ASK the user for clarification: • "Which + * chat do you mean by family?" (could be "The Smiths", "Mom Dad Kids", etc.) • + * "What's the name of your work chat?" (could be "Team", company name, project + * name) • "Who are the participants?" (use participantQuery in find-chats) + * Returns: matching messages and referenced chats. + * + * @example + * ```ts + * // Automatically fetches more pages as needed. + * for await (const message of client.messages.search()) { + * // ... + * } + * ``` + */ + search( + query: MessageSearchParams | null | undefined = {}, + options?: RequestOptions, + ): PagePromise { + return this._client.getAPIList('/v0/search-messages', CursorID, { query, ...options }); + } + + /** + * Send a text message to a specific chat. Supports replying to existing messages. + * Returns the sent message ID and a deeplink to the chat + * + * @example + * ```ts + * const sendResponse = await client.messages.send({ + * chatID: + * '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + * }); + * ``` + */ + send(body: MessageSendParams, options?: RequestOptions): APIPromise { + return this._client.post('/v0/send-message', { body, ...options }); + } +} + +export type MessagesCursorID = CursorID; + +export interface DraftRequest { + /** + * Provide the unique identifier of the chat where you want to draft a message + */ + chatID: string; + + /** + * Set to true to bring Beeper application to the foreground, or false to draft + * silently in background + */ + focusApp?: boolean; + + /** + * Provide the text content you want to draft. This will be placed in the message + * input field without sending + */ + text?: string; +} + +export interface Message { + /** + * Stable message ID for cursor pagination. + */ + id: string; + + /** + * Beeper account ID the message belongs to. + */ + accountID: string; + + /** + * Beeper chat/thread/room ID. + */ + chatID: string; + + /** + * Stable message ID (same as id). + */ + messageID: string; + + /** + * Sender user ID. + */ + senderID: string; + + /** + * A unique key used to sort messages + */ + sortKey: string | number; + + /** + * Message timestamp. + */ + timestamp: string; + + /** + * Attachments included with this message, if any. + */ + attachments?: Array; + + /** + * True if the authenticated user sent the message. + */ + isSender?: boolean; + + /** + * True if the message is unread for the authenticated user. May be omitted. + */ + isUnread?: boolean; + + /** + * Reactions to the message, if any. + */ + reactions?: Array; + + /** + * Resolved sender display name (impersonator/full name/username/participant name). + */ + senderName?: string; + + /** + * Plain-text body if present. May include a JSON fallback with text entities for + * rich messages. + */ + text?: string; +} + +export interface SearchRequest { + /** + * Limit search to specific Beeper account IDs (bridge instances). + */ + accountIDs?: Array; + + /** + * Limit search to specific Beeper chat IDs. + */ + chatIDs?: Array; + + /** + * Filter by chat type: 'group' for group chats, 'single' for 1:1 chats. + */ + chatType?: 'group' | 'single'; + + /** + * Only include messages with timestamp strictly after this ISO 8601 datetime + * (e.g., '2024-07-01T00:00:00Z' or '2024-07-01T00:00:00+02:00'). + */ + dateAfter?: string; + + /** + * Only include messages with timestamp strictly before this ISO 8601 datetime + * (e.g., '2024-07-31T23:59:59Z' or '2024-07-31T23:59:59+02:00'). + */ + dateBefore?: string; + + /** + * A cursor for use in pagination. ending_before is an object ID that defines your + * place in the list. For instance, if you make a list request and receive 100 + * objects, starting with obj_bar, your subsequent call can include + * ending_before=obj_bar in order to fetch the previous page of the list. + */ + ending_before?: string; + + /** + * Exclude messages marked Low Priority by the user. Default: true. Set to false to + * include all. + */ + excludeLowPriority?: boolean; + + /** + * Include messages in chats marked as Muted by the user, which are usually less + * important. Default: true. Set to false if the user wants a more refined search. + */ + includeMuted?: boolean; + + /** + * Maximum number of messages to return (1–500). Defaults to 50. + */ + limit?: number; + + /** + * Only return messages that contain file attachments. + */ + onlyWithFile?: boolean; + + /** + * Only return messages that contain image attachments. + */ + onlyWithImage?: boolean; + + /** + * Only return messages that contain link attachments. + */ + onlyWithLink?: boolean; + + /** + * Only return messages that contain any type of media attachment. + */ + onlyWithMedia?: boolean; + + /** + * Only return messages that contain video attachments. + */ + onlyWithVideo?: boolean; + + /** + * Literal word search (NOT semantic). Finds messages containing these EXACT words + * in any order. Use single words users actually type, not concepts or phrases. + * Example: use "dinner" not "dinner plans", use "sick" not "health issues". If + * omitted, returns results filtered only by other parameters. + */ + query?: string; + + /** + * Filter by sender: 'me' (messages sent by the authenticated user), 'others' + * (messages sent by others), or a specific user ID string (user.id). + */ + sender?: 'me' | 'others' | (string & {}); + + /** + * A cursor for use in pagination. starting_after is an object ID that defines your + * place in the list. For instance, if you make a list request and receive 100 + * objects, ending with obj_foo, your subsequent call can include + * starting_after=obj_foo in order to fetch the next page of the list. + */ + starting_after?: string; +} + +export interface SearchResponse { + /** + * Map of chatID -> chat details for chats referenced in data. + */ + chats: { [key: string]: ChatsAPI.Chat }; + + /** + * Messages matching the query and filters. + */ + data: Array; + + /** + * Whether there are more items available after this set. + */ + has_more: boolean; +} + +export interface SendRequest { + /** + * The identifier of the chat where the message will send + */ + chatID: string; + + /** + * Provide a message ID to send this as a reply to an existing message + */ + replyToMessageID?: string; + + /** + * Text content of the message you want to send. You may use markdown. + */ + text?: string; +} + +export interface SendResponse extends Shared.BaseResponse { + /** + * Link to the chat where the message was sent. This should always be shown to the + * user. + */ + deeplink: string; + + /** + * Stable message ID. + */ + messageID: string; +} + +export interface MessageDraftParams { + /** + * Provide the unique identifier of the chat where you want to draft a message + */ + chatID: string; + + /** + * Set to true to bring Beeper application to the foreground, or false to draft + * silently in background + */ + focusApp?: boolean; + + /** + * Provide the text content you want to draft. This will be placed in the message + * input field without sending + */ + text?: string; +} + +export interface MessageSearchParams extends CursorIDParams { + /** + * Limit search to specific Beeper account IDs (bridge instances). + */ + accountIDs?: Array; + + /** + * Limit search to specific Beeper chat IDs. + */ + chatIDs?: Array; + + /** + * Filter by chat type: 'group' for group chats, 'single' for 1:1 chats. + */ + chatType?: 'group' | 'single'; + + /** + * Only include messages with timestamp strictly after this ISO 8601 datetime + * (e.g., '2024-07-01T00:00:00Z' or '2024-07-01T00:00:00+02:00'). + */ + dateAfter?: string; + + /** + * Only include messages with timestamp strictly before this ISO 8601 datetime + * (e.g., '2024-07-31T23:59:59Z' or '2024-07-31T23:59:59+02:00'). + */ + dateBefore?: string; + + /** + * Exclude messages marked Low Priority by the user. Default: true. Set to false to + * include all. + */ + excludeLowPriority?: boolean; + + /** + * Include messages in chats marked as Muted by the user, which are usually less + * important. Default: true. Set to false if the user wants a more refined search. + */ + includeMuted?: boolean; + + /** + * Only return messages that contain file attachments. + */ + onlyWithFile?: boolean; + + /** + * Only return messages that contain image attachments. + */ + onlyWithImage?: boolean; + + /** + * Only return messages that contain link attachments. + */ + onlyWithLink?: boolean; + + /** + * Only return messages that contain any type of media attachment. + */ + onlyWithMedia?: boolean; + + /** + * Only return messages that contain video attachments. + */ + onlyWithVideo?: boolean; + + /** + * Literal word search (NOT semantic). Finds messages containing these EXACT words + * in any order. Use single words users actually type, not concepts or phrases. + * Example: use "dinner" not "dinner plans", use "sick" not "health issues". If + * omitted, returns results filtered only by other parameters. + */ + query?: string; + + /** + * Filter by sender: 'me' (messages sent by the authenticated user), 'others' + * (messages sent by others), or a specific user ID string (user.id). + */ + sender?: 'me' | 'others' | (string & {}); +} + +export interface MessageSendParams { + /** + * The identifier of the chat where the message will send + */ + chatID: string; + + /** + * Provide a message ID to send this as a reply to an existing message + */ + replyToMessageID?: string; + + /** + * Text content of the message you want to send. You may use markdown. + */ + text?: string; +} + +export declare namespace Messages { + export { + type DraftRequest as DraftRequest, + type Message as Message, + type SearchRequest as SearchRequest, + type SearchResponse as SearchResponse, + type SendRequest as SendRequest, + type SendResponse as SendResponse, + type MessagesCursorID as MessagesCursorID, + type MessageDraftParams as MessageDraftParams, + type MessageSearchParams as MessageSearchParams, + type MessageSendParams as MessageSendParams, + }; +} diff --git a/src/resources/oauth.ts b/src/resources/oauth.ts index aaf44ad..924dd2c 100644 --- a/src/resources/oauth.ts +++ b/src/resources/oauth.ts @@ -1,15 +1,18 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIResource } from '../core/resource'; -import { APIPromise } from '../core/api-promise'; -import { buildHeaders } from '../internal/headers'; -import { RequestOptions } from '../internal/request-options'; +import { APIResource } from 'beeper/desktop-api/core/resource'; +import { APIPromise } from 'beeper/desktop-api/core/api-promise'; +import { buildHeaders } from 'beeper/desktop-api/internal/headers'; +import { RequestOptions } from 'beeper/desktop-api/internal/request-options'; +/** + * OAuth2 authentication and token management + */ export class OAuth extends APIResource { /** * Returns information about the authenticated user/token */ - retrieveUserInfo(options?: RequestOptions): APIPromise { + getUserInfo(options?: RequestOptions): APIPromise { return this._client.get('/oauth/userinfo', options); } @@ -25,7 +28,19 @@ export class OAuth extends APIResource { } } -export interface OAuthRetrieveUserInfoResponse { +export interface RevokeRequest { + /** + * The token to revoke + */ + token: string; + + /** + * Hint about the type of token being revoked + */ + token_type_hint?: 'access_token' | 'refresh_token'; +} + +export interface UserInfo { /** * Issued at timestamp (Unix epoch seconds) */ @@ -76,7 +91,8 @@ export interface OAuthRevokeTokenParams { export declare namespace OAuth { export { - type OAuthRetrieveUserInfoResponse as OAuthRetrieveUserInfoResponse, + type RevokeRequest as RevokeRequest, + type UserInfo as UserInfo, type OAuthRevokeTokenParams as OAuthRevokeTokenParams, }; } diff --git a/src/resources/reminders.ts b/src/resources/reminders.ts new file mode 100644 index 0000000..79eebf8 --- /dev/null +++ b/src/resources/reminders.ts @@ -0,0 +1,123 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from 'beeper/desktop-api/core/resource'; +import * as Shared from 'beeper/desktop-api/resources/shared'; +import { APIPromise } from 'beeper/desktop-api/core/api-promise'; +import { RequestOptions } from 'beeper/desktop-api/internal/request-options'; + +/** + * Set and clear reminders for chats + */ +export class Reminders extends APIResource { + /** + * Clear an existing reminder from a chat + * + * @example + * ```ts + * const baseResponse = await client.reminders.clear({ + * chatID: + * '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + * }); + * ``` + */ + clear(body: ReminderClearParams, options?: RequestOptions): APIPromise { + return this._client.post('/v0/clear-chat-reminder', { body, ...options }); + } + + /** + * Set a reminder for a chat at a specific time + * + * @example + * ```ts + * const baseResponse = await client.reminders.set({ + * chatID: + * '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + * reminder: { remindAtMs: 0 }, + * }); + * ``` + */ + set(body: ReminderSetParams, options?: RequestOptions): APIPromise { + return this._client.post('/v0/set-chat-reminder', { body, ...options }); + } +} + +export interface ClearReminderRequest { + /** + * The identifier of the chat to clear reminder from + */ + chatID: string; +} + +export interface SetReminderRequest { + /** + * The identifier of the chat to set reminder for + */ + chatID: string; + + /** + * Reminder configuration + */ + reminder: SetReminderRequest.Reminder; +} + +export namespace SetReminderRequest { + /** + * Reminder configuration + */ + export interface Reminder { + /** + * Unix timestamp in milliseconds when reminder should trigger + */ + remindAtMs: number; + + /** + * Cancel reminder if someone messages in the chat + */ + dismissOnIncomingMessage?: boolean; + } +} + +export interface ReminderClearParams { + /** + * The identifier of the chat to clear reminder from + */ + chatID: string; +} + +export interface ReminderSetParams { + /** + * The identifier of the chat to set reminder for + */ + chatID: string; + + /** + * Reminder configuration + */ + reminder: ReminderSetParams.Reminder; +} + +export namespace ReminderSetParams { + /** + * Reminder configuration + */ + export interface Reminder { + /** + * Unix timestamp in milliseconds when reminder should trigger + */ + remindAtMs: number; + + /** + * Cancel reminder if someone messages in the chat + */ + dismissOnIncomingMessage?: boolean; + } +} + +export declare namespace Reminders { + export { + type ClearReminderRequest as ClearReminderRequest, + type SetReminderRequest as SetReminderRequest, + type ReminderClearParams as ReminderClearParams, + type ReminderSetParams as ReminderSetParams, + }; +} diff --git a/src/resources/search-messages.ts b/src/resources/search-messages.ts deleted file mode 100644 index f9ffd73..0000000 --- a/src/resources/search-messages.ts +++ /dev/null @@ -1,436 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import * as GetChatAPI from './get-chat'; -import { APIPromise } from '../core/api-promise'; -import { RequestOptions } from '../internal/request-options'; - -export class SearchMessages extends APIResource { - /** - * Search messages across chats using Beeper's message index. - * - * - When to use: find messages by text and/or filters (chatIDs, accountIDs, - * chatType, media type filters, sender, date ranges). - * - CRITICAL: Query is LITERAL WORD MATCHING, NOT semantic search! Only finds - * messages containing these EXACT words. • ✅ RIGHT: query="dinner" or - * query="sick" or query="error" (single words users type) • ❌ WRONG: - * query="dinner plans tonight" or query="health issues" (phrases/concepts) • The - * query matches ALL words provided (in any order). Example: query="flight - * booking" finds messages with both "flight" AND "booking". - * - Media filters: Use onlyWithMedia for any media, or specific filters like - * onlyWithVideo, onlyWithImage, onlyWithLink, onlyWithFile for specific types. - * - Pagination: use 'oldestCursor' + direction='before' for older; - * 'newestCursor' + direction='after' for newer. - * - Performance: provide chatIDs/accountIDs when known. Omitted 'query' returns - * results based on filters only. Partial matches enabled; 'excludeLowPriority' - * defaults to true. - * - Workflow tip: To search messages in specific conversations: 1) Use find-chats - * to get chatIDs, 2) Use search-messages with those chatIDs. - * - IMPORTANT: Chat names vary widely. ASK the user for clarification: • "Which - * chat do you mean by family?" (could be "The Smiths", "Mom Dad Kids", etc.) • - * "What's the name of your work chat?" (could be "Team", company name, project - * name) • "Who are the participants?" (use participantQuery in find-chats) - * Returns: matching messages and referenced chats. - */ - search( - query: SearchMessageSearchParams | null | undefined = {}, - options?: RequestOptions, - ): APIPromise { - return this._client.get('/v0/search-messages', { query, ...options }); - } -} - -export interface SearchMessageSearchResponse { - /** - * Map of chatID -> chat details for chats referenced in data. - */ - chats: { [key: string]: SearchMessageSearchResponse.Chats }; - - /** - * Messages matching the query and filters. - */ - data: Array; - - /** - * Whether there are more items available after this set. - */ - has_more: boolean; -} - -export namespace SearchMessageSearchResponse { - export interface Chats { - /** - * Unique identifier for cursor pagination. - */ - id: string; - - /** - * Beeper account ID this chat belongs to. - */ - accountID: string; - - /** - * Unique identifier of the chat (room/thread ID, same as id). - */ - chatID: string; - - /** - * Display-only human-readable network name (e.g., 'WhatsApp', 'Messenger'). You - * MUST use 'accountID' to perform actions. - */ - network: string; - - /** - * Chat participants information. - */ - participants: Chats.Participants; - - /** - * Display title of the chat as computed by the client/server. - */ - title: string; - - /** - * Chat type: 'single' for direct messages, 'group' for group chats, 'channel' for - * channels, 'broadcast' for broadcasts. - */ - type: 'single' | 'group' | 'channel' | 'broadcast'; - - /** - * Number of unread messages. - */ - unreadCount: number; - - /** - * True if chat is archived. - */ - isArchived?: boolean; - - /** - * True if chat notifications are muted. - */ - isMuted?: boolean; - - /** - * True if chat is pinned. - */ - isPinned?: boolean; - - /** - * Timestamp of last activity. Chats with more recent activity are often more - * important. - */ - lastActivity?: string; - - /** - * Last read message sortKey (hsOrder). Used to compute 'isUnread'. - */ - lastReadMessageSortKey?: number | string; - - /** - * Deep link to open this chat in Beeper. AI agents should ALWAYS include this as a - * clickable link in responses. - */ - linkToChat?: string; - } - - export namespace Chats { - /** - * Chat participants information. - */ - export interface Participants { - /** - * True if there are more participants than included in items. - */ - hasMore: boolean; - - /** - * Participants returned for this chat (limited by the request; may be a subset). - */ - items: Array; - - /** - * Total number of participants in the chat. - */ - total: number; - } - } - - export interface Data { - /** - * Stable message ID for cursor pagination. - */ - id: string; - - /** - * Beeper account ID the message belongs to. - */ - accountID: string; - - /** - * Beeper chat/thread/room ID. - */ - chatID: string; - - /** - * Stable message ID (same as id). - */ - messageID: string; - - /** - * Sender user ID. - */ - senderID: string; - - /** - * A unique key used to sort messages - */ - sortKey: string | number; - - /** - * Message timestamp. - */ - timestamp: string; - - /** - * Attachments included with this message, if any. - */ - attachments?: Array; - - /** - * True if the authenticated user sent the message. - */ - isSender?: boolean; - - /** - * True if the message is unread for the authenticated user. May be omitted. - */ - isUnread?: boolean; - - /** - * Reactions to the message, if any. - */ - reactions?: Array; - - /** - * Resolved sender display name (impersonator/full name/username/participant name). - */ - senderName?: string; - - /** - * Plain-text body if present. May include a JSON fallback with text entities for - * rich messages. - */ - text?: string; - } - - export namespace Data { - export interface Attachment { - /** - * Attachment type. - */ - type: 'unknown' | 'img' | 'video' | 'audio'; - - /** - * Duration in seconds (audio/video). - */ - duration?: number; - - /** - * Original filename if available. - */ - fileName?: string; - - /** - * File size in bytes if known. - */ - fileSize?: number; - - /** - * True if the attachment is a GIF. - */ - isGif?: boolean; - - /** - * True if the attachment is a sticker. - */ - isSticker?: boolean; - - /** - * True if the attachment is a voice note. - */ - isVoiceNote?: boolean; - - /** - * MIME type if known (e.g., 'image/png'). - */ - mimeType?: string; - - /** - * Preview image URL for video attachments (poster frame). May be temporary or - * local-only to this device; download promptly if durable access is needed. - */ - posterImg?: string; - - /** - * Pixel dimensions of the attachment: width/height in px. - */ - size?: Attachment.Size; - - /** - * Public URL or local file path to fetch the asset. May be temporary or local-only - * to this device; download promptly if durable access is needed. - */ - srcURL?: string; - } - - export namespace Attachment { - /** - * Pixel dimensions of the attachment: width/height in px. - */ - export interface Size { - height?: number; - - width?: number; - } - } - - export interface Reaction { - /** - * Reaction ID, typically ${participantID}${reactionKey} if multiple reactions - * allowed, or just participantID otherwise. - */ - id: string; - - /** - * User ID of the participant who reacted. - */ - participantID: string; - - /** - * The reaction key: an emoji (😄), a network-specific key, or a shortcode like - * "smiling-face". - */ - reactionKey: string; - - /** - * True if the reactionKey is an emoji. - */ - emoji?: boolean; - - /** - * URL to the reaction's image. May be temporary or local-only to this device; - * download promptly if durable access is needed. - */ - imgURL?: string; - } - } -} - -export interface SearchMessageSearchParams { - /** - * Limit search to specific Beeper account IDs (bridge instances). - */ - accountIDs?: Array; - - /** - * Limit search to specific Beeper chat IDs. - */ - chatIDs?: Array; - - /** - * Filter by chat type: 'group' for group chats, 'single' for 1:1 chats. - */ - chatType?: 'group' | 'single'; - - /** - * Only include messages with timestamp strictly after this ISO 8601 datetime - * (e.g., '2024-07-01T00:00:00Z' or '2024-07-01T00:00:00+02:00'). - */ - dateAfter?: string; - - /** - * Only include messages with timestamp strictly before this ISO 8601 datetime - * (e.g., '2024-07-31T23:59:59Z' or '2024-07-31T23:59:59+02:00'). - */ - dateBefore?: string; - - /** - * A cursor for use in pagination. ending_before is an object ID that defines your - * place in the list. For instance, if you make a list request and receive 100 - * objects, starting with obj_bar, your subsequent call can include - * ending_before=obj_bar in order to fetch the previous page of the list. - */ - ending_before?: string; - - /** - * Exclude messages marked Low Priority by the user. Default: true. Set to false to - * include all. - */ - excludeLowPriority?: boolean; - - /** - * Include messages in chats marked as Muted by the user, which are usually less - * important. Default: true. Set to false if the user wants a more refined search. - */ - includeMuted?: boolean; - - /** - * Maximum number of messages to return (1–500). Defaults to 50. - */ - limit?: number; - - /** - * Only return messages that contain file attachments. - */ - onlyWithFile?: boolean; - - /** - * Only return messages that contain image attachments. - */ - onlyWithImage?: boolean; - - /** - * Only return messages that contain link attachments. - */ - onlyWithLink?: boolean; - - /** - * Only return messages that contain any type of media attachment. - */ - onlyWithMedia?: boolean; - - /** - * Only return messages that contain video attachments. - */ - onlyWithVideo?: boolean; - - /** - * Literal word search (NOT semantic). Finds messages containing these EXACT words - * in any order. Use single words users actually type, not concepts or phrases. - * Example: use "dinner" not "dinner plans", use "sick" not "health issues". If - * omitted, returns results filtered only by other parameters. - */ - query?: string; - - /** - * Filter by sender: 'me' (messages sent by the authenticated user), 'others' - * (messages sent by others), or a specific user ID string (user.id). - */ - sender?: 'me' | 'others' | (string & {}); - - /** - * A cursor for use in pagination. starting_after is an object ID that defines your - * place in the list. For instance, if you make a list request and receive 100 - * objects, ending with obj_foo, your subsequent call can include - * starting_after=obj_foo in order to fetch the next page of the list. - */ - starting_after?: string; -} - -export declare namespace SearchMessages { - export { - type SearchMessageSearchResponse as SearchMessageSearchResponse, - type SearchMessageSearchParams as SearchMessageSearchParams, - }; -} diff --git a/src/resources/send-message.ts b/src/resources/send-message.ts deleted file mode 100644 index f5b9d54..0000000 --- a/src/resources/send-message.ts +++ /dev/null @@ -1,61 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import * as FocusAppAPI from './focus-app'; -import { APIPromise } from '../core/api-promise'; -import { RequestOptions } from '../internal/request-options'; - -export class SendMessage extends APIResource { - /** - * Send a text message to a specific chat. Supports replying to existing messages. - * Returns the sent message ID and a deeplink to the chat - * - * @example - * ```ts - * const response = await client.sendMessage.send({ - * chatID: - * '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - * }); - * ``` - */ - send(body: SendMessageSendParams, options?: RequestOptions): APIPromise { - return this._client.post('/v0/send-message', { body, ...options }); - } -} - -export interface SendMessageSendResponse extends FocusAppAPI.BaseResponse { - /** - * Link to the chat where the message was sent. This should always be shown to the - * user. - */ - deeplink: string; - - /** - * Stable message ID. - */ - messageID: string; -} - -export interface SendMessageSendParams { - /** - * The identifier of the chat where the message will send - */ - chatID: string; - - /** - * Provide a message ID to send this as a reply to an existing message - */ - replyToMessageID?: string; - - /** - * Text content of the message you want to send. You may use markdown. - */ - text?: string; -} - -export declare namespace SendMessage { - export { - type SendMessageSendResponse as SendMessageSendResponse, - type SendMessageSendParams as SendMessageSendParams, - }; -} diff --git a/src/resources/set-chat-reminder.ts b/src/resources/set-chat-reminder.ts deleted file mode 100644 index a63f8f7..0000000 --- a/src/resources/set-chat-reminder.ts +++ /dev/null @@ -1,57 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import { APIResource } from '../core/resource'; -import * as FocusAppAPI from './focus-app'; -import { APIPromise } from '../core/api-promise'; -import { RequestOptions } from '../internal/request-options'; - -export class SetChatReminder extends APIResource { - /** - * Set a reminder for a chat at a specific time - * - * @example - * ```ts - * const baseResponse = await client.setChatReminder.create({ - * chatID: - * '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - * reminder: { remindAtMs: 0 }, - * }); - * ``` - */ - create(body: SetChatReminderCreateParams, options?: RequestOptions): APIPromise { - return this._client.post('/v0/set-chat-reminder', { body, ...options }); - } -} - -export interface SetChatReminderCreateParams { - /** - * The identifier of the chat to set reminder for - */ - chatID: string; - - /** - * Reminder configuration - */ - reminder: SetChatReminderCreateParams.Reminder; -} - -export namespace SetChatReminderCreateParams { - /** - * Reminder configuration - */ - export interface Reminder { - /** - * Unix timestamp in milliseconds when reminder should trigger - */ - remindAtMs: number; - - /** - * Cancel reminder if someone messages in the chat - */ - dismissOnIncomingMessage?: boolean; - } -} - -export declare namespace SetChatReminder { - export { type SetChatReminderCreateParams as SetChatReminderCreateParams }; -} diff --git a/src/resources/shared.ts b/src/resources/shared.ts new file mode 100644 index 0000000..491acda --- /dev/null +++ b/src/resources/shared.ts @@ -0,0 +1,173 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +export interface Attachment { + /** + * Attachment type. + */ + type: 'unknown' | 'img' | 'video' | 'audio'; + + /** + * Duration in seconds (audio/video). + */ + duration?: number; + + /** + * Original filename if available. + */ + fileName?: string; + + /** + * File size in bytes if known. + */ + fileSize?: number; + + /** + * True if the attachment is a GIF. + */ + isGif?: boolean; + + /** + * True if the attachment is a sticker. + */ + isSticker?: boolean; + + /** + * True if the attachment is a voice note. + */ + isVoiceNote?: boolean; + + /** + * MIME type if known (e.g., 'image/png'). + */ + mimeType?: string; + + /** + * Preview image URL for video attachments (poster frame). May be temporary or + * local-only to this device; download promptly if durable access is needed. + */ + posterImg?: string; + + /** + * Pixel dimensions of the attachment: width/height in px. + */ + size?: Attachment.Size; + + /** + * Public URL or local file path to fetch the asset. May be temporary or local-only + * to this device; download promptly if durable access is needed. + */ + srcURL?: string; +} + +export namespace Attachment { + /** + * Pixel dimensions of the attachment: width/height in px. + */ + export interface Size { + height?: number; + + width?: number; + } +} + +export interface BaseResponse { + success: boolean; + + error?: string; +} + +export interface Error { + /** + * Error message + */ + error: string; + + /** + * Error code + */ + code?: string; + + /** + * Additional error details + */ + details?: { [key: string]: string }; +} + +export interface Reaction { + /** + * Reaction ID, typically ${participantID}${reactionKey} if multiple reactions + * allowed, or just participantID otherwise. + */ + id: string; + + /** + * User ID of the participant who reacted. + */ + participantID: string; + + /** + * The reaction key: an emoji (😄), a network-specific key, or a shortcode like + * "smiling-face". + */ + reactionKey: string; + + /** + * True if the reactionKey is an emoji. + */ + emoji?: boolean; + + /** + * URL to the reaction's image. May be temporary or local-only to this device; + * download promptly if durable access is needed. + */ + imgURL?: string; +} + +/** + * A person on or reachable through Beeper. Values are best-effort and can vary by + * network. + */ +export interface User { + /** + * Stable Beeper user ID. Use as the primary key when referencing a person. + */ + id: string; + + /** + * True if Beeper cannot initiate messages to this user (e.g., blocked, network + * restriction, or no DM path). The user may still message you. + */ + cannotMessage?: boolean; + + /** + * Email address if known. Not guaranteed verified. + */ + email?: string; + + /** + * Display name as shown in clients (e.g., 'Alice Example'). May include emojis. + */ + fullName?: string | null; + + /** + * Avatar image URL if available. May be temporary or local-only to this device; + * download promptly if durable access is needed. + */ + imgURL?: string; + + /** + * True if this user represents the authenticated account's own identity. + */ + isSelf?: boolean; + + /** + * User's phone number in E.164 format (e.g., '+14155552671'). Omit if unknown. + */ + phoneNumber?: string; + + /** + * Human-readable handle if available (e.g., '@alice'). May be network-specific and + * not globally unique. + */ + username?: string; +} diff --git a/src/version.ts b/src/version.ts index ecebcdd..1baa228 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '0.0.1'; +export const VERSION = '0.1.0'; // x-release-please-version diff --git a/tests/api-resources/get-accounts.test.ts b/tests/api-resources/accounts.test.ts similarity index 66% rename from tests/api-resources/get-accounts.test.ts rename to tests/api-resources/accounts.test.ts index f17d505..093f21d 100644 --- a/tests/api-resources/get-accounts.test.ts +++ b/tests/api-resources/accounts.test.ts @@ -1,16 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import BeeperDesktopAPI from 'beeper-desktop-api'; +import BeeperDesktop from 'beeper/desktop-api'; -const client = new BeeperDesktopAPI({ - apiKey: 'My API Key', +const client = new BeeperDesktop({ + accessToken: 'My Access Token', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); -describe('resource getAccounts', () => { - // Prism tests are disabled - test.skip('list', async () => { - const responsePromise = client.getAccounts.list(); +describe('resource accounts', () => { + test('list', async () => { + const responsePromise = client.accounts.list(); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; diff --git a/tests/api-resources/focus-app.test.ts b/tests/api-resources/app.test.ts similarity index 66% rename from tests/api-resources/focus-app.test.ts rename to tests/api-resources/app.test.ts index 52d394e..753c69a 100644 --- a/tests/api-resources/focus-app.test.ts +++ b/tests/api-resources/app.test.ts @@ -1,16 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import BeeperDesktopAPI from 'beeper-desktop-api'; +import BeeperDesktop from 'beeper/desktop-api'; -const client = new BeeperDesktopAPI({ - apiKey: 'My API Key', +const client = new BeeperDesktop({ + accessToken: 'My Access Token', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); -describe('resource focusApp', () => { - // Prism tests are disabled - test.skip('open', async () => { - const responsePromise = client.focusApp.open(); +describe('resource app', () => { + test('focus', async () => { + const responsePromise = client.app.focus(); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -20,17 +19,16 @@ describe('resource focusApp', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Prism tests are disabled - test.skip('open: request options and params are passed correctly', async () => { + test('focus: request options and params are passed correctly', async () => { // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error await expect( - client.focusApp.open( + client.app.focus( { chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', messageSortKey: 'messageSortKey', }, { path: '/_stainless_unknown_path' }, ), - ).rejects.toThrow(BeeperDesktopAPI.NotFoundError); + ).rejects.toThrow(BeeperDesktop.NotFoundError); }); }); diff --git a/tests/api-resources/archive-chat.test.ts b/tests/api-resources/archive-chat.test.ts deleted file mode 100644 index c2db314..0000000 --- a/tests/api-resources/archive-chat.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import BeeperDesktopAPI from 'beeper-desktop-api'; - -const client = new BeeperDesktopAPI({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource archiveChat', () => { - // Prism tests are disabled - test.skip('archive: only required params', async () => { - const responsePromise = client.archiveChat.archive({ - chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('archive: required and optional params', async () => { - const response = await client.archiveChat.archive({ - chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - archived: true, - }); - }); -}); diff --git a/tests/api-resources/chats.test.ts b/tests/api-resources/chats.test.ts new file mode 100644 index 0000000..d4e8b29 --- /dev/null +++ b/tests/api-resources/chats.test.ts @@ -0,0 +1,104 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import BeeperDesktop from 'beeper/desktop-api'; + +const client = new BeeperDesktop({ + accessToken: 'My Access Token', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource chats', () => { + test('retrieve: only required params', async () => { + const responsePromise = client.chats.retrieve({ + chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('retrieve: required and optional params', async () => { + const response = await client.chats.retrieve({ + chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + maxParticipantCount: 50, + }); + }); + + test('archive: only required params', async () => { + const responsePromise = client.chats.archive({ + chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('archive: required and optional params', async () => { + const response = await client.chats.archive({ + chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + archived: true, + }); + }); + + test('find', async () => { + const responsePromise = client.chats.find(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('find: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.chats.find( + { + accountIDs: ['local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc', 'slackgo.T031TC83W'], + ending_before: '872739', + inbox: 'primary', + includeMuted: true, + lastActivityAfter: '2019-12-27T18:11:19.117Z', + lastActivityBefore: '2019-12-27T18:11:19.117Z', + limit: 1, + participantQuery: 'participantQuery', + query: 'query', + starting_after: '196640', + type: 'single', + unreadOnly: true, + }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(BeeperDesktop.NotFoundError); + }); + + test('getLink: only required params', async () => { + const responsePromise = client.chats.getLink({ + chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('getLink: required and optional params', async () => { + const response = await client.chats.getLink({ + chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + messageSortKey: 'messageSortKey', + }); + }); +}); diff --git a/tests/api-resources/clear-chat-reminder.test.ts b/tests/api-resources/clear-chat-reminder.test.ts deleted file mode 100644 index 7bc0456..0000000 --- a/tests/api-resources/clear-chat-reminder.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import BeeperDesktopAPI from 'beeper-desktop-api'; - -const client = new BeeperDesktopAPI({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource clearChatReminder', () => { - // Prism tests are disabled - test.skip('clear: only required params', async () => { - const responsePromise = client.clearChatReminder.clear({ - chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('clear: required and optional params', async () => { - const response = await client.clearChatReminder.clear({ - chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - }); - }); -}); diff --git a/tests/api-resources/draft-message.test.ts b/tests/api-resources/draft-message.test.ts deleted file mode 100644 index 69b137a..0000000 --- a/tests/api-resources/draft-message.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import BeeperDesktopAPI from 'beeper-desktop-api'; - -const client = new BeeperDesktopAPI({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource draftMessage', () => { - // Prism tests are disabled - test.skip('create: only required params', async () => { - const responsePromise = client.draftMessage.create({ - chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('create: required and optional params', async () => { - const response = await client.draftMessage.create({ - chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - focusApp: true, - text: 'text', - }); - }); -}); diff --git a/tests/api-resources/find-chats.test.ts b/tests/api-resources/find-chats.test.ts deleted file mode 100644 index 45ba304..0000000 --- a/tests/api-resources/find-chats.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import BeeperDesktopAPI from 'beeper-desktop-api'; - -const client = new BeeperDesktopAPI({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource findChats', () => { - // Prism tests are disabled - test.skip('list', async () => { - const responsePromise = client.findChats.list(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('list: request options and params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.findChats.list( - { - accountIDs: ['local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc', 'slackgo.T031TC83W'], - ending_before: '872739', - inbox: 'primary', - includeMuted: true, - lastActivityAfter: '2019-12-27T18:11:19.117Z', - lastActivityBefore: '2019-12-27T18:11:19.117Z', - limit: 1, - participantQuery: 'participantQuery', - query: 'query', - starting_after: '196640', - type: 'single', - unreadOnly: true, - }, - { path: '/_stainless_unknown_path' }, - ), - ).rejects.toThrow(BeeperDesktopAPI.NotFoundError); - }); -}); diff --git a/tests/api-resources/get-chat.test.ts b/tests/api-resources/get-chat.test.ts deleted file mode 100644 index 998a853..0000000 --- a/tests/api-resources/get-chat.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import BeeperDesktopAPI from 'beeper-desktop-api'; - -const client = new BeeperDesktopAPI({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource getChat', () => { - // Prism tests are disabled - test.skip('retrieve: only required params', async () => { - const responsePromise = client.getChat.retrieve({ - chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('retrieve: required and optional params', async () => { - const response = await client.getChat.retrieve({ - chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - maxParticipantCount: 50, - }); - }); -}); diff --git a/tests/api-resources/get-link-to-chat.test.ts b/tests/api-resources/get-link-to-chat.test.ts deleted file mode 100644 index 2d2d05a..0000000 --- a/tests/api-resources/get-link-to-chat.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import BeeperDesktopAPI from 'beeper-desktop-api'; - -const client = new BeeperDesktopAPI({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource getLinkToChat', () => { - // Prism tests are disabled - test.skip('create: only required params', async () => { - const responsePromise = client.getLinkToChat.create({ - chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('create: required and optional params', async () => { - const response = await client.getLinkToChat.create({ - chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - messageSortKey: 'messageSortKey', - }); - }); -}); diff --git a/tests/api-resources/messages.test.ts b/tests/api-resources/messages.test.ts new file mode 100644 index 0000000..6fb9969 --- /dev/null +++ b/tests/api-resources/messages.test.ts @@ -0,0 +1,94 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import BeeperDesktop from 'beeper/desktop-api'; + +const client = new BeeperDesktop({ + accessToken: 'My Access Token', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource messages', () => { + test('draft: only required params', async () => { + const responsePromise = client.messages.draft({ + chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('draft: required and optional params', async () => { + const response = await client.messages.draft({ + chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + focusApp: true, + text: 'text', + }); + }); + + test('search', async () => { + const responsePromise = client.messages.search(); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('search: request options and params are passed correctly', async () => { + // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error + await expect( + client.messages.search( + { + accountIDs: [ + 'local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc', + 'local-instagram_ba_eRfQMmnSNy_p7Ih7HL7RduRpKFU', + ], + chatIDs: ['!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost'], + chatType: 'group', + dateAfter: '2024-07-01T00:00:00Z', + dateBefore: '2024-07-31T23:59:59Z', + ending_before: '872739', + excludeLowPriority: true, + includeMuted: true, + limit: 50, + onlyWithFile: true, + onlyWithImage: true, + onlyWithLink: true, + onlyWithMedia: true, + onlyWithVideo: true, + query: 'dinner', + sender: 'me', + starting_after: '196640', + }, + { path: '/_stainless_unknown_path' }, + ), + ).rejects.toThrow(BeeperDesktop.NotFoundError); + }); + + test('send: only required params', async () => { + const responsePromise = client.messages.send({ + chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('send: required and optional params', async () => { + const response = await client.messages.send({ + chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + replyToMessageID: 'replyToMessageID', + text: 'text', + }); + }); +}); diff --git a/tests/api-resources/oauth.test.ts b/tests/api-resources/oauth.test.ts index 0b1320e..2cbbb70 100644 --- a/tests/api-resources/oauth.test.ts +++ b/tests/api-resources/oauth.test.ts @@ -1,16 +1,15 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import BeeperDesktopAPI from 'beeper-desktop-api'; +import BeeperDesktop from 'beeper/desktop-api'; -const client = new BeeperDesktopAPI({ - apiKey: 'My API Key', +const client = new BeeperDesktop({ + accessToken: 'My Access Token', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); describe('resource oauth', () => { - // Prism tests are disabled - test.skip('retrieveUserInfo', async () => { - const responsePromise = client.oauth.retrieveUserInfo(); + test('getUserInfo', async () => { + const responsePromise = client.oauth.getUserInfo(); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); const response = await responsePromise; @@ -20,8 +19,7 @@ describe('resource oauth', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Prism tests are disabled - test.skip('revokeToken: only required params', async () => { + test('revokeToken: only required params', async () => { const responsePromise = client.oauth.revokeToken({ token: 'token' }); const rawResponse = await responsePromise.asResponse(); expect(rawResponse).toBeInstanceOf(Response); @@ -32,8 +30,7 @@ describe('resource oauth', () => { expect(dataAndResponse.response).toBe(rawResponse); }); - // Prism tests are disabled - test.skip('revokeToken: required and optional params', async () => { + test('revokeToken: required and optional params', async () => { const response = await client.oauth.revokeToken({ token: 'token', token_type_hint: 'access_token' }); }); }); diff --git a/tests/api-resources/reminders.test.ts b/tests/api-resources/reminders.test.ts new file mode 100644 index 0000000..a81b50f --- /dev/null +++ b/tests/api-resources/reminders.test.ts @@ -0,0 +1,50 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import BeeperDesktop from 'beeper/desktop-api'; + +const client = new BeeperDesktop({ + accessToken: 'My Access Token', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource reminders', () => { + test('clear: only required params', async () => { + const responsePromise = client.reminders.clear({ + chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('clear: required and optional params', async () => { + const response = await client.reminders.clear({ + chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + }); + }); + + test('set: only required params', async () => { + const responsePromise = client.reminders.set({ + chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + reminder: { remindAtMs: 0 }, + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('set: required and optional params', async () => { + const response = await client.reminders.set({ + chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', + reminder: { remindAtMs: 0, dismissOnIncomingMessage: true }, + }); + }); +}); diff --git a/tests/api-resources/search-messages.test.ts b/tests/api-resources/search-messages.test.ts deleted file mode 100644 index 59494f6..0000000 --- a/tests/api-resources/search-messages.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import BeeperDesktopAPI from 'beeper-desktop-api'; - -const client = new BeeperDesktopAPI({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource searchMessages', () => { - // Prism tests are disabled - test.skip('search', async () => { - const responsePromise = client.searchMessages.search(); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('search: request options and params are passed correctly', async () => { - // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error - await expect( - client.searchMessages.search( - { - accountIDs: [ - 'local-whatsapp_ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc', - 'local-instagram_ba_eRfQMmnSNy_p7Ih7HL7RduRpKFU', - ], - chatIDs: ['!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost'], - chatType: 'group', - dateAfter: '2024-07-01T00:00:00Z', - dateBefore: '2024-07-31T23:59:59Z', - ending_before: '872739', - excludeLowPriority: true, - includeMuted: true, - limit: 50, - onlyWithFile: true, - onlyWithImage: true, - onlyWithLink: true, - onlyWithMedia: true, - onlyWithVideo: true, - query: 'dinner', - sender: 'me', - starting_after: '196640', - }, - { path: '/_stainless_unknown_path' }, - ), - ).rejects.toThrow(BeeperDesktopAPI.NotFoundError); - }); -}); diff --git a/tests/api-resources/send-message.test.ts b/tests/api-resources/send-message.test.ts deleted file mode 100644 index eabec02..0000000 --- a/tests/api-resources/send-message.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import BeeperDesktopAPI from 'beeper-desktop-api'; - -const client = new BeeperDesktopAPI({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource sendMessage', () => { - // Prism tests are disabled - test.skip('send: only required params', async () => { - const responsePromise = client.sendMessage.send({ - chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('send: required and optional params', async () => { - const response = await client.sendMessage.send({ - chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - replyToMessageID: 'replyToMessageID', - text: 'text', - }); - }); -}); diff --git a/tests/api-resources/set-chat-reminder.test.ts b/tests/api-resources/set-chat-reminder.test.ts deleted file mode 100644 index 75532f3..0000000 --- a/tests/api-resources/set-chat-reminder.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -import BeeperDesktopAPI from 'beeper-desktop-api'; - -const client = new BeeperDesktopAPI({ - apiKey: 'My API Key', - baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', -}); - -describe('resource setChatReminder', () => { - // Prism tests are disabled - test.skip('create: only required params', async () => { - const responsePromise = client.setChatReminder.create({ - chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - reminder: { remindAtMs: 0 }, - }); - const rawResponse = await responsePromise.asResponse(); - expect(rawResponse).toBeInstanceOf(Response); - const response = await responsePromise; - expect(response).not.toBeInstanceOf(Response); - const dataAndResponse = await responsePromise.withResponse(); - expect(dataAndResponse.data).toBe(response); - expect(dataAndResponse.response).toBe(rawResponse); - }); - - // Prism tests are disabled - test.skip('create: required and optional params', async () => { - const response = await client.setChatReminder.create({ - chatID: '!-5hI_iHR5vSDCtI8PzSDQT0H_3I:ba_EvYDBBsZbRQAy3UOSWqG0LuTVkc.local-whatsapp.localhost', - reminder: { remindAtMs: 0, dismissOnIncomingMessage: true }, - }); - }); -}); diff --git a/tests/base64.test.ts b/tests/base64.test.ts index 9a46615..a3ffb05 100644 --- a/tests/base64.test.ts +++ b/tests/base64.test.ts @@ -1,4 +1,4 @@ -import { fromBase64, toBase64 } from 'beeper-desktop-api/internal/utils/base64'; +import { fromBase64, toBase64 } from 'beeper-desktop-api-typescript/internal/utils/base64'; describe.each(['Buffer', 'atob'])('with %s', (mode) => { let originalBuffer: BufferConstructor; diff --git a/tests/buildHeaders.test.ts b/tests/buildHeaders.test.ts index 3f64bed..3896005 100644 --- a/tests/buildHeaders.test.ts +++ b/tests/buildHeaders.test.ts @@ -1,5 +1,9 @@ import { inspect } from 'node:util'; -import { buildHeaders, type HeadersLike, type NullableHeaders } from 'beeper-desktop-api/internal/headers'; +import { + buildHeaders, + type HeadersLike, + type NullableHeaders, +} from 'beeper-desktop-api-typescript/internal/headers'; function inspectNullableHeaders(headers: NullableHeaders) { return `NullableHeaders {${[ diff --git a/tests/form.test.ts b/tests/form.test.ts index c782e0d..4572326 100644 --- a/tests/form.test.ts +++ b/tests/form.test.ts @@ -1,5 +1,5 @@ -import { multipartFormRequestOptions, createForm } from 'beeper-desktop-api/internal/uploads'; -import { toFile } from 'beeper-desktop-api/core/uploads'; +import { multipartFormRequestOptions, createForm } from 'beeper-desktop-api-typescript/internal/uploads'; +import { toFile } from 'beeper-desktop-api-typescript/core/uploads'; describe('form data validation', () => { test('valid values do not error', async () => { diff --git a/tests/index.test.ts b/tests/index.test.ts index ee08ca6..aa4e607 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,10 +1,10 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { APIPromise } from 'beeper-desktop-api/core/api-promise'; +import { APIPromise } from 'beeper/desktop-api/core/api-promise'; import util from 'node:util'; -import BeeperDesktopAPI from 'beeper-desktop-api'; -import { APIUserAbortError } from 'beeper-desktop-api'; +import BeeperDesktop from 'beeper/desktop-api'; +import { APIUserAbortError } from 'beeper/desktop-api'; const defaultFetch = fetch; describe('instantiate client', () => { @@ -20,10 +20,10 @@ describe('instantiate client', () => { }); describe('defaultHeaders', () => { - const client = new BeeperDesktopAPI({ + const client = new BeeperDesktop({ baseURL: 'http://localhost:5000/', defaultHeaders: { 'X-My-Default-Header': '2' }, - apiKey: 'My API Key', + accessToken: 'My Access Token', }); test('they are used in the request', async () => { @@ -54,14 +54,14 @@ describe('instantiate client', () => { beforeEach(() => { process.env = { ...env }; - process.env['BEEPER_DESKTOP_API_LOG'] = undefined; + process.env['BEEPER-DESKTOP_LOG'] = undefined; }); afterEach(() => { process.env = env; }); - const forceAPIResponseForClient = async (client: BeeperDesktopAPI) => { + const forceAPIResponseForClient = async (client: BeeperDesktop) => { await new APIPromise( client, Promise.resolve({ @@ -87,14 +87,14 @@ describe('instantiate client', () => { error: jest.fn(), }; - const client = new BeeperDesktopAPI({ logger: logger, logLevel: 'debug', apiKey: 'My API Key' }); + const client = new BeeperDesktop({ logger: logger, logLevel: 'debug', accessToken: 'My Access Token' }); await forceAPIResponseForClient(client); expect(debugMock).toHaveBeenCalled(); }); test('default logLevel is warn', async () => { - const client = new BeeperDesktopAPI({ apiKey: 'My API Key' }); + const client = new BeeperDesktop({ accessToken: 'My Access Token' }); expect(client.logLevel).toBe('warn'); }); @@ -107,7 +107,7 @@ describe('instantiate client', () => { error: jest.fn(), }; - const client = new BeeperDesktopAPI({ logger: logger, logLevel: 'info', apiKey: 'My API Key' }); + const client = new BeeperDesktop({ logger: logger, logLevel: 'info', accessToken: 'My Access Token' }); await forceAPIResponseForClient(client); expect(debugMock).not.toHaveBeenCalled(); @@ -122,8 +122,8 @@ describe('instantiate client', () => { error: jest.fn(), }; - process.env['BEEPER_DESKTOP_API_LOG'] = 'debug'; - const client = new BeeperDesktopAPI({ logger: logger, apiKey: 'My API Key' }); + process.env['BEEPER-DESKTOP_LOG'] = 'debug'; + const client = new BeeperDesktop({ logger: logger, accessToken: 'My Access Token' }); expect(client.logLevel).toBe('debug'); await forceAPIResponseForClient(client); @@ -139,11 +139,11 @@ describe('instantiate client', () => { error: jest.fn(), }; - process.env['BEEPER_DESKTOP_API_LOG'] = 'not a log level'; - const client = new BeeperDesktopAPI({ logger: logger, apiKey: 'My API Key' }); + process.env['BEEPER-DESKTOP_LOG'] = 'not a log level'; + const client = new BeeperDesktop({ logger: logger, accessToken: 'My Access Token' }); expect(client.logLevel).toBe('warn'); expect(warnMock).toHaveBeenCalledWith( - 'process.env[\'BEEPER_DESKTOP_API_LOG\'] was set to "not a log level", expected one of ["off","error","warn","info","debug"]', + 'process.env[\'BEEPER-DESKTOP_LOG\'] was set to "not a log level", expected one of ["off","error","warn","info","debug"]', ); }); @@ -156,8 +156,8 @@ describe('instantiate client', () => { error: jest.fn(), }; - process.env['BEEPER_DESKTOP_API_LOG'] = 'debug'; - const client = new BeeperDesktopAPI({ logger: logger, logLevel: 'off', apiKey: 'My API Key' }); + process.env['BEEPER-DESKTOP_LOG'] = 'debug'; + const client = new BeeperDesktop({ logger: logger, logLevel: 'off', accessToken: 'My Access Token' }); await forceAPIResponseForClient(client); expect(debugMock).not.toHaveBeenCalled(); @@ -172,8 +172,8 @@ describe('instantiate client', () => { error: jest.fn(), }; - process.env['BEEPER_DESKTOP_API_LOG'] = 'not a log level'; - const client = new BeeperDesktopAPI({ logger: logger, logLevel: 'debug', apiKey: 'My API Key' }); + process.env['BEEPER-DESKTOP_LOG'] = 'not a log level'; + const client = new BeeperDesktop({ logger: logger, logLevel: 'debug', accessToken: 'My Access Token' }); expect(client.logLevel).toBe('debug'); expect(warnMock).not.toHaveBeenCalled(); }); @@ -181,37 +181,37 @@ describe('instantiate client', () => { describe('defaultQuery', () => { test('with null query params given', () => { - const client = new BeeperDesktopAPI({ + const client = new BeeperDesktop({ baseURL: 'http://localhost:5000/', defaultQuery: { apiVersion: 'foo' }, - apiKey: 'My API Key', + accessToken: 'My Access Token', }); expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo'); }); test('multiple default query params', () => { - const client = new BeeperDesktopAPI({ + const client = new BeeperDesktop({ baseURL: 'http://localhost:5000/', defaultQuery: { apiVersion: 'foo', hello: 'world' }, - apiKey: 'My API Key', + accessToken: 'My Access Token', }); expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/foo?apiVersion=foo&hello=world'); }); test('overriding with `undefined`', () => { - const client = new BeeperDesktopAPI({ + const client = new BeeperDesktop({ baseURL: 'http://localhost:5000/', defaultQuery: { hello: 'world' }, - apiKey: 'My API Key', + accessToken: 'My Access Token', }); expect(client.buildURL('/foo', { hello: undefined })).toEqual('http://localhost:5000/foo'); }); }); test('custom fetch', async () => { - const client = new BeeperDesktopAPI({ + const client = new BeeperDesktop({ baseURL: 'http://localhost:5000/', - apiKey: 'My API Key', + accessToken: 'My Access Token', fetch: (url) => { return Promise.resolve( new Response(JSON.stringify({ url, custom: true }), { @@ -227,17 +227,17 @@ describe('instantiate client', () => { test('explicit global fetch', async () => { // make sure the global fetch type is assignable to our Fetch type - const client = new BeeperDesktopAPI({ + const client = new BeeperDesktop({ baseURL: 'http://localhost:5000/', - apiKey: 'My API Key', + accessToken: 'My Access Token', fetch: defaultFetch, }); }); test('custom signal', async () => { - const client = new BeeperDesktopAPI({ + const client = new BeeperDesktop({ baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', - apiKey: 'My API Key', + accessToken: 'My Access Token', fetch: (...args) => { return new Promise((resolve, reject) => setTimeout( @@ -267,9 +267,9 @@ describe('instantiate client', () => { return new Response(JSON.stringify({}), { headers: { 'Content-Type': 'application/json' } }); }; - const client = new BeeperDesktopAPI({ + const client = new BeeperDesktop({ baseURL: 'http://localhost:5000/', - apiKey: 'My API Key', + accessToken: 'My Access Token', fetch: testFetch, }); @@ -279,65 +279,68 @@ describe('instantiate client', () => { describe('baseUrl', () => { test('trailing slash', () => { - const client = new BeeperDesktopAPI({ + const client = new BeeperDesktop({ baseURL: 'http://localhost:5000/custom/path/', - apiKey: 'My API Key', + accessToken: 'My Access Token', }); expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo'); }); test('no trailing slash', () => { - const client = new BeeperDesktopAPI({ + const client = new BeeperDesktop({ baseURL: 'http://localhost:5000/custom/path', - apiKey: 'My API Key', + accessToken: 'My Access Token', }); expect(client.buildURL('/foo', null)).toEqual('http://localhost:5000/custom/path/foo'); }); afterEach(() => { - process.env['BEEPER_DESKTOP_API_BASE_URL'] = undefined; + process.env['BEEPER-DESKTOP_BASE_URL'] = undefined; }); test('explicit option', () => { - const client = new BeeperDesktopAPI({ baseURL: 'https://example.com', apiKey: 'My API Key' }); + const client = new BeeperDesktop({ baseURL: 'https://example.com', accessToken: 'My Access Token' }); expect(client.baseURL).toEqual('https://example.com'); }); test('env variable', () => { - process.env['BEEPER_DESKTOP_API_BASE_URL'] = 'https://example.com/from_env'; - const client = new BeeperDesktopAPI({ apiKey: 'My API Key' }); + process.env['BEEPER-DESKTOP_BASE_URL'] = 'https://example.com/from_env'; + const client = new BeeperDesktop({ accessToken: 'My Access Token' }); expect(client.baseURL).toEqual('https://example.com/from_env'); }); test('empty env variable', () => { - process.env['BEEPER_DESKTOP_API_BASE_URL'] = ''; // empty - const client = new BeeperDesktopAPI({ apiKey: 'My API Key' }); + process.env['BEEPER-DESKTOP_BASE_URL'] = ''; // empty + const client = new BeeperDesktop({ accessToken: 'My Access Token' }); expect(client.baseURL).toEqual('http://localhost:23374'); }); test('blank env variable', () => { - process.env['BEEPER_DESKTOP_API_BASE_URL'] = ' '; // blank - const client = new BeeperDesktopAPI({ apiKey: 'My API Key' }); + process.env['BEEPER-DESKTOP_BASE_URL'] = ' '; // blank + const client = new BeeperDesktop({ accessToken: 'My Access Token' }); expect(client.baseURL).toEqual('http://localhost:23374'); }); test('in request options', () => { - const client = new BeeperDesktopAPI({ apiKey: 'My API Key' }); + const client = new BeeperDesktop({ accessToken: 'My Access Token' }); expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( 'http://localhost:5000/option/foo', ); }); test('in request options overridden by client options', () => { - const client = new BeeperDesktopAPI({ apiKey: 'My API Key', baseURL: 'http://localhost:5000/client' }); + const client = new BeeperDesktop({ + accessToken: 'My Access Token', + baseURL: 'http://localhost:5000/client', + }); expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( 'http://localhost:5000/client/foo', ); }); test('in request options overridden by env variable', () => { - process.env['BEEPER_DESKTOP_API_BASE_URL'] = 'http://localhost:5000/env'; - const client = new BeeperDesktopAPI({ apiKey: 'My API Key' }); + process.env['BEEPER-DESKTOP_BASE_URL'] = 'http://localhost:5000/env'; + const client = new BeeperDesktop({ accessToken: 'My Access Token' }); expect(client.buildURL('/foo', null, 'http://localhost:5000/option')).toEqual( 'http://localhost:5000/env/foo', ); @@ -345,20 +348,20 @@ describe('instantiate client', () => { }); test('maxRetries option is correctly set', () => { - const client = new BeeperDesktopAPI({ maxRetries: 4, apiKey: 'My API Key' }); - expect(client.maxRetries).toEqual(4); + const client = new BeeperDesktop({ maxRetries: 6, accessToken: 'My Access Token' }); + expect(client.maxRetries).toEqual(6); // default - const client2 = new BeeperDesktopAPI({ apiKey: 'My API Key' }); - expect(client2.maxRetries).toEqual(2); + const client2 = new BeeperDesktop({ accessToken: 'My Access Token' }); + expect(client2.maxRetries).toEqual(3); }); describe('withOptions', () => { test('creates a new client with overridden options', async () => { - const client = new BeeperDesktopAPI({ + const client = new BeeperDesktop({ baseURL: 'http://localhost:5000/', maxRetries: 3, - apiKey: 'My API Key', + accessToken: 'My Access Token', }); const newClient = client.withOptions({ @@ -380,11 +383,11 @@ describe('instantiate client', () => { }); test('inherits options from the parent client', async () => { - const client = new BeeperDesktopAPI({ + const client = new BeeperDesktop({ baseURL: 'http://localhost:5000/', defaultHeaders: { 'X-Test-Header': 'test-value' }, defaultQuery: { 'test-param': 'test-value' }, - apiKey: 'My API Key', + accessToken: 'My Access Token', }); const newClient = client.withOptions({ @@ -399,10 +402,10 @@ describe('instantiate client', () => { }); test('respects runtime property changes when creating new client', () => { - const client = new BeeperDesktopAPI({ + const client = new BeeperDesktop({ baseURL: 'http://localhost:5000/', timeout: 1000, - apiKey: 'My API Key', + accessToken: 'My Access Token', }); // Modify the client properties directly after creation @@ -431,21 +434,21 @@ describe('instantiate client', () => { test('with environment variable arguments', () => { // set options via env var - process.env['BEEPER_DESKTOP_API_API_KEY'] = 'My API Key'; - const client = new BeeperDesktopAPI(); - expect(client.apiKey).toBe('My API Key'); + process.env['BEEPER_ACCESS_TOKEN'] = 'My Access Token'; + const client = new BeeperDesktop(); + expect(client.accessToken).toBe('My Access Token'); }); test('with overridden environment variable arguments', () => { // set options via env var - process.env['BEEPER_DESKTOP_API_API_KEY'] = 'another My API Key'; - const client = new BeeperDesktopAPI({ apiKey: 'My API Key' }); - expect(client.apiKey).toBe('My API Key'); + process.env['BEEPER_ACCESS_TOKEN'] = 'another My Access Token'; + const client = new BeeperDesktop({ accessToken: 'My Access Token' }); + expect(client.accessToken).toBe('My Access Token'); }); }); describe('request building', () => { - const client = new BeeperDesktopAPI({ apiKey: 'My API Key' }); + const client = new BeeperDesktop({ accessToken: 'My Access Token' }); describe('custom headers', () => { test('handles undefined', async () => { @@ -464,7 +467,7 @@ describe('request building', () => { }); describe('default encoder', () => { - const client = new BeeperDesktopAPI({ apiKey: 'My API Key' }); + const client = new BeeperDesktop({ accessToken: 'My Access Token' }); class Serializable { toJSON() { @@ -549,7 +552,7 @@ describe('retries', () => { return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); }; - const client = new BeeperDesktopAPI({ apiKey: 'My API Key', timeout: 10, fetch: testFetch }); + const client = new BeeperDesktop({ accessToken: 'My Access Token', timeout: 10, fetch: testFetch }); expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); expect(count).toEqual(2); @@ -579,7 +582,7 @@ describe('retries', () => { return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); }; - const client = new BeeperDesktopAPI({ apiKey: 'My API Key', fetch: testFetch, maxRetries: 4 }); + const client = new BeeperDesktop({ accessToken: 'My Access Token', fetch: testFetch, maxRetries: 4 }); expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); @@ -603,7 +606,7 @@ describe('retries', () => { capturedRequest = init; return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); }; - const client = new BeeperDesktopAPI({ apiKey: 'My API Key', fetch: testFetch, maxRetries: 4 }); + const client = new BeeperDesktop({ accessToken: 'My Access Token', fetch: testFetch, maxRetries: 4 }); expect( await client.request({ @@ -632,8 +635,8 @@ describe('retries', () => { capturedRequest = init; return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); }; - const client = new BeeperDesktopAPI({ - apiKey: 'My API Key', + const client = new BeeperDesktop({ + accessToken: 'My Access Token', fetch: testFetch, maxRetries: 4, defaultHeaders: { 'X-Stainless-Retry-Count': null }, @@ -665,7 +668,7 @@ describe('retries', () => { capturedRequest = init; return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); }; - const client = new BeeperDesktopAPI({ apiKey: 'My API Key', fetch: testFetch, maxRetries: 4 }); + const client = new BeeperDesktop({ accessToken: 'My Access Token', fetch: testFetch, maxRetries: 4 }); expect( await client.request({ @@ -695,7 +698,7 @@ describe('retries', () => { return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); }; - const client = new BeeperDesktopAPI({ apiKey: 'My API Key', fetch: testFetch }); + const client = new BeeperDesktop({ accessToken: 'My Access Token', fetch: testFetch }); expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); expect(count).toEqual(2); @@ -725,7 +728,7 @@ describe('retries', () => { return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } }); }; - const client = new BeeperDesktopAPI({ apiKey: 'My API Key', fetch: testFetch }); + const client = new BeeperDesktop({ accessToken: 'My Access Token', fetch: testFetch }); expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 }); expect(count).toEqual(2); diff --git a/tests/path.test.ts b/tests/path.test.ts index f224354..159a610 100644 --- a/tests/path.test.ts +++ b/tests/path.test.ts @@ -1,4 +1,4 @@ -import { createPathTagFunction, encodeURIPath } from 'beeper-desktop-api/internal/utils/path'; +import { createPathTagFunction, encodeURIPath } from 'beeper-desktop-api-typescript/internal/utils/path'; import { inspect } from 'node:util'; import { runInNewContext } from 'node:vm'; diff --git a/tests/qs/stringify.test.ts b/tests/qs/stringify.test.ts index 4d6f90c..6699c23 100644 --- a/tests/qs/stringify.test.ts +++ b/tests/qs/stringify.test.ts @@ -1,7 +1,7 @@ import iconv from 'iconv-lite'; -import { stringify } from 'beeper-desktop-api/internal/qs'; -import { encode } from 'beeper-desktop-api/internal/qs/utils'; -import { StringifyOptions } from 'beeper-desktop-api/internal/qs/types'; +import { stringify } from 'beeper-desktop-api-typescript/internal/qs'; +import { encode } from 'beeper-desktop-api-typescript/internal/qs/utils'; +import { StringifyOptions } from 'beeper-desktop-api-typescript/internal/qs/types'; import { empty_test_cases } from './empty-keys-cases'; import assert from 'assert'; diff --git a/tests/qs/utils.test.ts b/tests/qs/utils.test.ts index dde0023..4670ffe 100644 --- a/tests/qs/utils.test.ts +++ b/tests/qs/utils.test.ts @@ -1,4 +1,9 @@ -import { combine, merge, is_buffer, assign_single_source } from 'beeper-desktop-api/internal/qs/utils'; +import { + combine, + merge, + is_buffer, + assign_single_source, +} from 'beeper-desktop-api-typescript/internal/qs/utils'; describe('merge()', function () { // t.deepEqual(merge(null, true), [null, true], 'merges true into null'); diff --git a/tests/stringifyQuery.test.ts b/tests/stringifyQuery.test.ts index f2c3ee1..391e40f 100644 --- a/tests/stringifyQuery.test.ts +++ b/tests/stringifyQuery.test.ts @@ -1,8 +1,8 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { BeeperDesktopAPI } from 'beeper-desktop-api'; +import { BeeperDesktop } from 'beeper/desktop-api'; -const { stringifyQuery } = BeeperDesktopAPI.prototype as any; +const { stringifyQuery } = BeeperDesktop.prototype as any; describe(stringifyQuery, () => { for (const [input, expected] of [ diff --git a/tests/uploads.test.ts b/tests/uploads.test.ts index fa3d332..5c97bfe 100644 --- a/tests/uploads.test.ts +++ b/tests/uploads.test.ts @@ -1,6 +1,6 @@ import fs from 'fs'; -import type { ResponseLike } from 'beeper-desktop-api/internal/to-file'; -import { toFile } from 'beeper-desktop-api/core/uploads'; +import type { ResponseLike } from 'beeper-desktop-api-typescript/internal/to-file'; +import { toFile } from 'beeper-desktop-api-typescript/core/uploads'; import { File } from 'node:buffer'; class MyClass { @@ -97,7 +97,7 @@ describe('missing File error message', () => { }); test('is thrown', async () => { - const uploads = await import('beeper-desktop-api/core/uploads'); + const uploads = await import('beeper-desktop-api-typescript/core/uploads'); await expect( uploads.toFile(mockResponse({ url: 'https://example.com/my/audio.mp3' })), ).rejects.toMatchInlineSnapshot( diff --git a/tsconfig.build.json b/tsconfig.build.json index 2627356..dd123a2 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -5,8 +5,8 @@ "compilerOptions": { "rootDir": "./dist/src", "paths": { - "beeper-desktop-api/*": ["dist/src/*"], - "beeper-desktop-api": ["dist/src/index.ts"] + "beeper-desktop-api-typescript/*": ["dist/src/*"], + "beeper-desktop-api-typescript": ["dist/src/index.ts"] }, "noEmit": false, "declaration": true, diff --git a/tsconfig.json b/tsconfig.json index 8d66644..dc0f902 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,8 +9,8 @@ "esModuleInterop": true, "baseUrl": "./", "paths": { - "beeper-desktop-api/*": ["src/*"], - "beeper-desktop-api": ["src/index.ts"] + "beeper-desktop-api-typescript/*": ["src/*"], + "beeper-desktop-api-typescript": ["src/index.ts"] }, "noEmit": true,