├── .devcontainer └── devcontainer.json ├── .env.example ├── .eslintrc.js ├── .github └── workflows │ ├── ci.yml │ ├── publish-npm.yml │ └── release-doctor.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── .release-please-manifest.json ├── .stats.yml ├── Brewfile ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── api.md ├── bin ├── check-release-environment └── publish-npm ├── examples ├── packages │ ├── extensions │ │ ├── .DS_Store │ │ ├── .gitignore │ │ └── browserbase-test │ │ │ ├── hello.html │ │ │ ├── images │ │ │ └── logo.png │ │ │ ├── manifest.json │ │ │ └── scripts │ │ │ └── content.js │ └── logo.png ├── playwright-basic.ts ├── playwright-captcha.ts ├── playwright-context.ts ├── playwright-download.ts ├── playwright-extension.ts ├── playwright-proxy.ts ├── playwright-upload.ts ├── puppeteer-basic.ts ├── puppeteer-proxy.ts ├── selenium-basic.ts └── selenium-upload.ts ├── jest.config.ts ├── package-lock.json ├── package.json ├── release-please-config.json ├── scripts ├── bootstrap ├── build ├── format ├── lint ├── mock ├── test └── utils │ ├── check-is-in-git-install.sh │ ├── check-version.cjs │ ├── fix-index-exports.cjs │ ├── git-swap.sh │ ├── make-dist-package-json.cjs │ ├── postprocess-files.cjs │ └── upload-artifact.sh ├── src ├── _shims │ ├── MultipartBody.ts │ ├── README.md │ ├── auto │ │ ├── runtime-bun.ts │ │ ├── runtime-deno.ts │ │ ├── runtime-node.ts │ │ ├── runtime.ts │ │ ├── types-deno.ts │ │ ├── types-node.ts │ │ ├── types.d.ts │ │ ├── types.js │ │ └── types.mjs │ ├── bun-runtime.ts │ ├── index-deno.ts │ ├── index.d.ts │ ├── index.js │ ├── index.mjs │ ├── manual-types.d.ts │ ├── manual-types.js │ ├── manual-types.mjs │ ├── node-runtime.ts │ ├── node-types.d.ts │ ├── node-types.js │ ├── node-types.mjs │ ├── registry.ts │ ├── web-runtime.ts │ ├── web-types.d.ts │ ├── web-types.js │ └── web-types.mjs ├── core.ts ├── error.ts ├── index.ts ├── lib │ └── .keep ├── resource.ts ├── resources.ts ├── resources │ ├── contexts.ts │ ├── extensions.ts │ ├── index.ts │ ├── projects.ts │ ├── sessions.ts │ └── sessions │ │ ├── downloads.ts │ │ ├── index.ts │ │ ├── logs.ts │ │ ├── recording.ts │ │ ├── sessions.ts │ │ └── uploads.ts ├── shims │ ├── node.ts │ └── web.ts ├── uploads.ts └── version.ts ├── tests ├── api-resources │ ├── contexts.test.ts │ ├── extensions.test.ts │ ├── projects.test.ts │ └── sessions │ │ ├── downloads.test.ts │ │ ├── logs.test.ts │ │ ├── recording.test.ts │ │ ├── sessions.test.ts │ │ └── uploads.test.ts ├── form.test.ts ├── index.test.ts ├── responses.test.ts ├── stringifyQuery.test.ts └── uploads.test.ts ├── tsc-multi.json ├── tsconfig.build.json ├── tsconfig.deno.json ├── tsconfig.dist-src.json ├── tsconfig.json └── yarn.lock /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/debian 3 | { 4 | "name": "Development", 5 | "image": "mcr.microsoft.com/devcontainers/typescript-node:latest", 6 | "features": { 7 | "ghcr.io/devcontainers/features/node:1": {} 8 | }, 9 | "postCreateCommand": "yarn install", 10 | "customizations": { 11 | "vscode": { 12 | "extensions": [ 13 | "esbenp.prettier-vscode" 14 | ] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | BROWSERBASE_API_KEY="your-api-key-here" 2 | BROWSERBASE_PROJECT_ID="your-project-id-here" -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | plugins: ['@typescript-eslint', 'unused-imports', 'prettier'], 4 | rules: { 5 | 'no-unused-vars': 'off', 6 | 'prettier/prettier': 'error', 7 | 'unused-imports/no-unused-imports': 'error', 8 | }, 9 | root: true, 10 | }; 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches-ignore: 5 | - 'generated' 6 | - 'codegen/**' 7 | - 'integrated/**' 8 | - 'stl-preview-head/**' 9 | - 'stl-preview-base/**' 10 | 11 | jobs: 12 | lint: 13 | timeout-minutes: 10 14 | name: lint 15 | runs-on: ${{ github.repository == 'stainless-sdks/browserbase-node' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Set up Node 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: '20' 23 | 24 | - name: Bootstrap 25 | run: ./scripts/bootstrap 26 | 27 | - name: Check types 28 | run: ./scripts/lint 29 | 30 | build: 31 | timeout-minutes: 5 32 | name: build 33 | runs-on: ${{ github.repository == 'stainless-sdks/browserbase-node' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} 34 | permissions: 35 | contents: read 36 | id-token: write 37 | steps: 38 | - uses: actions/checkout@v4 39 | 40 | - name: Set up Node 41 | uses: actions/setup-node@v4 42 | with: 43 | node-version: '20' 44 | 45 | - name: Bootstrap 46 | run: ./scripts/bootstrap 47 | 48 | - name: Check build 49 | run: ./scripts/build 50 | 51 | - name: Get GitHub OIDC Token 52 | if: github.repository == 'stainless-sdks/browserbase-node' 53 | id: github-oidc 54 | uses: actions/github-script@v6 55 | with: 56 | script: core.setOutput('github_token', await core.getIDToken()); 57 | 58 | - name: Upload tarball 59 | if: github.repository == 'stainless-sdks/browserbase-node' 60 | env: 61 | URL: https://pkg.stainless.com/s 62 | AUTH: ${{ steps.github-oidc.outputs.github_token }} 63 | SHA: ${{ github.sha }} 64 | run: ./scripts/utils/upload-artifact.sh 65 | test: 66 | timeout-minutes: 10 67 | name: test 68 | runs-on: ${{ github.repository == 'stainless-sdks/browserbase-node' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} 69 | steps: 70 | - uses: actions/checkout@v4 71 | 72 | - name: Set up Node 73 | uses: actions/setup-node@v4 74 | with: 75 | node-version: '20' 76 | 77 | - name: Bootstrap 78 | run: ./scripts/bootstrap 79 | 80 | - name: Run tests 81 | run: ./scripts/test 82 | -------------------------------------------------------------------------------- /.github/workflows/publish-npm.yml: -------------------------------------------------------------------------------- 1 | # This workflow is triggered when a GitHub release is created. 2 | # It can also be run manually to re-publish to NPM in case it failed for some reason. 3 | # You can run this workflow by navigating to https://www.github.com/browserbase/sdk-node/actions/workflows/publish-npm.yml 4 | name: Publish NPM 5 | on: 6 | workflow_dispatch: 7 | 8 | release: 9 | types: [published] 10 | 11 | jobs: 12 | publish: 13 | name: publish 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Set up Node 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: '20' 23 | 24 | - name: Install dependencies 25 | run: | 26 | yarn install 27 | 28 | - name: Publish to NPM 29 | run: | 30 | bash ./bin/publish-npm 31 | env: 32 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 33 | -------------------------------------------------------------------------------- /.github/workflows/release-doctor.yml: -------------------------------------------------------------------------------- 1 | name: Release Doctor 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | workflow_dispatch: 7 | 8 | jobs: 9 | release_doctor: 10 | name: release doctor 11 | runs-on: ubuntu-latest 12 | if: github.repository == 'browserbase/sdk-node' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') 13 | 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Check release environment 18 | run: | 19 | bash ./bin/check-release-environment 20 | env: 21 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .prism.log 2 | node_modules 3 | yarn-error.log 4 | codegen.log 5 | Brewfile.lock.json 6 | dist 7 | dist-deno 8 | /*.tgz 9 | .idea/ 10 | 11 | .env -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | CHANGELOG.md 2 | /ecosystem-tests/*/** 3 | /node_modules 4 | /deno 5 | 6 | # don't format tsc output, will break source maps 7 | /dist 8 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "experimentalTernaries": true, 4 | "printWidth": 110, 5 | "singleQuote": true, 6 | "trailingComma": "all" 7 | } 8 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "2.6.0" 3 | } 4 | -------------------------------------------------------------------------------- /.stats.yml: -------------------------------------------------------------------------------- 1 | configured_endpoints: 18 2 | openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fbrowserbase-e2ed1b5267eeff92982918505349017b9155da2c7ab948787ab11cf9068af1b8.yml 3 | openapi_spec_hash: 6639c21dccb52ca610cae833227a9791 4 | config_hash: 74882e23a455dece33e43a27e67f0fbb 5 | -------------------------------------------------------------------------------- /Brewfile: -------------------------------------------------------------------------------- 1 | brew "node" 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Setting up the environment 2 | 3 | This repository uses [`yarn@v1`](https://classic.yarnpkg.com/lang/en/docs/install). 4 | Other package managers may work but are not officially supported for development. 5 | 6 | To set up the repository, run: 7 | 8 | ```sh 9 | $ yarn 10 | $ yarn build 11 | ``` 12 | 13 | This will install all the required dependencies and build output files to `dist/`. 14 | 15 | ## Modifying/Adding code 16 | 17 | Most of the SDK is generated code. Modifications to code will be persisted between generations, but may 18 | result in merge conflicts between manual patches and changes from the generator. The generator will never 19 | modify the contents of the `src/lib/` and `examples/` directories. 20 | 21 | ## Adding and running examples 22 | 23 | All files in the `examples/` directory are not modified by the generator and can be freely edited or added to. 24 | 25 | ```ts 26 | // add an example to examples/.ts 27 | 28 | #!/usr/bin/env -S npm run tsn -T 29 | … 30 | ``` 31 | 32 | ```sh 33 | $ chmod +x examples/.ts 34 | # run the example against your api 35 | $ yarn tsn -T examples/.ts 36 | ``` 37 | 38 | ## Using the repository from source 39 | 40 | If you’d like to use the repository from source, you can either install from git or link to a cloned repository: 41 | 42 | To install via git: 43 | 44 | ```sh 45 | $ npm install git+ssh://git@github.com:browserbase/sdk-node.git 46 | ``` 47 | 48 | Alternatively, to link a local copy of the repo: 49 | 50 | ```sh 51 | # Clone 52 | $ git clone https://www.github.com/browserbase/sdk-node 53 | $ cd sdk-node 54 | 55 | # With yarn 56 | $ yarn link 57 | $ cd ../my-package 58 | $ yarn link @browserbasehq/sdk 59 | 60 | # With pnpm 61 | $ pnpm link --global 62 | $ cd ../my-package 63 | $ pnpm link -—global @browserbasehq/sdk 64 | ``` 65 | 66 | ## Running tests 67 | 68 | Most tests require you to [set up a mock server](https://github.com/stoplightio/prism) against the OpenAPI spec to run the tests. 69 | 70 | ```sh 71 | $ npx prism mock path/to/your/openapi.yml 72 | ``` 73 | 74 | ```sh 75 | $ yarn run test 76 | ``` 77 | 78 | ## Linting and formatting 79 | 80 | This repository uses [prettier](https://www.npmjs.com/package/prettier) and 81 | [eslint](https://www.npmjs.com/package/eslint) to format the code in the repository. 82 | 83 | To lint: 84 | 85 | ```sh 86 | $ yarn lint 87 | ``` 88 | 89 | To format and fix all lint issues automatically: 90 | 91 | ```sh 92 | $ yarn fix 93 | ``` 94 | 95 | ## Publishing and releases 96 | 97 | Changes made to this repository via the automated release PR pipeline should publish to npm automatically. If 98 | the changes aren't made through the automated pipeline, you may want to make releases manually. 99 | 100 | ### Publish with a GitHub workflow 101 | 102 | You can release to package managers by using [the `Publish NPM` GitHub action](https://www.github.com/browserbase/sdk-node/actions/workflows/publish-npm.yml). This requires a setup organization or repository secret to be set up. 103 | 104 | ### Publish manually 105 | 106 | If you need to manually release a package, you can run the `bin/publish-npm` script with an `NPM_TOKEN` set on 107 | the environment. 108 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2025 Browserbase 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Browserbase Node API Library 2 | 3 | [![NPM version](https://img.shields.io/npm/v/@browserbasehq/sdk.svg)](https://npmjs.org/package/@browserbasehq/sdk) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/@browserbasehq/sdk) 4 | 5 | This library provides convenient access to the Browserbase REST API from server-side TypeScript or JavaScript. 6 | 7 | The REST API documentation can be found on [docs.browserbase.com](https://docs.browserbase.com). The full API of this library can be found in [api.md](api.md). 8 | 9 | It is generated with [Stainless](https://www.stainless.com/). 10 | 11 | ## Installation 12 | 13 | ```sh 14 | npm install @browserbasehq/sdk 15 | ``` 16 | 17 | ## Usage 18 | 19 | The full API of this library can be found in [api.md](api.md). 20 | 21 | 22 | ```js 23 | import Browserbase from '@browserbasehq/sdk'; 24 | 25 | const client = new Browserbase({ 26 | apiKey: process.env['BROWSERBASE_API_KEY'], // This is the default and can be omitted 27 | }); 28 | 29 | async function main() { 30 | const session = await client.sessions.create({ projectId: 'your_project_id' }); 31 | 32 | console.log(session.id); 33 | } 34 | 35 | main(); 36 | ``` 37 | 38 | ### Request & Response types 39 | 40 | This library includes TypeScript definitions for all request params and response fields. You may import and use them like so: 41 | 42 | 43 | ```ts 44 | import Browserbase from '@browserbasehq/sdk'; 45 | 46 | const client = new Browserbase({ 47 | apiKey: process.env['BROWSERBASE_API_KEY'], // This is the default and can be omitted 48 | }); 49 | 50 | async function main() { 51 | const params: Browserbase.SessionCreateParams = { projectId: 'your_project_id' }; 52 | const session: Browserbase.SessionCreateResponse = await client.sessions.create(params); 53 | } 54 | 55 | main(); 56 | ``` 57 | 58 | Documentation for each method, request param, and response field are available in docstrings and will appear on hover in most modern editors. 59 | 60 | ## File uploads 61 | 62 | Request parameters that correspond to file uploads can be passed in many different forms: 63 | 64 | - `File` (or an object with the same structure) 65 | - a `fetch` `Response` (or an object with the same structure) 66 | - an `fs.ReadStream` 67 | - the return value of our `toFile` helper 68 | 69 | ```ts 70 | import fs from 'fs'; 71 | import fetch from 'node-fetch'; 72 | import Browserbase, { toFile } from '@browserbasehq/sdk'; 73 | 74 | const client = new Browserbase(); 75 | 76 | // If you have access to Node `fs` we recommend using `fs.createReadStream()`: 77 | await client.extensions.create({ file: fs.createReadStream('/path/to/file') }); 78 | 79 | // Or if you have the web `File` API you can pass a `File` instance: 80 | await client.extensions.create({ file: new File(['my bytes'], 'file') }); 81 | 82 | // You can also pass a `fetch` `Response`: 83 | await client.extensions.create({ file: await fetch('https://somesite/file') }); 84 | 85 | // Finally, if none of the above are convenient, you can use our `toFile` helper: 86 | await client.extensions.create({ file: await toFile(Buffer.from('my bytes'), 'file') }); 87 | await client.extensions.create({ file: await toFile(new Uint8Array([0, 1, 2]), 'file') }); 88 | ``` 89 | 90 | ## Handling errors 91 | 92 | When the library is unable to connect to the API, 93 | or if the API returns a non-success status code (i.e., 4xx or 5xx response), 94 | a subclass of `APIError` will be thrown: 95 | 96 | 97 | ```ts 98 | async function main() { 99 | const session = await client.sessions.create({ projectId: 'your_project_id' }).catch(async (err) => { 100 | if (err instanceof Browserbase.APIError) { 101 | console.log(err.status); // 400 102 | console.log(err.name); // BadRequestError 103 | console.log(err.headers); // {server: 'nginx', ...} 104 | } else { 105 | throw err; 106 | } 107 | }); 108 | } 109 | 110 | main(); 111 | ``` 112 | 113 | Error codes are as follows: 114 | 115 | | Status Code | Error Type | 116 | | ----------- | -------------------------- | 117 | | 400 | `BadRequestError` | 118 | | 401 | `AuthenticationError` | 119 | | 403 | `PermissionDeniedError` | 120 | | 404 | `NotFoundError` | 121 | | 422 | `UnprocessableEntityError` | 122 | | 429 | `RateLimitError` | 123 | | >=500 | `InternalServerError` | 124 | | N/A | `APIConnectionError` | 125 | 126 | ### Retries 127 | 128 | Certain errors will be automatically retried 2 times by default, with a short exponential backoff. 129 | Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, 130 | 429 Rate Limit, and >=500 Internal errors will all be retried by default. 131 | 132 | You can use the `maxRetries` option to configure or disable this: 133 | 134 | 135 | ```js 136 | // Configure the default for all requests: 137 | const client = new Browserbase({ 138 | maxRetries: 0, // default is 2 139 | }); 140 | 141 | // Or, configure per-request: 142 | await client.sessions.create({ projectId: 'your_project_id' }, { 143 | maxRetries: 5, 144 | }); 145 | ``` 146 | 147 | ### Timeouts 148 | 149 | Requests time out after 1 minute by default. You can configure this with a `timeout` option: 150 | 151 | 152 | ```ts 153 | // Configure the default for all requests: 154 | const client = new Browserbase({ 155 | timeout: 20 * 1000, // 20 seconds (default is 1 minute) 156 | }); 157 | 158 | // Override per-request: 159 | await client.sessions.create({ projectId: 'your_project_id' }, { 160 | timeout: 5 * 1000, 161 | }); 162 | ``` 163 | 164 | On timeout, an `APIConnectionTimeoutError` is thrown. 165 | 166 | Note that requests which time out will be [retried twice by default](#retries). 167 | 168 | ## Advanced Usage 169 | 170 | ### Accessing raw Response data (e.g., headers) 171 | 172 | The "raw" `Response` returned by `fetch()` can be accessed through the `.asResponse()` method on the `APIPromise` type that all methods return. 173 | 174 | You can also use the `.withResponse()` method to get the raw `Response` along with the parsed data. 175 | 176 | 177 | ```ts 178 | const client = new Browserbase(); 179 | 180 | const response = await client.sessions.create({ projectId: 'your_project_id' }).asResponse(); 181 | console.log(response.headers.get('X-My-Header')); 182 | console.log(response.statusText); // access the underlying Response object 183 | 184 | const { data: session, response: raw } = await client.sessions 185 | .create({ projectId: 'your_project_id' }) 186 | .withResponse(); 187 | console.log(raw.headers.get('X-My-Header')); 188 | console.log(session.id); 189 | ``` 190 | 191 | ### Making custom/undocumented requests 192 | 193 | This library is typed for convenient access to the documented API. If you need to access undocumented 194 | endpoints, params, or response properties, the library can still be used. 195 | 196 | #### Undocumented endpoints 197 | 198 | To make requests to undocumented endpoints, you can use `client.get`, `client.post`, and other HTTP verbs. 199 | Options on the client, such as retries, will be respected when making these requests. 200 | 201 | ```ts 202 | await client.post('/some/path', { 203 | body: { some_prop: 'foo' }, 204 | query: { some_query_arg: 'bar' }, 205 | }); 206 | ``` 207 | 208 | #### Undocumented request params 209 | 210 | To make requests using undocumented parameters, you may use `// @ts-expect-error` on the undocumented 211 | parameter. This library doesn't validate at runtime that the request matches the type, so any extra values you 212 | send will be sent as-is. 213 | 214 | ```ts 215 | client.foo.create({ 216 | foo: 'my_param', 217 | bar: 12, 218 | // @ts-expect-error baz is not yet public 219 | baz: 'undocumented option', 220 | }); 221 | ``` 222 | 223 | For requests with the `GET` verb, any extra params will be in the query, all other requests will send the 224 | extra param in the body. 225 | 226 | If you want to explicitly send an extra argument, you can do so with the `query`, `body`, and `headers` request 227 | options. 228 | 229 | #### Undocumented response properties 230 | 231 | To access undocumented response properties, you may access the response object with `// @ts-expect-error` on 232 | the response object, or cast the response object to the requisite type. Like the request params, we do not 233 | validate or strip extra properties from the response from the API. 234 | 235 | ### Customizing the fetch client 236 | 237 | By default, this library uses `node-fetch` in Node, and expects a global `fetch` function in other environments. 238 | 239 | If you would prefer to use a global, web-standards-compliant `fetch` function even in a Node environment, 240 | (for example, if you are running Node with `--experimental-fetch` or using NextJS which polyfills with `undici`), 241 | add the following import before your first import `from "Browserbase"`: 242 | 243 | ```ts 244 | // Tell TypeScript and the package to use the global web fetch instead of node-fetch. 245 | // Note, despite the name, this does not add any polyfills, but expects them to be provided if needed. 246 | import '@browserbasehq/sdk/shims/web'; 247 | import Browserbase from '@browserbasehq/sdk'; 248 | ``` 249 | 250 | To do the inverse, add `import "@browserbasehq/sdk/shims/node"` (which does import polyfills). 251 | This can also be useful if you are getting the wrong TypeScript types for `Response` ([more details](https://github.com/browserbase/sdk-node/tree/main/src/_shims#readme)). 252 | 253 | ### Logging and middleware 254 | 255 | You may also provide a custom `fetch` function when instantiating the client, 256 | which can be used to inspect or alter the `Request` or `Response` before/after each request: 257 | 258 | ```ts 259 | import { fetch } from 'undici'; // as one example 260 | import Browserbase from '@browserbasehq/sdk'; 261 | 262 | const client = new Browserbase({ 263 | fetch: async (url: RequestInfo, init?: RequestInit): Promise => { 264 | console.log('About to make a request', url, init); 265 | const response = await fetch(url, init); 266 | console.log('Got response', response); 267 | return response; 268 | }, 269 | }); 270 | ``` 271 | 272 | Note that if given a `DEBUG=true` environment variable, this library will log all requests and responses automatically. 273 | This is intended for debugging purposes only and may change in the future without notice. 274 | 275 | ### Configuring an HTTP(S) Agent (e.g., for proxies) 276 | 277 | By default, this library uses a stable agent for all http/https requests to reuse TCP connections, eliminating many TCP & TLS handshakes and shaving around 100ms off most requests. 278 | 279 | If you would like to disable or customize this behavior, for example to use the API behind a proxy, you can pass an `httpAgent` which is used for all requests (be they http or https), for example: 280 | 281 | 282 | ```ts 283 | import http from 'http'; 284 | import { HttpsProxyAgent } from 'https-proxy-agent'; 285 | 286 | // Configure the default for all requests: 287 | const client = new Browserbase({ 288 | httpAgent: new HttpsProxyAgent(process.env.PROXY_URL), 289 | }); 290 | 291 | // Override per-request: 292 | await client.sessions.create( 293 | { projectId: 'your_project_id' }, 294 | { 295 | httpAgent: new http.Agent({ keepAlive: false }), 296 | }, 297 | ); 298 | ``` 299 | 300 | ## Semantic versioning 301 | 302 | This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions: 303 | 304 | 1. Changes that only affect static types, without breaking runtime behavior. 305 | 2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_ 306 | 3. Changes that we do not expect to impact the vast majority of users in practice. 307 | 308 | We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. 309 | 310 | We are keen for your feedback; please open an [issue](https://www.github.com/browserbase/sdk-node/issues) with questions, bugs, or suggestions. 311 | 312 | ## Requirements 313 | 314 | TypeScript >= 4.5 is supported. 315 | 316 | The following runtimes are supported: 317 | 318 | - Web browsers (Up-to-date Chrome, Firefox, Safari, Edge, and more) 319 | - Node.js 18 LTS or later ([non-EOL](https://endoflife.date/nodejs)) versions. 320 | - Deno v1.28.0 or higher. 321 | - Bun 1.0 or later. 322 | - Cloudflare Workers. 323 | - Vercel Edge Runtime. 324 | - Jest 28 or greater with the `"node"` environment (`"jsdom"` is not supported at this time). 325 | - Nitro v2.6 or greater. 326 | 327 | Note that React Native is not supported at this time. 328 | 329 | If you are interested in other runtime environments, please open or upvote an issue on GitHub. 330 | 331 | ## Contributing 332 | 333 | See [the contributing documentation](./CONTRIBUTING.md). 334 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting Security Issues 4 | 5 | This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. 6 | 7 | To report a security issue, please contact the Stainless team at security@stainless.com. 8 | 9 | ## Responsible Disclosure 10 | 11 | We appreciate the efforts of security researchers and individuals who help us maintain the security of 12 | SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible 13 | disclosure practices by allowing us a reasonable amount of time to investigate and address the issue 14 | before making any information public. 15 | 16 | ## Reporting Non-SDK Related Security Issues 17 | 18 | If you encounter security issues that are not directly related to SDKs but pertain to the services 19 | or products provided by Browserbase please follow the respective company's security reporting guidelines. 20 | 21 | ### Browserbase Terms and Policies 22 | 23 | Please contact support@browserbase.com for any questions or concerns regarding security of our services. 24 | 25 | --- 26 | 27 | Thank you for helping us keep the SDKs and systems they interact with secure. 28 | -------------------------------------------------------------------------------- /api.md: -------------------------------------------------------------------------------- 1 | # Contexts 2 | 3 | Types: 4 | 5 | - Context 6 | - ContextCreateResponse 7 | - ContextUpdateResponse 8 | 9 | Methods: 10 | 11 | - client.contexts.create({ ...params }) -> ContextCreateResponse 12 | - client.contexts.retrieve(id) -> Context 13 | - client.contexts.update(id) -> ContextUpdateResponse 14 | 15 | # Extensions 16 | 17 | Types: 18 | 19 | - Extension 20 | 21 | Methods: 22 | 23 | - client.extensions.create({ ...params }) -> Extension 24 | - client.extensions.retrieve(id) -> Extension 25 | - client.extensions.delete(id) -> void 26 | 27 | # Projects 28 | 29 | Types: 30 | 31 | - Project 32 | - ProjectUsage 33 | - ProjectListResponse 34 | 35 | Methods: 36 | 37 | - client.projects.retrieve(id) -> Project 38 | - client.projects.list() -> ProjectListResponse 39 | - client.projects.usage(id) -> ProjectUsage 40 | 41 | # Sessions 42 | 43 | Types: 44 | 45 | - Session 46 | - SessionLiveURLs 47 | - SessionCreateResponse 48 | - SessionRetrieveResponse 49 | - SessionListResponse 50 | 51 | Methods: 52 | 53 | - client.sessions.create({ ...params }) -> SessionCreateResponse 54 | - client.sessions.retrieve(id) -> SessionRetrieveResponse 55 | - client.sessions.update(id, { ...params }) -> Session 56 | - client.sessions.list({ ...params }) -> SessionListResponse 57 | - client.sessions.debug(id) -> SessionLiveURLs 58 | 59 | ## Downloads 60 | 61 | Methods: 62 | 63 | - client.sessions.downloads.list(id) -> Response 64 | 65 | ## Logs 66 | 67 | Types: 68 | 69 | - SessionLog 70 | - LogListResponse 71 | 72 | Methods: 73 | 74 | - client.sessions.logs.list(id) -> LogListResponse 75 | 76 | ## Recording 77 | 78 | Types: 79 | 80 | - SessionRecording 81 | - RecordingRetrieveResponse 82 | 83 | Methods: 84 | 85 | - client.sessions.recording.retrieve(id) -> RecordingRetrieveResponse 86 | 87 | ## Uploads 88 | 89 | Types: 90 | 91 | - UploadCreateResponse 92 | 93 | Methods: 94 | 95 | - client.sessions.uploads.create(id, { ...params }) -> UploadCreateResponse 96 | -------------------------------------------------------------------------------- /bin/check-release-environment: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | errors=() 4 | 5 | if [ -z "${NPM_TOKEN}" ]; then 6 | errors+=("The BROWSERBASE_NPM_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets") 7 | fi 8 | 9 | lenErrors=${#errors[@]} 10 | 11 | if [[ lenErrors -gt 0 ]]; then 12 | echo -e "Found the following errors in the release environment:\n" 13 | 14 | for error in "${errors[@]}"; do 15 | echo -e "- $error\n" 16 | done 17 | 18 | exit 1 19 | fi 20 | 21 | echo "The environment is ready to push releases!" 22 | 23 | -------------------------------------------------------------------------------- /bin/publish-npm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | npm config set '//registry.npmjs.org/:_authToken' "$NPM_TOKEN" 6 | 7 | # Build the project 8 | yarn build 9 | 10 | # Navigate to the dist directory 11 | cd dist 12 | 13 | # Get the version from package.json 14 | VERSION="$(node -p "require('./package.json').version")" 15 | 16 | # Extract the pre-release tag if it exists 17 | if [[ "$VERSION" =~ -([a-zA-Z]+) ]]; then 18 | # Extract the part before any dot in the pre-release identifier 19 | TAG="${BASH_REMATCH[1]}" 20 | else 21 | TAG="latest" 22 | fi 23 | 24 | # Publish with the appropriate tag 25 | yarn publish --access public --tag "$TAG" 26 | -------------------------------------------------------------------------------- /examples/packages/extensions/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserbase/sdk-node/6ea947537d9f4d1cfad723b44890bd52aae02642/examples/packages/extensions/.DS_Store -------------------------------------------------------------------------------- /examples/packages/extensions/.gitignore: -------------------------------------------------------------------------------- 1 | *.zip -------------------------------------------------------------------------------- /examples/packages/extensions/browserbase-test/hello.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Hello Extensions

4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/packages/extensions/browserbase-test/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserbase/sdk-node/6ea947537d9f4d1cfad723b44890bd52aae02642/examples/packages/extensions/browserbase-test/images/logo.png -------------------------------------------------------------------------------- /examples/packages/extensions/browserbase-test/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Browserbase Extension Test", 4 | "description": "Test extension for browserbase", 5 | "version": "1.0", 6 | "action": { 7 | "default_popup": "hello.html" 8 | }, 9 | "content_scripts": [ 10 | { 11 | "matches": ["https://www.browserbase.com/*"], 12 | "js": ["scripts/content.js"] 13 | } 14 | ], 15 | "web_accessible_resources": [ 16 | { 17 | "resources": ["images/logo.png"], 18 | "matches": ["https://www.browserbase.com/*"] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /examples/packages/extensions/browserbase-test/scripts/content.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable prettier/prettier */ 2 | const imageUrl = chrome.runtime.getURL('images/logo.png'); 3 | window 4 | .fetch(imageUrl) 5 | .then((response) => { 6 | if (response.ok) { 7 | console.log('browserbase test extension image loaded'); 8 | } 9 | }) 10 | .catch((error) => { 11 | console.log(error); 12 | }); 13 | -------------------------------------------------------------------------------- /examples/packages/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/browserbase/sdk-node/6ea947537d9f4d1cfad723b44890bd52aae02642/examples/packages/logo.png -------------------------------------------------------------------------------- /examples/playwright-basic.ts: -------------------------------------------------------------------------------- 1 | import Browserbase from '@browserbasehq/sdk'; 2 | import { chromium } from 'playwright-core'; 3 | 4 | const BROWSERBASE_API_KEY = process.env['BROWSERBASE_API_KEY']!; 5 | const BROWSERBASE_PROJECT_ID = process.env['BROWSERBASE_PROJECT_ID']!; 6 | 7 | const bb = new Browserbase({ 8 | apiKey: BROWSERBASE_API_KEY, 9 | }); 10 | 11 | (async () => { 12 | // Create a new session 13 | const session = await bb.sessions.create({ 14 | projectId: BROWSERBASE_PROJECT_ID, 15 | }); 16 | 17 | // Connect to the session 18 | const browser = await chromium.connectOverCDP(session.connectUrl); 19 | 20 | // Getting the default context to ensure the sessions are recorded. 21 | const [defaultContext] = browser.contexts(); 22 | const page = defaultContext?.pages()[0]; 23 | 24 | await page?.goto('https://browserbase.com/'); 25 | await page?.close(); 26 | await browser.close(); 27 | console.log(`Session complete! View replay at https://browserbase.com/sessions/${session.id}`); 28 | })().catch((error) => console.error(error.message)); 29 | -------------------------------------------------------------------------------- /examples/playwright-captcha.ts: -------------------------------------------------------------------------------- 1 | import Browserbase from '@browserbasehq/sdk'; 2 | import { chromium } from 'playwright-core'; 3 | 4 | // Configuration 5 | const DEFAULT_CAPTCHA_URL = 'https://2captcha.com/demo/recaptcha-v2'; 6 | const OVERRIDE_TIMEOUT = 60_000; // 1 minute in milliseconds 7 | const BROWSERBASE_PROJECT_ID = process.env['BROWSERBASE_PROJECT_ID']!; 8 | const BROWSERBASE_API_KEY = process.env['BROWSERBASE_API_KEY']!; 9 | 10 | const bb = new Browserbase({ 11 | apiKey: BROWSERBASE_API_KEY, 12 | }); 13 | 14 | async function solveCaptcha() { 15 | // Create a session with captcha solving enabled 16 | const session = await bb.sessions.create({ 17 | projectId: BROWSERBASE_PROJECT_ID, 18 | browserSettings: { 19 | solveCaptchas: true, 20 | }, 21 | proxies: true, 22 | }); 23 | try { 24 | // Connect to the browser 25 | const browser = await chromium.connectOverCDP(session.connectUrl); 26 | 27 | const context = browser.contexts()[0]!; 28 | const page = context.pages()[0]!; 29 | 30 | let captchaSolvingStarted = false; 31 | let captchaSolvingFinished = false; 32 | 33 | // Listen for console messages 34 | page.on('console', (msg) => { 35 | if (msg.text() === 'browserbase-solving-started') { 36 | captchaSolvingStarted = true; 37 | console.log('Captcha solving started...'); 38 | page.evaluate(() => { 39 | // @ts-ignore 40 | window.captchaSolvingFinished = false; 41 | }); 42 | } else if (msg.text() === 'browserbase-solving-finished') { 43 | captchaSolvingFinished = true; 44 | console.log('Captcha solving finished!'); 45 | page.evaluate(() => { 46 | // @ts-ignore 47 | window.captchaSolvingFinished = true; 48 | }); 49 | } 50 | }); 51 | 52 | // Navigate to the captcha page 53 | console.log('Navigating to captcha page...'); 54 | await page.goto(DEFAULT_CAPTCHA_URL, { waitUntil: 'networkidle' }); 55 | 56 | // Wait for captcha to be solved 57 | // @ts-ignore 58 | await page.waitForFunction(() => window.captchaSolvingFinished === true, null, { 59 | timeout: OVERRIDE_TIMEOUT, 60 | }); 61 | 62 | // Cleanup 63 | await page.close(); 64 | await browser.close(); 65 | 66 | console.log('Captcha solving started:', captchaSolvingStarted); 67 | console.log('Captcha solving finished:', captchaSolvingFinished); 68 | } catch (error) { 69 | console.error('An error occurred:', error); 70 | process.exit(1); 71 | } finally { 72 | console.log(`View replay at https://browserbase.com/sessions/${session.id}`); 73 | } 74 | } 75 | 76 | // Run the script 77 | solveCaptcha(); 78 | -------------------------------------------------------------------------------- /examples/playwright-context.ts: -------------------------------------------------------------------------------- 1 | import Browserbase from '@browserbasehq/sdk'; 2 | import { chromium, type Browser, type Cookie } from 'playwright-core'; 3 | 4 | // Configuration 5 | const CONTEXT_TEST_URL = 'https://www.browserbase.com'; 6 | const BROWSERBASE_PROJECT_ID = process.env['BROWSERBASE_PROJECT_ID']!; 7 | const BROWSERBASE_API_KEY = process.env['BROWSERBASE_API_KEY']!; 8 | 9 | const bb = new Browserbase({ 10 | apiKey: BROWSERBASE_API_KEY, 11 | }); 12 | 13 | // Helper functions 14 | function addHour(date: Date): number { 15 | const SECOND = 1000; 16 | return new Date(date.getTime() + 60 * 60 * 1000).getTime() / SECOND; 17 | } 18 | 19 | async function findCookie(browser: Browser, name: string): Promise { 20 | const [defaultContext] = browser.contexts(); 21 | const cookies = await defaultContext?.cookies(); 22 | return cookies?.find((cookie) => cookie.name === name); 23 | } 24 | 25 | async function runSessionWithContextPersistence() { 26 | let contextId: string; 27 | let sessionId: string; 28 | let testCookieName: string; 29 | let testCookieValue: string; 30 | 31 | try { 32 | // Step 1: Create a context 33 | console.log('Creating a new context...'); 34 | const context = await bb.contexts.create({ 35 | projectId: BROWSERBASE_PROJECT_ID, 36 | }); 37 | contextId = context.id; 38 | console.log(`Context created with ID: ${contextId}`); 39 | 40 | // Step 2: Create a session with the context 41 | console.log('Creating a session with the context...'); 42 | const session = await bb.sessions.create({ 43 | projectId: BROWSERBASE_PROJECT_ID, 44 | browserSettings: { 45 | context: { 46 | id: contextId, 47 | persist: true, 48 | }, 49 | }, 50 | }); 51 | sessionId = session.id; 52 | console.log(`Session created with ID: ${sessionId}`); 53 | 54 | // Step 3: Populate and persist the context 55 | console.log(`Populating context ${contextId} during session ${sessionId}`); 56 | const browser = await chromium.connectOverCDP(session.connectUrl); 57 | const browserContext = browser.contexts()[0]!; 58 | const page = browserContext.pages()[0]!; 59 | 60 | await page.goto(CONTEXT_TEST_URL, { waitUntil: 'domcontentloaded' }); 61 | 62 | // Set a random cookie on the page 63 | const now = new Date(); 64 | testCookieName = `bb_${now.getTime().toString()}`; 65 | testCookieValue = now.toISOString(); 66 | 67 | await browserContext.addCookies([ 68 | { 69 | domain: '.browserbase.com', 70 | expires: addHour(now), 71 | name: testCookieName, 72 | path: '/', 73 | value: testCookieValue, 74 | }, 75 | ]); 76 | 77 | console.log(`Set test cookie: ${testCookieName}=${testCookieValue}`); 78 | 79 | // Validate cookie persistence between pages 80 | await page.goto('https://www.google.com', { waitUntil: 'domcontentloaded' }); 81 | await page.goBack(); 82 | 83 | const cookie = await findCookie(browser, testCookieName); 84 | console.log('Cookie persisted between pages:', !!cookie); 85 | 86 | await page.close(); 87 | await browser.close(); 88 | 89 | // Wait for context to persist 90 | console.log('Waiting for context to persist...'); 91 | await new Promise((resolve) => setTimeout(resolve, 5000)); 92 | 93 | // Step 4: Create another session with the same context 94 | console.log('Creating a new session with the same context...'); 95 | const newSession = await bb.sessions.create({ 96 | projectId: BROWSERBASE_PROJECT_ID, 97 | browserSettings: { 98 | context: { 99 | id: contextId, 100 | }, 101 | }, 102 | }); 103 | sessionId = newSession.id; 104 | 105 | // Step 5: Verify previous state 106 | console.log(`Reusing context ${contextId} during session ${sessionId}`); 107 | const newBrowser = await chromium.connectOverCDP(newSession.connectUrl); 108 | const newPage = newBrowser.contexts()[0]!.pages()[0]!; 109 | 110 | await newPage.goto(CONTEXT_TEST_URL, { waitUntil: 'domcontentloaded' }); 111 | 112 | const foundCookie = await findCookie(newBrowser, testCookieName); 113 | console.log('Cookie found in new session:', !!foundCookie); 114 | console.log('Cookie value matches:', foundCookie?.value === testCookieValue); 115 | 116 | await newPage.close(); 117 | await newBrowser.close(); 118 | 119 | console.log('Context persistence test completed successfully!'); 120 | console.log(`View session replays at https://browserbase.com/sessions/${sessionId}`); 121 | } catch (error) { 122 | console.error('An error occurred:', error); 123 | process.exit(1); 124 | } 125 | } 126 | 127 | // Run the script 128 | runSessionWithContextPersistence(); 129 | -------------------------------------------------------------------------------- /examples/playwright-download.ts: -------------------------------------------------------------------------------- 1 | import Browserbase from '@browserbasehq/sdk'; 2 | import { chromium } from 'playwright-core'; 3 | import AdmZip from 'adm-zip'; 4 | 5 | // Configuration 6 | const BROWSERBASE_PROJECT_ID = process.env['BROWSERBASE_PROJECT_ID']!; 7 | const BROWSERBASE_API_KEY = process.env['BROWSERBASE_API_KEY']!; 8 | const DOWNLOAD_TEST_URL = 'https://browser-tests-alpha.vercel.app/api/download-test'; 9 | const EXPECTED_FILE_SIZE = 6137541; 10 | const downloadRe = /sandstorm-(\d{13})+\.mp3/; 11 | 12 | const bb = new Browserbase({ 13 | apiKey: BROWSERBASE_API_KEY, 14 | }); 15 | 16 | async function getDownload(sessionId: string): Promise { 17 | const response = await bb.sessions.downloads.list(sessionId); 18 | return Buffer.from(await response.arrayBuffer()); 19 | } 20 | 21 | async function runSessionWithDownload() { 22 | try { 23 | console.log('Creating a new session...'); 24 | const session = await bb.sessions.create({ 25 | projectId: BROWSERBASE_PROJECT_ID, 26 | }); 27 | 28 | console.log(`Session created with ID: ${session.id}`); 29 | const browser = await chromium.connectOverCDP(session.connectUrl); 30 | 31 | const context = browser.contexts()[0]!; 32 | const page = context.pages()[0]!; 33 | 34 | // Set up CDP session for download handling 35 | console.log('Configuring download behavior...'); 36 | const client = await context.newCDPSession(page); 37 | await client.send('Browser.setDownloadBehavior', { 38 | behavior: 'allow', 39 | downloadPath: 'downloads', 40 | eventsEnabled: true, 41 | }); 42 | 43 | // Navigate to download test page 44 | console.log('Navigating to download test page...'); 45 | await page.goto(DOWNLOAD_TEST_URL); 46 | 47 | // Initiate download 48 | console.log('Initiating download...'); 49 | const [download] = await Promise.all([page.waitForEvent('download'), page.locator('#download').click()]); 50 | 51 | const downloadError = await download.failure(); 52 | if (downloadError !== null) { 53 | throw new Error(`Download failed: ${downloadError}`); 54 | } 55 | 56 | // Cleanup browser 57 | await page.close(); 58 | await browser.close(); 59 | 60 | // Verify download 61 | console.log('Verifying download...'); 62 | const zipBuffer = await getDownload(session.id); 63 | 64 | if (zipBuffer.length === 0) { 65 | throw new Error('Download buffer is empty'); 66 | } 67 | 68 | const zip = new AdmZip(zipBuffer); 69 | const zipEntries = zip.getEntries(); 70 | const mp3Entry = zipEntries.find((entry) => downloadRe.test(entry.entryName)); 71 | 72 | if (!mp3Entry) { 73 | throw new Error( 74 | `Missing file matching "${downloadRe.toString()}" in zip entries: ${JSON.stringify( 75 | zipEntries.map((entry) => entry.entryName), 76 | )}`, 77 | ); 78 | } 79 | 80 | if (mp3Entry.header.size !== EXPECTED_FILE_SIZE) { 81 | throw new Error(`File size mismatch. Expected: ${EXPECTED_FILE_SIZE}, Got: ${mp3Entry.header.size}`); 82 | } 83 | 84 | console.log('Download test completed successfully!'); 85 | console.log(`File size verified: ${mp3Entry.header.size} bytes`); 86 | console.log(`View session replay at https://browserbase.com/sessions/${session.id}`); 87 | } catch (error) { 88 | console.error('An error occurred:', error); 89 | process.exit(1); 90 | } 91 | } 92 | 93 | // Run the script 94 | runSessionWithDownload(); 95 | -------------------------------------------------------------------------------- /examples/playwright-extension.ts: -------------------------------------------------------------------------------- 1 | import { createReadStream } from 'node:fs'; 2 | import { join } from 'node:path'; 3 | import { tmpdir } from 'node:os'; 4 | import { chromium } from 'playwright-core'; 5 | import AdmZip from 'adm-zip'; 6 | import Browserbase from '@browserbasehq/sdk'; 7 | 8 | // Configuration 9 | const BROWSERBASE_PROJECT_ID = process.env['BROWSERBASE_PROJECT_ID']!; 10 | const BROWSERBASE_API_KEY = process.env['BROWSERBASE_API_KEY']!; 11 | const BROWSERBASE_API_URL = process.env['BROWSERBASE_API_URL']!; 12 | const EXTENSION_TIMEOUT = 5000; // 5 seconds 13 | const PROXY_EXTENSION_TIMEOUT = 10000; // 10 seconds 14 | 15 | const bb = new Browserbase({ 16 | apiKey: BROWSERBASE_API_KEY, 17 | }); 18 | 19 | function zipExtension() { 20 | const zip = new AdmZip(); 21 | zip.addLocalFolder(join(__dirname, './packages/extensions/browserbase-test')); 22 | const name = `extension-${Date.now().toString()}.zip`; 23 | const dest = join(tmpdir(), name); 24 | zip.writeZip(dest); 25 | return { dest }; 26 | } 27 | 28 | async function createExtension() { 29 | const { dest } = zipExtension(); 30 | try { 31 | const res = await bb.extensions.create({ 32 | file: createReadStream(dest), 33 | }); 34 | 35 | return res.id; 36 | } catch (error) { 37 | console.error('Failed to create extension:', error); 38 | throw error; 39 | } 40 | } 41 | 42 | async function waitForExtensionMessage(page: any, timeout: number) { 43 | return new Promise((resolve, reject) => { 44 | const timeoutId = setTimeout(() => { 45 | reject(new Error('Extension message timeout exceeded')); 46 | }, timeout); 47 | 48 | page.on('console', (msg: any) => { 49 | if (msg.text().includes('browserbase test extension image loaded')) { 50 | clearTimeout(timeoutId); 51 | resolve(msg.text()); 52 | } 53 | }); 54 | }); 55 | } 56 | 57 | async function testExtension() { 58 | let extensionId: string; 59 | 60 | try { 61 | // Step 1: Create extension 62 | console.log('Creating extension...'); 63 | extensionId = await createExtension(); 64 | console.log(`Extension created with ID: ${extensionId}`); 65 | 66 | // Step 2: Test extension without proxies 67 | console.log('\nTesting extension without proxies...'); 68 | const session = await bb.sessions.create({ 69 | projectId: BROWSERBASE_PROJECT_ID, 70 | extensionId, 71 | }); 72 | 73 | const browser = await chromium.connectOverCDP(session.connectUrl); 74 | const context = browser.contexts()[0]!; 75 | const page = context.pages()[0]!; 76 | 77 | console.log('Navigating to test page...'); 78 | const messagePromise = waitForExtensionMessage(page, EXTENSION_TIMEOUT); 79 | await page.goto('https://www.browserbase.com/'); 80 | await messagePromise; 81 | console.log('Extension loaded successfully without proxies'); 82 | 83 | await page.close(); 84 | await browser.close(); 85 | console.log(`Session complete, view replay at https://browserbase.com/sessions/${session.id}`); 86 | 87 | // Step 3: Test extension with proxies 88 | console.log('\nTesting extension with proxies...'); 89 | const proxySession = await bb.sessions.create({ 90 | projectId: BROWSERBASE_PROJECT_ID, 91 | extensionId, 92 | proxies: true, 93 | }); 94 | 95 | const proxyBrowser = await chromium.connectOverCDP(proxySession.connectUrl); 96 | const proxyContext = proxyBrowser.contexts()[0]!; 97 | const proxyPage = proxyContext.pages()[0]!; 98 | 99 | console.log('Navigating to test page with proxies...'); 100 | const proxyMessagePromise = waitForExtensionMessage(proxyPage, PROXY_EXTENSION_TIMEOUT); 101 | await proxyPage.goto('https://www.browserbase.com/'); 102 | await proxyMessagePromise; 103 | console.log('Extension loaded successfully with proxies'); 104 | 105 | await proxyPage.close(); 106 | await proxyBrowser.close(); 107 | console.log(`Session complete, view replay at https://browserbase.com/sessions/${proxySession.id}`); 108 | } catch (error) { 109 | console.error('\nAn error occurred:', error); 110 | process.exit(1); 111 | } 112 | } 113 | 114 | // Run the script 115 | testExtension(); 116 | -------------------------------------------------------------------------------- /examples/playwright-proxy.ts: -------------------------------------------------------------------------------- 1 | import { chromium, type Browser } from 'playwright-core'; 2 | import Browserbase from '@browserbasehq/sdk'; 3 | 4 | // Configuration 5 | const BROWSERBASE_PROJECT_ID = process.env['BROWSERBASE_PROJECT_ID']!; 6 | const BROWSERBASE_API_KEY = process.env['BROWSERBASE_API_KEY']!; 7 | 8 | const bb = new Browserbase({ 9 | apiKey: BROWSERBASE_API_KEY, 10 | }); 11 | 12 | async function extractFromTable(browser: Browser, cell: string) { 13 | const context = browser.contexts()[0]!; 14 | const page = context.pages()[0]!; 15 | await page.goto('https://www.showmyip.com/'); 16 | await page.waitForSelector('table.iptab'); 17 | 18 | const td = page.locator(`table.iptab tr:has-text("${cell}") td:last-child`); 19 | const text = await td.textContent(); 20 | if (!text) { 21 | throw new Error(`Failed to extract ${cell}`); 22 | } 23 | return text.trim(); 24 | } 25 | 26 | async function testProxies() { 27 | try { 28 | // Test 1: Basic proxy functionality 29 | console.log('\nTesting basic proxy functionality...'); 30 | const session = await bb.sessions.create({ 31 | projectId: BROWSERBASE_PROJECT_ID, 32 | proxies: true, 33 | }); 34 | 35 | let browser = await chromium.connectOverCDP(session.connectUrl); 36 | const page = browser.contexts()[0]!.pages()[0]!; 37 | 38 | await page.goto('https://www.google.com'); 39 | const pageTitle = await page.title(); 40 | console.log('Page title:', pageTitle); 41 | 42 | await page.close(); 43 | await browser.close(); 44 | 45 | const updatedSession = await bb.sessions.retrieve(session.id); 46 | console.log('Proxy bytes used:', updatedSession.proxyBytes); 47 | console.log(`View session replay at https://browserbase.com/sessions/${session.id}`); 48 | 49 | // Test 2: Geolocation - Canada 50 | console.log('\nTesting proxy geolocation - Canada...'); 51 | const canadaSession = await bb.sessions.create({ 52 | projectId: BROWSERBASE_PROJECT_ID, 53 | proxies: [ 54 | { 55 | type: 'browserbase', 56 | geolocation: { 57 | country: 'CA', 58 | }, 59 | }, 60 | ], 61 | }); 62 | 63 | browser = await chromium.connectOverCDP(canadaSession.connectUrl); 64 | const country = await extractFromTable(browser, 'Country'); 65 | console.log('Detected country:', country); 66 | await browser.close(); 67 | console.log(`View session replay at https://browserbase.com/sessions/${canadaSession.id}`); 68 | 69 | // Test 3: Geolocation - New York 70 | console.log('\nTesting proxy geolocation - New York...'); 71 | const nySession = await bb.sessions.create({ 72 | projectId: BROWSERBASE_PROJECT_ID, 73 | proxies: [ 74 | { 75 | type: 'browserbase', 76 | geolocation: { 77 | country: 'US', 78 | state: 'NY', 79 | }, 80 | }, 81 | ], 82 | }); 83 | 84 | browser = await chromium.connectOverCDP(nySession.connectUrl); 85 | const state = await extractFromTable(browser, 'Region'); 86 | console.log('Detected state:', state); 87 | await browser.close(); 88 | console.log(`View session replay at https://browserbase.com/sessions/${nySession.id}`); 89 | 90 | // Test 4: Geolocation - Los Angeles 91 | console.log('\nTesting proxy geolocation - Los Angeles...'); 92 | const laSession = await bb.sessions.create({ 93 | projectId: BROWSERBASE_PROJECT_ID, 94 | proxies: [ 95 | { 96 | type: 'browserbase', 97 | geolocation: { 98 | country: 'US', 99 | state: 'CA', 100 | city: 'Los Angeles', 101 | }, 102 | }, 103 | ], 104 | }); 105 | 106 | browser = await chromium.connectOverCDP(laSession.connectUrl); 107 | const city = await extractFromTable(browser, 'City'); 108 | console.log('Detected city:', city); 109 | await browser.close(); 110 | console.log(`View session replay at https://browserbase.com/sessions/${laSession.id}`); 111 | 112 | // Test 5: Geolocation - London 113 | console.log('\nTesting proxy geolocation - London...'); 114 | const londonSession = await bb.sessions.create({ 115 | projectId: BROWSERBASE_PROJECT_ID, 116 | proxies: [ 117 | { 118 | type: 'browserbase', 119 | geolocation: { 120 | country: 'GB', 121 | city: 'London', 122 | }, 123 | }, 124 | ], 125 | }); 126 | 127 | browser = await chromium.connectOverCDP(londonSession.connectUrl); 128 | const londonCity = await extractFromTable(browser, 'City'); 129 | console.log('Detected city:', londonCity); 130 | await browser.close(); 131 | console.log(`View session replay at https://browserbase.com/sessions/${londonSession.id}`); 132 | 133 | console.log('\nProxy tests completed successfully!'); 134 | } catch (error) { 135 | console.error('An error occurred:', error); 136 | process.exit(1); 137 | } 138 | } 139 | 140 | // Run the script 141 | testProxies(); 142 | -------------------------------------------------------------------------------- /examples/playwright-upload.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'node:path'; 2 | import { chromium } from 'playwright-core'; 3 | import Browserbase from '@browserbasehq/sdk'; 4 | 5 | // Configuration 6 | const BROWSERBASE_PROJECT_ID = process.env['BROWSERBASE_PROJECT_ID']!; 7 | const BROWSERBASE_API_KEY = process.env['BROWSERBASE_API_KEY']!; 8 | const UPLOAD_TEST_URL = 'https://browser-tests-alpha.vercel.app/api/upload-test'; 9 | 10 | const bb = new Browserbase({ 11 | apiKey: BROWSERBASE_API_KEY, 12 | }); 13 | 14 | async function testFileUpload() { 15 | try { 16 | // Create a new session 17 | console.log('Creating a new session...'); 18 | const session = await bb.sessions.create({ 19 | projectId: BROWSERBASE_PROJECT_ID, 20 | }); 21 | 22 | console.log(`Session created with ID: ${session.id}`); 23 | const browser = await chromium.connectOverCDP(session.connectUrl); 24 | 25 | const context = browser.contexts()[0]!; 26 | const page = context.pages()[0]!; 27 | 28 | // Navigate to upload test page 29 | console.log('Navigating to upload test page...'); 30 | await page.goto(UPLOAD_TEST_URL); 31 | 32 | // Perform file upload 33 | console.log('Uploading file...'); 34 | const fileInput = page.locator('#fileUpload'); 35 | const filePath = join(__dirname, 'packages/logo.png'); 36 | await fileInput.setInputFiles(filePath); 37 | 38 | // Verify upload 39 | const fileNameSpan = page.locator('#fileName'); 40 | const fileName = await fileNameSpan.innerText(); 41 | 42 | const fileSizeSpan = page.locator('#fileSize'); 43 | const fileSize = Number(await fileSizeSpan.innerText()); 44 | 45 | console.log('Upload results:'); 46 | console.log('- File name:', fileName); 47 | console.log('- File size:', fileSize, 'bytes'); 48 | 49 | if (fileName === 'logo.png' && fileSize > 0) { 50 | console.log('File upload successful!'); 51 | } else { 52 | throw new Error('File upload verification failed'); 53 | } 54 | 55 | // Cleanup 56 | await page.close(); 57 | await browser.close(); 58 | 59 | console.log(`View session replay at https://browserbase.com/sessions/${session.id}`); 60 | } catch (error) { 61 | console.error('An error occurred:', error); 62 | process.exit(1); 63 | } 64 | } 65 | 66 | // Run the script 67 | testFileUpload(); 68 | -------------------------------------------------------------------------------- /examples/puppeteer-basic.ts: -------------------------------------------------------------------------------- 1 | import { connect } from 'puppeteer-core'; 2 | import Browserbase from '@browserbasehq/sdk'; 3 | 4 | // Configuration 5 | const BROWSERBASE_PROJECT_ID = process.env['BROWSERBASE_PROJECT_ID']!; 6 | const BROWSERBASE_API_KEY = process.env['BROWSERBASE_API_KEY']!; 7 | 8 | const bb = new Browserbase({ 9 | apiKey: BROWSERBASE_API_KEY, 10 | }); 11 | 12 | async function runPuppeteerSession() { 13 | try { 14 | // Create a new session 15 | console.log('Creating a new session...'); 16 | const session = await bb.sessions.create({ 17 | projectId: BROWSERBASE_PROJECT_ID, 18 | }); 19 | console.log(`Session created with ID: ${session.id}`); 20 | 21 | // Connect to the browser using Puppeteer 22 | console.log('Connecting to browser...'); 23 | const browser = await connect({ 24 | browserWSEndpoint: session.connectUrl, 25 | defaultViewport: null, 26 | }); 27 | 28 | // Create a new page 29 | const [page] = await browser.pages(); 30 | if (!page) { 31 | throw new Error('No pages available'); 32 | } 33 | 34 | // Navigate to a website 35 | console.log('Navigating to Hacker News...'); 36 | await page.goto('https://news.ycombinator.com/'); 37 | const pageTitle = await page.title(); 38 | console.log('Page title:', pageTitle); 39 | 40 | // Cleanup 41 | await browser.disconnect(); 42 | 43 | console.log('Session completed successfully!'); 44 | console.log(`View session replay at https://browserbase.com/sessions/${session.id}`); 45 | } catch (error) { 46 | console.error('An error occurred:', error); 47 | process.exit(1); 48 | } 49 | } 50 | 51 | // Run the script 52 | runPuppeteerSession(); 53 | -------------------------------------------------------------------------------- /examples/puppeteer-proxy.ts: -------------------------------------------------------------------------------- 1 | import { Browser, connect } from 'puppeteer-core'; 2 | import Browserbase from '@browserbasehq/sdk'; 3 | 4 | // Configuration 5 | const BROWSERBASE_PROJECT_ID = process.env['BROWSERBASE_PROJECT_ID']!; 6 | const BROWSERBASE_API_KEY = process.env['BROWSERBASE_API_KEY']!; 7 | 8 | const bb = new Browserbase({ 9 | apiKey: BROWSERBASE_API_KEY, 10 | }); 11 | 12 | async function extractFromTable(browser: Browser, cell: string) { 13 | const [page] = await browser.pages(); 14 | if (!page) { 15 | throw new Error('No pages available'); 16 | } 17 | await page.goto('https://www.showmyip.com/'); 18 | await page.waitForSelector('table.iptab'); 19 | 20 | const text = await page.evaluate((cell) => { 21 | // @ts-expect-error 22 | const rows = Array.from(document.querySelectorAll('table.iptab tr')); 23 | for (const row of rows) { 24 | // @ts-expect-error 25 | const cells = row.querySelectorAll('td'); 26 | if (cells.length > 1 && cells[0].textContent?.trim() === cell) { 27 | return cells[cells.length - 1].textContent?.trim(); 28 | } 29 | } 30 | return null; 31 | }, cell); 32 | 33 | if (!text) { 34 | throw new Error(`Failed to extract ${cell}`); 35 | } 36 | return text; 37 | } 38 | 39 | async function testProxies() { 40 | try { 41 | // Test 1: Basic proxy functionality 42 | console.log('\nTesting basic proxy functionality...'); 43 | const session = await bb.sessions.create({ 44 | projectId: BROWSERBASE_PROJECT_ID, 45 | proxies: true, 46 | }); 47 | 48 | let browser = await connect({ 49 | browserWSEndpoint: session.connectUrl, 50 | defaultViewport: null, 51 | }); 52 | 53 | const [page] = await browser.pages(); 54 | if (!page) { 55 | throw new Error('No pages available'); 56 | } 57 | 58 | await page.goto('https://www.google.com/'); 59 | const pageTitle = await page.title(); 60 | console.log('Page title:', pageTitle); 61 | 62 | await browser.disconnect(); 63 | 64 | console.log(`View session replay at https://browserbase.com/sessions/${session.id}`); 65 | 66 | // Test 2: Geolocation - Canada 67 | console.log('\nTesting proxy geolocation - Canada...'); 68 | const canadaSession = await bb.sessions.create({ 69 | projectId: BROWSERBASE_PROJECT_ID, 70 | proxies: [ 71 | { 72 | type: 'browserbase', 73 | geolocation: { 74 | country: 'CA', 75 | }, 76 | }, 77 | ], 78 | }); 79 | 80 | browser = await connect({ 81 | browserWSEndpoint: canadaSession.connectUrl, 82 | defaultViewport: null, 83 | }); 84 | 85 | const country = await extractFromTable(browser, 'Country'); 86 | console.log('Detected country:', country); 87 | await browser.disconnect(); 88 | console.log(`View session replay at https://browserbase.com/sessions/${canadaSession.id}`); 89 | 90 | // Test 3: Geolocation - New York 91 | console.log('\nTesting proxy geolocation - New York...'); 92 | const nySession = await bb.sessions.create({ 93 | projectId: BROWSERBASE_PROJECT_ID, 94 | proxies: [ 95 | { 96 | type: 'browserbase', 97 | geolocation: { 98 | country: 'US', 99 | state: 'NY', 100 | }, 101 | }, 102 | ], 103 | }); 104 | 105 | browser = await connect({ 106 | browserWSEndpoint: nySession.connectUrl, 107 | defaultViewport: null, 108 | }); 109 | 110 | const state = await extractFromTable(browser, 'Region'); 111 | console.log('Detected state:', state); 112 | await browser.disconnect(); 113 | console.log(`View session replay at https://browserbase.com/sessions/${nySession.id}`); 114 | 115 | // Test 4: Geolocation - Los Angeles 116 | console.log('\nTesting proxy geolocation - Los Angeles...'); 117 | const laSession = await bb.sessions.create({ 118 | projectId: BROWSERBASE_PROJECT_ID, 119 | proxies: [ 120 | { 121 | type: 'browserbase', 122 | geolocation: { 123 | country: 'US', 124 | state: 'CA', 125 | city: 'Los Angeles', 126 | }, 127 | }, 128 | ], 129 | }); 130 | 131 | browser = await connect({ 132 | browserWSEndpoint: laSession.connectUrl, 133 | defaultViewport: null, 134 | }); 135 | 136 | const city = await extractFromTable(browser, 'City'); 137 | console.log('Detected city:', city); 138 | await browser.disconnect(); 139 | console.log(`View session replay at https://browserbase.com/sessions/${laSession.id}`); 140 | 141 | // Test 5: Geolocation - London 142 | console.log('\nTesting proxy geolocation - London...'); 143 | const londonSession = await bb.sessions.create({ 144 | projectId: BROWSERBASE_PROJECT_ID, 145 | proxies: [ 146 | { 147 | type: 'browserbase', 148 | geolocation: { 149 | country: 'GB', 150 | city: 'London', 151 | }, 152 | }, 153 | ], 154 | }); 155 | 156 | browser = await connect({ 157 | browserWSEndpoint: londonSession.connectUrl, 158 | defaultViewport: null, 159 | }); 160 | 161 | const londonCity = await extractFromTable(browser, 'City'); 162 | console.log('Detected city:', londonCity); 163 | await browser.disconnect(); 164 | console.log(`View session replay at https://browserbase.com/sessions/${londonSession.id}`); 165 | 166 | console.log('\nProxy tests completed successfully!'); 167 | } catch (error) { 168 | console.error('An error occurred:', error); 169 | process.exit(1); 170 | } 171 | } 172 | 173 | // Run the script 174 | testProxies(); 175 | -------------------------------------------------------------------------------- /examples/selenium-basic.ts: -------------------------------------------------------------------------------- 1 | import http, { type ClientRequest } from 'node:http'; 2 | import { Builder } from 'selenium-webdriver'; 3 | import Browserbase from '@browserbasehq/sdk'; 4 | 5 | // Configuration 6 | const BROWSERBASE_PROJECT_ID = process.env['BROWSERBASE_PROJECT_ID']!; 7 | const BROWSERBASE_API_KEY = process.env['BROWSERBASE_API_KEY']!; 8 | 9 | const bb = new Browserbase({ 10 | apiKey: BROWSERBASE_API_KEY, 11 | }); 12 | 13 | async function runSeleniumSession() { 14 | try { 15 | // Create a new session 16 | console.log('Creating a new session...'); 17 | const session = await bb.sessions.create({ 18 | projectId: BROWSERBASE_PROJECT_ID, 19 | }); 20 | console.log(`Session created with ID: ${session.id}`); 21 | 22 | // Configure HTTP agent for Selenium 23 | const customHttpAgent = new http.Agent({}); 24 | // @ts-expect-error -- accessing private method 25 | customHttpAgent.addRequest = (req: ClientRequest, options: unknown) => { 26 | req.setHeader('session-id', session.id); 27 | req.setHeader('x-bb-api-key', BROWSERBASE_API_KEY); 28 | // @ts-expect-error -- accessing private method 29 | (http.Agent.prototype.addRequest as (...args: any[]) => unknown).call(customHttpAgent, req, options); 30 | }; 31 | 32 | // Configure Selenium WebDriver 33 | console.log('Connecting to browser...'); 34 | const seleniumRemoteUrl = session.seleniumRemoteUrl; 35 | 36 | const driver = new Builder() 37 | .forBrowser('chrome') 38 | .usingHttpAgent(customHttpAgent) 39 | .usingServer(seleniumRemoteUrl) 40 | .build(); 41 | 42 | // Navigate to a website 43 | console.log('Navigating to Hacker News...'); 44 | await driver.get('https://news.ycombinator.com/'); 45 | const pageTitle = await driver.getTitle(); 46 | console.log('Page title:', pageTitle); 47 | 48 | // Test with Selenium Remote URL 49 | console.log('\nTesting with Selenium Remote URL...'); 50 | const customHttpAgentWithSigningKey = new http.Agent({}); 51 | // @ts-expect-error -- accessing private method 52 | customHttpAgentWithSigningKey.addRequest = (req: ClientRequest, options: unknown) => { 53 | req.setHeader('x-bb-signing-key', session.signingKey); 54 | // @ts-expect-error -- accessing private method 55 | (http.Agent.prototype.addRequest as (...args: any[]) => unknown).call( 56 | customHttpAgentWithSigningKey, 57 | req, 58 | options, 59 | ); 60 | }; 61 | 62 | const seleniumDriver = new Builder() 63 | .forBrowser('chrome') 64 | .usingHttpAgent(customHttpAgentWithSigningKey) 65 | .usingServer(session.seleniumRemoteUrl) 66 | .build(); 67 | 68 | await seleniumDriver.get('https://news.ycombinator.com/'); 69 | const seleniumPageTitle = await seleniumDriver.getTitle(); 70 | console.log('Page title (Selenium Remote URL):', seleniumPageTitle); 71 | 72 | // Cleanup 73 | await driver.quit(); 74 | await seleniumDriver.quit(); 75 | 76 | console.log('Session completed successfully!'); 77 | console.log(`View session replay at https://browserbase.com/sessions/${session.id}`); 78 | } catch (error) { 79 | console.error('An error occurred:', error); 80 | process.exit(1); 81 | } 82 | } 83 | 84 | // Run the script 85 | runSeleniumSession(); 86 | -------------------------------------------------------------------------------- /examples/selenium-upload.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import http, { type ClientRequest } from 'node:http'; 3 | import { join } from 'node:path'; 4 | import { Builder, By, until } from 'selenium-webdriver'; 5 | import Browserbase from '@browserbasehq/sdk'; 6 | 7 | // Configuration 8 | const BROWSERBASE_PROJECT_ID = process.env['BROWSERBASE_PROJECT_ID']!; 9 | const BROWSERBASE_API_KEY = process.env['BROWSERBASE_API_KEY']!; 10 | const UPLOAD_TEST_URL = 'https://browser-tests-alpha.vercel.app/api/upload-test'; 11 | const UPLOAD_TIMEOUT = 10_000; // 10 seconds 12 | 13 | const bb = new Browserbase({ 14 | apiKey: BROWSERBASE_API_KEY, 15 | }); 16 | 17 | async function testFileUpload() { 18 | try { 19 | // Create a new session 20 | console.log('Creating a new session...'); 21 | const session = await bb.sessions.create({ 22 | projectId: BROWSERBASE_PROJECT_ID, 23 | }); 24 | console.log(`Session created with ID: ${session.id}`); 25 | 26 | // Upload file to session 27 | const localFilePath = join(__dirname, 'packages/logo.png'); 28 | const remoteFilePath = '/tmp/.uploads/logo.png'; 29 | 30 | console.log('Uploading file to session...'); 31 | await bb.sessions.uploads.create(session.id, { 32 | file: fs.createReadStream(localFilePath), 33 | }); 34 | 35 | // Configure HTTP agent for Selenium 36 | const customHttpAgent = new http.Agent({}); 37 | // @ts-expect-error -- accessing private method 38 | customHttpAgent.addRequest = (req: ClientRequest, options: unknown) => { 39 | req.setHeader('session-id', session.id); 40 | req.setHeader('x-bb-api-key', BROWSERBASE_API_KEY); 41 | // @ts-expect-error -- accessing private method 42 | (http.Agent.prototype.addRequest as (...args: any[]) => unknown).call(customHttpAgent, req, options); 43 | }; 44 | 45 | // Configure Selenium WebDriver 46 | console.log('Connecting to browser...'); 47 | const driver = new Builder() 48 | .forBrowser('chrome') 49 | .usingHttpAgent(customHttpAgent) 50 | .usingServer(session.seleniumRemoteUrl) 51 | .build(); 52 | 53 | // Navigate to upload test page 54 | console.log('Navigating to upload test page...'); 55 | await driver.get(UPLOAD_TEST_URL); 56 | 57 | // Perform file upload 58 | console.log('Uploading file...'); 59 | const fileInput = await driver.wait(until.elementLocated(By.css('#fileUpload')), UPLOAD_TIMEOUT); 60 | await fileInput.sendKeys(remoteFilePath); 61 | 62 | // Verify upload 63 | const fileNameSpan = await driver.findElement(By.css('#fileName')); 64 | const fileName = await fileNameSpan.getText(); 65 | 66 | const fileSizeSpan = await driver.findElement(By.css('#fileSize')); 67 | const fileSize = Number(await fileSizeSpan.getText()); 68 | 69 | console.log('Upload results:'); 70 | console.log('- File name:', fileName); 71 | console.log('- File size:', fileSize, 'bytes'); 72 | 73 | if (fileName === 'logo.png' && fileSize > 0) { 74 | console.log('File upload successful!'); 75 | } else { 76 | throw new Error('File upload verification failed'); 77 | } 78 | 79 | // Optional: Take screenshot for debugging 80 | // const screenshot = await driver.takeScreenshot(); 81 | // await writeFile('selenium-upload.png', screenshot, 'base64'); 82 | 83 | // Cleanup 84 | await driver.quit(); 85 | 86 | console.log('Upload test completed successfully!'); 87 | console.log(`View session replay at https://browserbase.com/sessions/${session.id}`); 88 | } catch (error) { 89 | console.error('An error occurred:', error); 90 | process.exit(1); 91 | } 92 | } 93 | 94 | // Run the script 95 | testFileUpload(); 96 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { JestConfigWithTsJest } from 'ts-jest'; 2 | 3 | const config: JestConfigWithTsJest = { 4 | preset: 'ts-jest/presets/default-esm', 5 | testEnvironment: 'node', 6 | transform: { 7 | '^.+\\.(t|j)sx?$': ['@swc/jest', { sourceMaps: 'inline' }], 8 | }, 9 | moduleNameMapper: { 10 | '^@browserbasehq/sdk$': '/src/index.ts', 11 | '^@browserbasehq/sdk/_shims/auto/(.*)$': '/src/_shims/auto/$1-node', 12 | '^@browserbasehq/sdk/(.*)$': '/src/$1', 13 | }, 14 | modulePathIgnorePatterns: [ 15 | '/ecosystem-tests/', 16 | '/dist/', 17 | '/deno/', 18 | '/deno_tests/', 19 | ], 20 | testPathIgnorePatterns: ['scripts'], 21 | }; 22 | 23 | export default config; 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@browserbasehq/sdk", 3 | "version": "2.6.0", 4 | "description": "The official Node.js library for the Browserbase API", 5 | "author": "Browserbase ", 6 | "types": "dist/index.d.ts", 7 | "main": "dist/index.js", 8 | "type": "commonjs", 9 | "repository": "github:browserbase/sdk-node", 10 | "packageManager": "yarn@1.22.22", 11 | "files": [ 12 | "**/*" 13 | ], 14 | "private": false, 15 | "scripts": { 16 | "test": "./scripts/test", 17 | "build": "./scripts/build", 18 | "prepublishOnly": "echo 'to publish, run yarn build && (cd dist; yarn publish)' && exit 1", 19 | "format": "prettier --write --cache --cache-strategy metadata . !dist", 20 | "prepare": "if ./scripts/utils/check-is-in-git-install.sh; then ./scripts/build && ./scripts/utils/git-swap.sh; fi", 21 | "tsn": "ts-node -r tsconfig-paths/register", 22 | "lint": "./scripts/lint", 23 | "fix": "./scripts/format" 24 | }, 25 | "dependencies": { 26 | "@types/node": "^18.11.18", 27 | "@types/node-fetch": "^2.6.4", 28 | "abort-controller": "^3.0.0", 29 | "agentkeepalive": "^4.2.1", 30 | "form-data-encoder": "1.7.2", 31 | "formdata-node": "^4.3.2", 32 | "node-fetch": "^2.6.7" 33 | }, 34 | "devDependencies": { 35 | "@swc/core": "^1.3.102", 36 | "@swc/jest": "^0.2.29", 37 | "@types/adm-zip": "^0.5.5", 38 | "@types/jest": "^29.4.0", 39 | "@types/selenium-webdriver": "^4.1.26", 40 | "@typescript-eslint/eslint-plugin": "^6.7.0", 41 | "@typescript-eslint/parser": "^6.7.0", 42 | "adm-zip": "^0.5.16", 43 | "eslint": "^8.49.0", 44 | "eslint-plugin-prettier": "^5.0.1", 45 | "eslint-plugin-unused-imports": "^3.0.0", 46 | "iconv-lite": "^0.6.3", 47 | "jest": "^29.4.0", 48 | "playwright-core": "^1.42.1", 49 | "prettier": "^3.0.0", 50 | "puppeteer-core": "^19.7.2", 51 | "selenium-webdriver": "^4.2.0", 52 | "ts-jest": "^29.1.0", 53 | "ts-node": "^10.5.0", 54 | "tsc-multi": "^1.1.0", 55 | "tsconfig-paths": "^4.0.0", 56 | "tsx": "^4.15.0", 57 | "typescript": "^5.3.3" 58 | }, 59 | "sideEffects": [ 60 | "./_shims/index.js", 61 | "./_shims/index.mjs", 62 | "./shims/node.js", 63 | "./shims/node.mjs", 64 | "./shims/web.js", 65 | "./shims/web.mjs" 66 | ], 67 | "imports": { 68 | "@browserbasehq/sdk": ".", 69 | "@browserbasehq/sdk/*": "./src/*" 70 | }, 71 | "exports": { 72 | "./_shims/auto/*": { 73 | "deno": { 74 | "types": "./dist/_shims/auto/*.d.ts", 75 | "require": "./dist/_shims/auto/*.js", 76 | "default": "./dist/_shims/auto/*.mjs" 77 | }, 78 | "bun": { 79 | "types": "./dist/_shims/auto/*.d.ts", 80 | "require": "./dist/_shims/auto/*-bun.js", 81 | "default": "./dist/_shims/auto/*-bun.mjs" 82 | }, 83 | "browser": { 84 | "types": "./dist/_shims/auto/*.d.ts", 85 | "require": "./dist/_shims/auto/*.js", 86 | "default": "./dist/_shims/auto/*.mjs" 87 | }, 88 | "worker": { 89 | "types": "./dist/_shims/auto/*.d.ts", 90 | "require": "./dist/_shims/auto/*.js", 91 | "default": "./dist/_shims/auto/*.mjs" 92 | }, 93 | "workerd": { 94 | "types": "./dist/_shims/auto/*.d.ts", 95 | "require": "./dist/_shims/auto/*.js", 96 | "default": "./dist/_shims/auto/*.mjs" 97 | }, 98 | "node": { 99 | "types": "./dist/_shims/auto/*-node.d.ts", 100 | "require": "./dist/_shims/auto/*-node.js", 101 | "default": "./dist/_shims/auto/*-node.mjs" 102 | }, 103 | "types": "./dist/_shims/auto/*.d.ts", 104 | "require": "./dist/_shims/auto/*.js", 105 | "default": "./dist/_shims/auto/*.mjs" 106 | }, 107 | ".": { 108 | "require": { 109 | "types": "./dist/index.d.ts", 110 | "default": "./dist/index.js" 111 | }, 112 | "types": "./dist/index.d.mts", 113 | "default": "./dist/index.mjs" 114 | }, 115 | "./*.mjs": { 116 | "types": "./dist/*.d.ts", 117 | "default": "./dist/*.mjs" 118 | }, 119 | "./*.js": { 120 | "types": "./dist/*.d.ts", 121 | "default": "./dist/*.js" 122 | }, 123 | "./*": { 124 | "types": "./dist/*.d.ts", 125 | "require": "./dist/*.js", 126 | "default": "./dist/*.mjs" 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": { 3 | ".": {} 4 | }, 5 | "$schema": "https://raw.githubusercontent.com/stainless-api/release-please/main/schemas/config.json", 6 | "include-v-in-tag": true, 7 | "include-component-in-tag": false, 8 | "versioning": "prerelease", 9 | "prerelease": true, 10 | "bump-minor-pre-major": true, 11 | "bump-patch-for-minor-pre-major": false, 12 | "pull-request-header": "Automated Release PR", 13 | "pull-request-title-pattern": "release: ${version}", 14 | "changelog-sections": [ 15 | { 16 | "type": "feat", 17 | "section": "Features" 18 | }, 19 | { 20 | "type": "fix", 21 | "section": "Bug Fixes" 22 | }, 23 | { 24 | "type": "perf", 25 | "section": "Performance Improvements" 26 | }, 27 | { 28 | "type": "revert", 29 | "section": "Reverts" 30 | }, 31 | { 32 | "type": "chore", 33 | "section": "Chores" 34 | }, 35 | { 36 | "type": "docs", 37 | "section": "Documentation" 38 | }, 39 | { 40 | "type": "style", 41 | "section": "Styles" 42 | }, 43 | { 44 | "type": "refactor", 45 | "section": "Refactors" 46 | }, 47 | { 48 | "type": "test", 49 | "section": "Tests", 50 | "hidden": true 51 | }, 52 | { 53 | "type": "build", 54 | "section": "Build System" 55 | }, 56 | { 57 | "type": "ci", 58 | "section": "Continuous Integration", 59 | "hidden": true 60 | } 61 | ], 62 | "release-type": "node", 63 | "extra-files": ["src/version.ts", "README.md"] 64 | } 65 | -------------------------------------------------------------------------------- /scripts/bootstrap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ]; then 8 | brew bundle check >/dev/null 2>&1 || { 9 | echo "==> Installing Homebrew dependencies…" 10 | brew bundle 11 | } 12 | fi 13 | 14 | echo "==> Installing Node dependencies…" 15 | 16 | PACKAGE_MANAGER=$(command -v yarn >/dev/null 2>&1 && echo "yarn" || echo "npm") 17 | 18 | $PACKAGE_MANAGER install 19 | -------------------------------------------------------------------------------- /scripts/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -exuo pipefail 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | node scripts/utils/check-version.cjs 8 | 9 | # Build into dist and will publish the package from there, 10 | # so that src/resources/foo.ts becomes /resources/foo.js 11 | # This way importing from `"@browserbasehq/sdk/resources/foo"` works 12 | # even with `"moduleResolution": "node"` 13 | 14 | rm -rf dist; mkdir dist 15 | # Copy src to dist/src and build from dist/src into dist, so that 16 | # the source map for index.js.map will refer to ./src/index.ts etc 17 | cp -rp src README.md dist 18 | rm dist/src/_shims/*-deno.ts dist/src/_shims/auto/*-deno.ts 19 | for file in LICENSE CHANGELOG.md; do 20 | if [ -e "${file}" ]; then cp "${file}" dist; fi 21 | done 22 | if [ -e "bin/cli" ]; then 23 | mkdir dist/bin 24 | cp -p "bin/cli" dist/bin/; 25 | fi 26 | # this converts the export map paths for the dist directory 27 | # and does a few other minor things 28 | node scripts/utils/make-dist-package-json.cjs > dist/package.json 29 | 30 | # build to .js/.mjs/.d.ts files 31 | npm exec tsc-multi 32 | # copy over handwritten .js/.mjs/.d.ts files 33 | cp src/_shims/*.{d.ts,js,mjs,md} dist/_shims 34 | cp src/_shims/auto/*.{d.ts,js,mjs} dist/_shims/auto 35 | # we need to add exports = module.exports = Browserbase to index.js; 36 | # No way to get that from index.ts because it would cause compile errors 37 | # when building .mjs 38 | node scripts/utils/fix-index-exports.cjs 39 | # with "moduleResolution": "nodenext", if ESM resolves to index.d.ts, 40 | # it'll have TS errors on the default import. But if it resolves to 41 | # index.d.mts the default import will work (even though both files have 42 | # the same export default statement) 43 | cp dist/index.d.ts dist/index.d.mts 44 | cp tsconfig.dist-src.json dist/src/tsconfig.json 45 | 46 | node scripts/utils/postprocess-files.cjs 47 | 48 | # make sure that nothing crashes when we require the output CJS or 49 | # import the output ESM 50 | (cd dist && node -e 'require("@browserbasehq/sdk")') 51 | (cd dist && node -e 'import("@browserbasehq/sdk")' --input-type=module) 52 | 53 | if [ -e ./scripts/build-deno ] 54 | then 55 | ./scripts/build-deno 56 | fi 57 | -------------------------------------------------------------------------------- /scripts/format: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | echo "==> Running eslint --fix" 8 | ESLINT_USE_FLAT_CONFIG="false" ./node_modules/.bin/eslint --fix --ext ts,js . 9 | -------------------------------------------------------------------------------- /scripts/lint: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | echo "==> Running eslint" 8 | ESLINT_USE_FLAT_CONFIG="false" ./node_modules/.bin/eslint --ext ts,js . 9 | 10 | echo "==> Running tsc" 11 | ./node_modules/.bin/tsc --noEmit 12 | -------------------------------------------------------------------------------- /scripts/mock: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | if [[ -n "$1" && "$1" != '--'* ]]; then 8 | URL="$1" 9 | shift 10 | else 11 | URL="$(grep 'openapi_spec_url' .stats.yml | cut -d' ' -f2)" 12 | fi 13 | 14 | # Check if the URL is empty 15 | if [ -z "$URL" ]; then 16 | echo "Error: No OpenAPI spec path/url provided or found in .stats.yml" 17 | exit 1 18 | fi 19 | 20 | echo "==> Starting mock server with URL ${URL}" 21 | 22 | # Run prism mock on the given spec 23 | if [ "$1" == "--daemon" ]; then 24 | npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" &> .prism.log & 25 | 26 | # Wait for server to come online 27 | echo -n "Waiting for server" 28 | while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do 29 | echo -n "." 30 | sleep 0.1 31 | done 32 | 33 | if grep -q "✖ fatal" ".prism.log"; then 34 | cat .prism.log 35 | exit 1 36 | fi 37 | 38 | echo 39 | else 40 | npm exec --package=@stainless-api/prism-cli@5.8.5 -- prism mock "$URL" 41 | fi 42 | -------------------------------------------------------------------------------- /scripts/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | cd "$(dirname "$0")/.." 6 | 7 | RED='\033[0;31m' 8 | GREEN='\033[0;32m' 9 | YELLOW='\033[0;33m' 10 | NC='\033[0m' # No Color 11 | 12 | function prism_is_running() { 13 | curl --silent "http://localhost:4010" >/dev/null 2>&1 14 | } 15 | 16 | kill_server_on_port() { 17 | pids=$(lsof -t -i tcp:"$1" || echo "") 18 | if [ "$pids" != "" ]; then 19 | kill "$pids" 20 | echo "Stopped $pids." 21 | fi 22 | } 23 | 24 | function is_overriding_api_base_url() { 25 | [ -n "$TEST_API_BASE_URL" ] 26 | } 27 | 28 | if ! is_overriding_api_base_url && ! prism_is_running ; then 29 | # When we exit this script, make sure to kill the background mock server process 30 | trap 'kill_server_on_port 4010' EXIT 31 | 32 | # Start the dev server 33 | ./scripts/mock --daemon 34 | fi 35 | 36 | if is_overriding_api_base_url ; then 37 | echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}" 38 | echo 39 | elif ! prism_is_running ; then 40 | echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server" 41 | echo -e "running against your OpenAPI spec." 42 | echo 43 | echo -e "To run the server, pass in the path or url of your OpenAPI" 44 | echo -e "spec to the prism command:" 45 | echo 46 | echo -e " \$ ${YELLOW}npm exec --package=@stoplight/prism-cli@~5.3.2 -- prism mock path/to/your.openapi.yml${NC}" 47 | echo 48 | 49 | exit 1 50 | else 51 | echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}" 52 | echo 53 | fi 54 | 55 | echo "==> Running tests" 56 | ./node_modules/.bin/jest "$@" 57 | -------------------------------------------------------------------------------- /scripts/utils/check-is-in-git-install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Check if you happen to call prepare for a repository that's already in node_modules. 3 | [ "$(basename "$(dirname "$PWD")")" = 'node_modules' ] || 4 | # The name of the containing directory that 'npm` uses, which looks like 5 | # $HOME/.npm/_cacache/git-cloneXXXXXX 6 | [ "$(basename "$(dirname "$PWD")")" = 'tmp' ] || 7 | # The name of the containing directory that 'yarn` uses, which looks like 8 | # $(yarn cache dir)/.tmp/XXXXX 9 | [ "$(basename "$(dirname "$PWD")")" = '.tmp' ] 10 | -------------------------------------------------------------------------------- /scripts/utils/check-version.cjs: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const main = () => { 5 | const pkg = require('../../package.json'); 6 | const version = pkg['version']; 7 | if (!version) throw 'The version property is not set in the package.json file'; 8 | if (typeof version !== 'string') { 9 | throw `Unexpected type for the package.json version field; got ${typeof version}, expected string`; 10 | } 11 | 12 | const versionFile = path.resolve(__dirname, '..', '..', 'src', 'version.ts'); 13 | const contents = fs.readFileSync(versionFile, 'utf8'); 14 | const output = contents.replace(/(export const VERSION = ')(.*)(')/g, `$1${version}$3`); 15 | fs.writeFileSync(versionFile, output); 16 | }; 17 | 18 | if (require.main === module) { 19 | main(); 20 | } 21 | -------------------------------------------------------------------------------- /scripts/utils/fix-index-exports.cjs: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const indexJs = 5 | process.env['DIST_PATH'] ? 6 | path.resolve(process.env['DIST_PATH'], 'index.js') 7 | : path.resolve(__dirname, '..', '..', 'dist', 'index.js'); 8 | 9 | let before = fs.readFileSync(indexJs, 'utf8'); 10 | let after = before.replace( 11 | /^\s*exports\.default\s*=\s*(\w+)/m, 12 | 'exports = module.exports = $1;\nexports.default = $1', 13 | ); 14 | fs.writeFileSync(indexJs, after, 'utf8'); 15 | -------------------------------------------------------------------------------- /scripts/utils/git-swap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -exuo pipefail 3 | # the package is published to NPM from ./dist 4 | # we want the final file structure for git installs to match the npm installs, so we 5 | 6 | # delete everything except ./dist and ./node_modules 7 | find . -maxdepth 1 -mindepth 1 ! -name 'dist' ! -name 'node_modules' -exec rm -rf '{}' + 8 | 9 | # move everything from ./dist to . 10 | mv dist/* . 11 | 12 | # delete the now-empty ./dist 13 | rmdir dist 14 | -------------------------------------------------------------------------------- /scripts/utils/make-dist-package-json.cjs: -------------------------------------------------------------------------------- 1 | const pkgJson = require(process.env['PKG_JSON_PATH'] || '../../package.json'); 2 | 3 | function processExportMap(m) { 4 | for (const key in m) { 5 | const value = m[key]; 6 | if (typeof value === 'string') m[key] = value.replace(/^\.\/dist\//, './'); 7 | else processExportMap(value); 8 | } 9 | } 10 | processExportMap(pkgJson.exports); 11 | 12 | for (const key of ['types', 'main', 'module']) { 13 | if (typeof pkgJson[key] === 'string') pkgJson[key] = pkgJson[key].replace(/^(\.\/)?dist\//, './'); 14 | } 15 | 16 | delete pkgJson.devDependencies; 17 | delete pkgJson.scripts.prepack; 18 | delete pkgJson.scripts.prepublishOnly; 19 | delete pkgJson.scripts.prepare; 20 | 21 | console.log(JSON.stringify(pkgJson, null, 2)); 22 | -------------------------------------------------------------------------------- /scripts/utils/postprocess-files.cjs: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { parse } = require('@typescript-eslint/parser'); 4 | 5 | const pkgImportPath = process.env['PKG_IMPORT_PATH'] ?? '@browserbasehq/sdk/'; 6 | 7 | const distDir = 8 | process.env['DIST_PATH'] ? 9 | path.resolve(process.env['DIST_PATH']) 10 | : path.resolve(__dirname, '..', '..', 'dist'); 11 | const distSrcDir = path.join(distDir, 'src'); 12 | 13 | /** 14 | * Quick and dirty AST traversal 15 | */ 16 | function traverse(node, visitor) { 17 | if (!node || typeof node.type !== 'string') return; 18 | visitor.node?.(node); 19 | visitor[node.type]?.(node); 20 | for (const key in node) { 21 | const value = node[key]; 22 | if (Array.isArray(value)) { 23 | for (const elem of value) traverse(elem, visitor); 24 | } else if (value instanceof Object) { 25 | traverse(value, visitor); 26 | } 27 | } 28 | } 29 | 30 | /** 31 | * Helper method for replacing arbitrary ranges of text in input code. 32 | * 33 | * The `replacer` is a function that will be called with a mini-api. For example: 34 | * 35 | * replaceRanges('foobar', ({ replace }) => replace([0, 3], 'baz')) // 'bazbar' 36 | * 37 | * The replaced ranges must not be overlapping. 38 | */ 39 | function replaceRanges(code, replacer) { 40 | const replacements = []; 41 | replacer({ replace: (range, replacement) => replacements.push({ range, replacement }) }); 42 | 43 | if (!replacements.length) return code; 44 | replacements.sort((a, b) => a.range[0] - b.range[0]); 45 | const overlapIndex = replacements.findIndex( 46 | (r, index) => index > 0 && replacements[index - 1].range[1] > r.range[0], 47 | ); 48 | if (overlapIndex >= 0) { 49 | throw new Error( 50 | `replacements overlap: ${JSON.stringify(replacements[overlapIndex - 1])} and ${JSON.stringify( 51 | replacements[overlapIndex], 52 | )}`, 53 | ); 54 | } 55 | 56 | const parts = []; 57 | let end = 0; 58 | for (const { 59 | range: [from, to], 60 | replacement, 61 | } of replacements) { 62 | if (from > end) parts.push(code.substring(end, from)); 63 | parts.push(replacement); 64 | end = to; 65 | } 66 | if (end < code.length) parts.push(code.substring(end)); 67 | return parts.join(''); 68 | } 69 | 70 | /** 71 | * Like calling .map(), where the iteratee is called on the path in every import or export from statement. 72 | * @returns the transformed code 73 | */ 74 | function mapModulePaths(code, iteratee) { 75 | const ast = parse(code, { range: true }); 76 | return replaceRanges(code, ({ replace }) => 77 | traverse(ast, { 78 | node(node) { 79 | switch (node.type) { 80 | case 'ImportDeclaration': 81 | case 'ExportNamedDeclaration': 82 | case 'ExportAllDeclaration': 83 | case 'ImportExpression': 84 | if (node.source) { 85 | const { range, value } = node.source; 86 | const transformed = iteratee(value); 87 | if (transformed !== value) { 88 | replace(range, JSON.stringify(transformed)); 89 | } 90 | } 91 | } 92 | }, 93 | }), 94 | ); 95 | } 96 | 97 | async function* walk(dir) { 98 | for await (const d of await fs.promises.opendir(dir)) { 99 | const entry = path.join(dir, d.name); 100 | if (d.isDirectory()) yield* walk(entry); 101 | else if (d.isFile()) yield entry; 102 | } 103 | } 104 | 105 | async function postprocess() { 106 | for await (const file of walk(path.resolve(__dirname, '..', '..', 'dist'))) { 107 | if (!/\.([cm]?js|(\.d)?[cm]?ts)$/.test(file)) continue; 108 | 109 | const code = await fs.promises.readFile(file, 'utf8'); 110 | 111 | let transformed = mapModulePaths(code, (importPath) => { 112 | if (file.startsWith(distSrcDir)) { 113 | if (importPath.startsWith(pkgImportPath)) { 114 | // convert self-references in dist/src to relative paths 115 | let relativePath = path.relative( 116 | path.dirname(file), 117 | path.join(distSrcDir, importPath.substring(pkgImportPath.length)), 118 | ); 119 | if (!relativePath.startsWith('.')) relativePath = `./${relativePath}`; 120 | return relativePath; 121 | } 122 | return importPath; 123 | } 124 | if (importPath.startsWith('.')) { 125 | // add explicit file extensions to relative imports 126 | const { dir, name } = path.parse(importPath); 127 | const ext = /\.mjs$/.test(file) ? '.mjs' : '.js'; 128 | return `${dir}/${name}${ext}`; 129 | } 130 | return importPath; 131 | }); 132 | 133 | if (file.startsWith(distSrcDir) && !file.endsWith('_shims/index.d.ts')) { 134 | // strip out `unknown extends Foo ? never :` shim guards in dist/src 135 | // to prevent errors from appearing in Go To Source 136 | transformed = transformed.replace( 137 | new RegExp('unknown extends (typeof )?\\S+ \\? \\S+ :\\s*'.replace(/\s+/, '\\s+'), 'gm'), 138 | // replace with same number of characters to avoid breaking source maps 139 | (match) => ' '.repeat(match.length), 140 | ); 141 | } 142 | 143 | if (file.endsWith('.d.ts')) { 144 | // work around bad tsc behavior 145 | // if we have `import { type Readable } from '@browserbasehq/sdk/_shims/index'`, 146 | // tsc sometimes replaces `Readable` with `import("stream").Readable` inline 147 | // in the output .d.ts 148 | transformed = transformed.replace(/import\("stream"\).Readable/g, 'Readable'); 149 | } 150 | 151 | // strip out lib="dom" and types="node" references; these are needed at build time, 152 | // but would pollute the user's TS environment 153 | transformed = transformed.replace( 154 | /^ *\/\/\/ * ' '.repeat(match.length - 1) + '\n', 157 | ); 158 | 159 | if (transformed !== code) { 160 | await fs.promises.writeFile(file, transformed, 'utf8'); 161 | console.error(`wrote ${path.relative(process.cwd(), file)}`); 162 | } 163 | } 164 | } 165 | postprocess(); 166 | -------------------------------------------------------------------------------- /scripts/utils/upload-artifact.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -exuo pipefail 3 | 4 | RESPONSE=$(curl -X POST "$URL" \ 5 | -H "Authorization: Bearer $AUTH" \ 6 | -H "Content-Type: application/json") 7 | 8 | SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') 9 | 10 | if [[ "$SIGNED_URL" == "null" ]]; then 11 | echo -e "\033[31mFailed to get signed URL.\033[0m" 12 | exit 1 13 | fi 14 | 15 | UPLOAD_RESPONSE=$(tar -cz dist | curl -v -X PUT \ 16 | -H "Content-Type: application/gzip" \ 17 | --data-binary @- "$SIGNED_URL" 2>&1) 18 | 19 | if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then 20 | echo -e "\033[32mUploaded build to Stainless storage.\033[0m" 21 | echo -e "\033[32mInstallation: npm install 'https://pkg.stainless.com/s/browserbase-node/$SHA'\033[0m" 22 | else 23 | echo -e "\033[31mFailed to upload artifact.\033[0m" 24 | exit 1 25 | fi 26 | -------------------------------------------------------------------------------- /src/_shims/MultipartBody.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export class MultipartBody { 5 | constructor(public body: any) {} 6 | get [Symbol.toStringTag](): string { 7 | return 'MultipartBody'; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/_shims/README.md: -------------------------------------------------------------------------------- 1 | # 👋 Wondering what everything in here does? 2 | 3 | `@browserbasehq/sdk` supports a wide variety of runtime environments like Node.js, Deno, Bun, browsers, and various 4 | edge runtimes, as well as both CommonJS (CJS) and EcmaScript Modules (ESM). 5 | 6 | To do this, `@browserbasehq/sdk` provides shims for either using `node-fetch` when in Node (because `fetch` is still experimental there) or the global `fetch` API built into the environment when not in Node. 7 | 8 | It uses [conditional exports](https://nodejs.org/api/packages.html#conditional-exports) to 9 | automatically select the correct shims for each environment. However, conditional exports are a fairly new 10 | feature and not supported everywhere. For instance, the TypeScript `"moduleResolution": "node"` 11 | 12 | setting doesn't consult the `exports` map, compared to `"moduleResolution": "nodeNext"`, which does. 13 | Unfortunately that's still the default setting, and it can result in errors like 14 | getting the wrong raw `Response` type from `.asResponse()`, for example. 15 | 16 | The user can work around these issues by manually importing one of: 17 | 18 | - `import '@browserbasehq/sdk/shims/node'` 19 | - `import '@browserbasehq/sdk/shims/web'` 20 | 21 | All of the code here in `_shims` handles selecting the automatic default shims or manual overrides. 22 | 23 | ### How it works - Runtime 24 | 25 | Runtime shims get installed by calling `setShims` exported by `@browserbasehq/sdk/_shims/registry`. 26 | 27 | Manually importing `@browserbasehq/sdk/shims/node` or `@browserbasehq/sdk/shims/web`, calls `setShims` with the respective runtime shims. 28 | 29 | All client code imports shims from `@browserbasehq/sdk/_shims/index`, which: 30 | 31 | - checks if shims have been set manually 32 | - if not, calls `setShims` with the shims from `@browserbasehq/sdk/_shims/auto/runtime` 33 | - re-exports the installed shims from `@browserbasehq/sdk/_shims/registry`. 34 | 35 | `@browserbasehq/sdk/_shims/auto/runtime` exports web runtime shims. 36 | If the `node` export condition is set, the export map replaces it with `@browserbasehq/sdk/_shims/auto/runtime-node`. 37 | 38 | ### How it works - Type time 39 | 40 | All client code imports shim types from `@browserbasehq/sdk/_shims/index`, which selects the manual types from `@browserbasehq/sdk/_shims/manual-types` if they have been declared, otherwise it exports the auto types from `@browserbasehq/sdk/_shims/auto/types`. 41 | 42 | `@browserbasehq/sdk/_shims/manual-types` exports an empty namespace. 43 | Manually importing `@browserbasehq/sdk/shims/node` or `@browserbasehq/sdk/shims/web` merges declarations into this empty namespace, so they get picked up by `@browserbasehq/sdk/_shims/index`. 44 | 45 | `@browserbasehq/sdk/_shims/auto/types` exports web type definitions. 46 | If the `node` export condition is set, the export map replaces it with `@browserbasehq/sdk/_shims/auto/types-node`, though TS only picks this up if `"moduleResolution": "nodenext"` or `"moduleResolution": "bundler"`. 47 | -------------------------------------------------------------------------------- /src/_shims/auto/runtime-bun.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export * from '../bun-runtime'; 5 | -------------------------------------------------------------------------------- /src/_shims/auto/runtime-deno.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export * from '../web-runtime'; 5 | -------------------------------------------------------------------------------- /src/_shims/auto/runtime-node.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export * from '../node-runtime'; 5 | -------------------------------------------------------------------------------- /src/_shims/auto/runtime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export * from '../web-runtime'; 5 | -------------------------------------------------------------------------------- /src/_shims/auto/types-deno.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export * from '../web-types'; 5 | -------------------------------------------------------------------------------- /src/_shims/auto/types-node.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export * from '../node-types'; 5 | -------------------------------------------------------------------------------- /src/_shims/auto/types.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export type Agent = any; 5 | 6 | // @ts-ignore 7 | declare const _fetch: unknown extends typeof fetch ? never : typeof fetch; 8 | export { _fetch as fetch }; 9 | 10 | // @ts-ignore 11 | type _Request = unknown extends Request ? never : Request; 12 | export { _Request as Request }; 13 | 14 | // @ts-ignore 15 | type _RequestInfo = unknown extends RequestInfo ? never : RequestInfo; 16 | export { type _RequestInfo as RequestInfo }; 17 | 18 | // @ts-ignore 19 | type _RequestInit = unknown extends RequestInit ? never : RequestInit; 20 | export { type _RequestInit as RequestInit }; 21 | 22 | // @ts-ignore 23 | type _Response = unknown extends Response ? never : Response; 24 | export { _Response as Response }; 25 | 26 | // @ts-ignore 27 | type _ResponseInit = unknown extends ResponseInit ? never : ResponseInit; 28 | export { type _ResponseInit as ResponseInit }; 29 | 30 | // @ts-ignore 31 | type _ResponseType = unknown extends ResponseType ? never : ResponseType; 32 | export { type _ResponseType as ResponseType }; 33 | 34 | // @ts-ignore 35 | type _BodyInit = unknown extends BodyInit ? never : BodyInit; 36 | export { type _BodyInit as BodyInit }; 37 | 38 | // @ts-ignore 39 | type _Headers = unknown extends Headers ? never : Headers; 40 | export { _Headers as Headers }; 41 | 42 | // @ts-ignore 43 | type _HeadersInit = unknown extends HeadersInit ? never : HeadersInit; 44 | export { type _HeadersInit as HeadersInit }; 45 | 46 | type EndingType = 'native' | 'transparent'; 47 | 48 | export interface BlobPropertyBag { 49 | endings?: EndingType; 50 | type?: string; 51 | } 52 | 53 | export interface FilePropertyBag extends BlobPropertyBag { 54 | lastModified?: number; 55 | } 56 | 57 | export type FileFromPathOptions = Omit; 58 | 59 | // @ts-ignore 60 | type _FormData = unknown extends FormData ? never : FormData; 61 | // @ts-ignore 62 | declare const _FormData: unknown extends typeof FormData ? never : typeof FormData; 63 | export { _FormData as FormData }; 64 | 65 | // @ts-ignore 66 | type _File = unknown extends File ? never : File; 67 | // @ts-ignore 68 | declare const _File: unknown extends typeof File ? never : typeof File; 69 | export { _File as File }; 70 | 71 | // @ts-ignore 72 | type _Blob = unknown extends Blob ? never : Blob; 73 | // @ts-ignore 74 | declare const _Blob: unknown extends typeof Blob ? never : typeof Blob; 75 | export { _Blob as Blob }; 76 | 77 | export declare class Readable { 78 | readable: boolean; 79 | readonly readableEnded: boolean; 80 | readonly readableFlowing: boolean | null; 81 | readonly readableHighWaterMark: number; 82 | readonly readableLength: number; 83 | readonly readableObjectMode: boolean; 84 | destroyed: boolean; 85 | read(size?: number): any; 86 | pause(): this; 87 | resume(): this; 88 | isPaused(): boolean; 89 | destroy(error?: Error): this; 90 | [Symbol.asyncIterator](): AsyncIterableIterator; 91 | } 92 | 93 | export declare class FsReadStream extends Readable { 94 | path: {}; // node type is string | Buffer 95 | } 96 | 97 | // @ts-ignore 98 | type _ReadableStream = unknown extends ReadableStream ? never : ReadableStream; 99 | // @ts-ignore 100 | declare const _ReadableStream: unknown extends typeof ReadableStream ? never : typeof ReadableStream; 101 | export { _ReadableStream as ReadableStream }; 102 | -------------------------------------------------------------------------------- /src/_shims/auto/types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/auto/types.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/bun-runtime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import { type Shims } from './registry'; 5 | import { getRuntime as getWebRuntime } from './web-runtime'; 6 | import { ReadStream as FsReadStream } from 'node:fs'; 7 | 8 | export function getRuntime(): Shims { 9 | const runtime = getWebRuntime(); 10 | function isFsReadStream(value: any): value is FsReadStream { 11 | return value instanceof FsReadStream; 12 | } 13 | return { ...runtime, isFsReadStream }; 14 | } 15 | -------------------------------------------------------------------------------- /src/_shims/index-deno.ts: -------------------------------------------------------------------------------- 1 | import { MultipartBody } from './MultipartBody'; 2 | import { type RequestOptions } from '../core'; 3 | 4 | export const kind: string = 'web'; 5 | 6 | export type Agent = any; 7 | 8 | const _fetch = fetch; 9 | type _fetch = typeof fetch; 10 | export { _fetch as fetch }; 11 | 12 | const _Request = Request; 13 | type _Request = Request; 14 | export { _Request as Request }; 15 | 16 | type _RequestInfo = RequestInfo; 17 | export { type _RequestInfo as RequestInfo }; 18 | 19 | type _RequestInit = RequestInit; 20 | export { type _RequestInit as RequestInit }; 21 | 22 | const _Response = Response; 23 | type _Response = Response; 24 | export { _Response as Response }; 25 | 26 | type _ResponseInit = ResponseInit; 27 | export { type _ResponseInit as ResponseInit }; 28 | 29 | type _ResponseType = ResponseType; 30 | export { type _ResponseType as ResponseType }; 31 | 32 | type _BodyInit = BodyInit; 33 | export { type _BodyInit as BodyInit }; 34 | 35 | const _Headers = Headers; 36 | type _Headers = Headers; 37 | export { _Headers as Headers }; 38 | 39 | type _HeadersInit = HeadersInit; 40 | export { type _HeadersInit as HeadersInit }; 41 | 42 | type EndingType = 'native' | 'transparent'; 43 | 44 | export interface BlobPropertyBag { 45 | endings?: EndingType; 46 | type?: string; 47 | } 48 | 49 | export interface FilePropertyBag extends BlobPropertyBag { 50 | lastModified?: number; 51 | } 52 | 53 | export type FileFromPathOptions = Omit; 54 | 55 | const _FormData = FormData; 56 | type _FormData = FormData; 57 | export { _FormData as FormData }; 58 | 59 | const _File = File; 60 | type _File = File; 61 | export { _File as File }; 62 | 63 | const _Blob = Blob; 64 | type _Blob = Blob; 65 | export { _Blob as Blob }; 66 | 67 | export async function getMultipartRequestOptions>( 68 | form: FormData, 69 | opts: RequestOptions, 70 | ): Promise> { 71 | return { 72 | ...opts, 73 | body: new MultipartBody(form) as any, 74 | }; 75 | } 76 | 77 | export function getDefaultAgent(url: string) { 78 | return undefined; 79 | } 80 | export function fileFromPath() { 81 | throw new Error( 82 | 'The `fileFromPath` function is only supported in Node. See the README for more details: https://www.github.com/browserbase/sdk-node#file-uploads', 83 | ); 84 | } 85 | 86 | export const isFsReadStream = (value: any) => false; 87 | 88 | export declare class Readable { 89 | readable: boolean; 90 | readonly readableEnded: boolean; 91 | readonly readableFlowing: boolean | null; 92 | readonly readableHighWaterMark: number; 93 | readonly readableLength: number; 94 | readonly readableObjectMode: boolean; 95 | destroyed: boolean; 96 | read(size?: number): any; 97 | pause(): this; 98 | resume(): this; 99 | isPaused(): boolean; 100 | destroy(error?: Error): this; 101 | [Symbol.asyncIterator](): AsyncIterableIterator; 102 | } 103 | 104 | export declare class FsReadStream extends Readable { 105 | path: {}; // node type is string | Buffer 106 | } 107 | 108 | const _ReadableStream = ReadableStream; 109 | type _ReadableStream = ReadableStream; 110 | export { _ReadableStream as ReadableStream }; 111 | 112 | export const init = () => {}; 113 | -------------------------------------------------------------------------------- /src/_shims/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import { manual } from './manual-types'; 5 | import * as auto from '@browserbasehq/sdk/_shims/auto/types'; 6 | import { type RequestOptions } from '../core'; 7 | 8 | type SelectType = unknown extends Manual ? Auto : Manual; 9 | 10 | export const kind: string; 11 | 12 | // @ts-ignore 13 | export type Agent = SelectType; 14 | 15 | // @ts-ignore 16 | export const fetch: SelectType; 17 | 18 | // @ts-ignore 19 | export type Request = SelectType; 20 | // @ts-ignore 21 | export type RequestInfo = SelectType; 22 | // @ts-ignore 23 | export type RequestInit = SelectType; 24 | 25 | // @ts-ignore 26 | export type Response = SelectType; 27 | // @ts-ignore 28 | export type ResponseInit = SelectType; 29 | // @ts-ignore 30 | export type ResponseType = SelectType; 31 | // @ts-ignore 32 | export type BodyInit = SelectType; 33 | // @ts-ignore 34 | export type Headers = SelectType; 35 | // @ts-ignore 36 | export const Headers: SelectType; 37 | // @ts-ignore 38 | export type HeadersInit = SelectType; 39 | 40 | // @ts-ignore 41 | export type BlobPropertyBag = SelectType; 42 | // @ts-ignore 43 | export type FilePropertyBag = SelectType; 44 | // @ts-ignore 45 | export type FileFromPathOptions = SelectType; 46 | // @ts-ignore 47 | export type FormData = SelectType; 48 | // @ts-ignore 49 | export const FormData: SelectType; 50 | // @ts-ignore 51 | export type File = SelectType; 52 | // @ts-ignore 53 | export const File: SelectType; 54 | // @ts-ignore 55 | export type Blob = SelectType; 56 | // @ts-ignore 57 | export const Blob: SelectType; 58 | 59 | // @ts-ignore 60 | export type Readable = SelectType; 61 | // @ts-ignore 62 | export type FsReadStream = SelectType; 63 | // @ts-ignore 64 | export type ReadableStream = SelectType; 65 | // @ts-ignore 66 | export const ReadableStream: SelectType; 67 | 68 | export function getMultipartRequestOptions>( 69 | form: FormData, 70 | opts: RequestOptions, 71 | ): Promise>; 72 | 73 | export function getDefaultAgent(url: string): any; 74 | 75 | // @ts-ignore 76 | export type FileFromPathOptions = SelectType; 77 | 78 | export function fileFromPath(path: string, options?: FileFromPathOptions): Promise; 79 | export function fileFromPath(path: string, filename?: string, options?: FileFromPathOptions): Promise; 80 | 81 | export function isFsReadStream(value: any): value is FsReadStream; 82 | 83 | export const init: () => void; 84 | -------------------------------------------------------------------------------- /src/_shims/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | const shims = require('./registry'); 5 | const auto = require('@browserbasehq/sdk/_shims/auto/runtime'); 6 | exports.init = () => { 7 | if (!shims.kind) shims.setShims(auto.getRuntime(), { auto: true }); 8 | }; 9 | for (const property of Object.keys(shims)) { 10 | Object.defineProperty(exports, property, { 11 | get() { 12 | return shims[property]; 13 | }, 14 | }); 15 | } 16 | 17 | exports.init(); 18 | -------------------------------------------------------------------------------- /src/_shims/index.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import * as shims from './registry.mjs'; 5 | import * as auto from '@browserbasehq/sdk/_shims/auto/runtime'; 6 | export const init = () => { 7 | if (!shims.kind) shims.setShims(auto.getRuntime(), { auto: true }); 8 | }; 9 | export * from './registry.mjs'; 10 | 11 | init(); 12 | -------------------------------------------------------------------------------- /src/_shims/manual-types.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | /** 5 | * Types will get added to this namespace when you import one of the following: 6 | * 7 | * import '@browserbasehq/sdk/shims/node' 8 | * import '@browserbasehq/sdk/shims/web' 9 | * 10 | * Importing more than one will cause type and runtime errors. 11 | */ 12 | export namespace manual {} 13 | -------------------------------------------------------------------------------- /src/_shims/manual-types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/manual-types.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/node-runtime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import * as nf from 'node-fetch'; 5 | import * as fd from 'formdata-node'; 6 | import { type File, type FilePropertyBag } from 'formdata-node'; 7 | import KeepAliveAgent from 'agentkeepalive'; 8 | import { AbortController as AbortControllerPolyfill } from 'abort-controller'; 9 | import { ReadStream as FsReadStream } from 'node:fs'; 10 | import { type Agent } from 'node:http'; 11 | import { FormDataEncoder } from 'form-data-encoder'; 12 | import { Readable } from 'node:stream'; 13 | import { type RequestOptions } from '../core'; 14 | import { MultipartBody } from './MultipartBody'; 15 | import { type Shims } from './registry'; 16 | import { ReadableStream } from 'node:stream/web'; 17 | 18 | type FileFromPathOptions = Omit; 19 | 20 | let fileFromPathWarned = false; 21 | 22 | /** 23 | * @deprecated use fs.createReadStream('./my/file.txt') instead 24 | */ 25 | async function fileFromPath(path: string): Promise; 26 | async function fileFromPath(path: string, filename?: string): Promise; 27 | async function fileFromPath(path: string, options?: FileFromPathOptions): Promise; 28 | async function fileFromPath(path: string, filename?: string, options?: FileFromPathOptions): Promise; 29 | async function fileFromPath(path: string, ...args: any[]): Promise { 30 | // this import fails in environments that don't handle export maps correctly, like old versions of Jest 31 | const { fileFromPath: _fileFromPath } = await import('formdata-node/file-from-path'); 32 | 33 | if (!fileFromPathWarned) { 34 | console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path)}) instead`); 35 | fileFromPathWarned = true; 36 | } 37 | // @ts-ignore 38 | return await _fileFromPath(path, ...args); 39 | } 40 | 41 | const defaultHttpAgent: Agent = new KeepAliveAgent({ keepAlive: true, timeout: 5 * 60 * 1000 }); 42 | const defaultHttpsAgent: Agent = new KeepAliveAgent.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1000 }); 43 | 44 | async function getMultipartRequestOptions>( 45 | form: fd.FormData, 46 | opts: RequestOptions, 47 | ): Promise> { 48 | const encoder = new FormDataEncoder(form); 49 | const readable = Readable.from(encoder); 50 | const body = new MultipartBody(readable); 51 | const headers = { 52 | ...opts.headers, 53 | ...encoder.headers, 54 | 'Content-Length': encoder.contentLength, 55 | }; 56 | 57 | return { ...opts, body: body as any, headers }; 58 | } 59 | 60 | export function getRuntime(): Shims { 61 | // Polyfill global object if needed. 62 | if (typeof AbortController === 'undefined') { 63 | // @ts-expect-error (the types are subtly different, but compatible in practice) 64 | globalThis.AbortController = AbortControllerPolyfill; 65 | } 66 | return { 67 | kind: 'node', 68 | fetch: nf.default, 69 | Request: nf.Request, 70 | Response: nf.Response, 71 | Headers: nf.Headers, 72 | FormData: fd.FormData, 73 | Blob: fd.Blob, 74 | File: fd.File, 75 | ReadableStream, 76 | getMultipartRequestOptions, 77 | getDefaultAgent: (url: string): Agent => (url.startsWith('https') ? defaultHttpsAgent : defaultHttpAgent), 78 | fileFromPath, 79 | isFsReadStream: (value: any): value is FsReadStream => value instanceof FsReadStream, 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /src/_shims/node-types.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import * as nf from 'node-fetch'; 5 | import * as fd from 'formdata-node'; 6 | 7 | export { type Agent } from 'node:http'; 8 | export { type Readable } from 'node:stream'; 9 | export { type ReadStream as FsReadStream } from 'node:fs'; 10 | export { ReadableStream } from 'node:stream/web'; 11 | 12 | export const fetch: typeof nf.default; 13 | 14 | export type Request = nf.Request; 15 | export type RequestInfo = nf.RequestInfo; 16 | export type RequestInit = nf.RequestInit; 17 | 18 | export type Response = nf.Response; 19 | export type ResponseInit = nf.ResponseInit; 20 | export type ResponseType = nf.ResponseType; 21 | export type BodyInit = nf.BodyInit; 22 | export type Headers = nf.Headers; 23 | export type HeadersInit = nf.HeadersInit; 24 | 25 | type EndingType = 'native' | 'transparent'; 26 | export interface BlobPropertyBag { 27 | endings?: EndingType; 28 | type?: string; 29 | } 30 | 31 | export interface FilePropertyBag extends BlobPropertyBag { 32 | lastModified?: number; 33 | } 34 | 35 | export type FileFromPathOptions = Omit; 36 | 37 | export type FormData = fd.FormData; 38 | export const FormData: typeof fd.FormData; 39 | export type File = fd.File; 40 | export const File: typeof fd.File; 41 | export type Blob = fd.Blob; 42 | export const Blob: typeof fd.Blob; 43 | -------------------------------------------------------------------------------- /src/_shims/node-types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/node-types.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/registry.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import { type RequestOptions } from '../core'; 5 | 6 | export interface Shims { 7 | kind: string; 8 | fetch: any; 9 | Request: any; 10 | Response: any; 11 | Headers: any; 12 | FormData: any; 13 | Blob: any; 14 | File: any; 15 | ReadableStream: any; 16 | getMultipartRequestOptions: >( 17 | form: Shims['FormData'], 18 | opts: RequestOptions, 19 | ) => Promise>; 20 | getDefaultAgent: (url: string) => any; 21 | fileFromPath: 22 | | ((path: string, filename?: string, options?: {}) => Promise) 23 | | ((path: string, options?: {}) => Promise); 24 | isFsReadStream: (value: any) => boolean; 25 | } 26 | 27 | export let auto = false; 28 | export let kind: Shims['kind'] | undefined = undefined; 29 | export let fetch: Shims['fetch'] | undefined = undefined; 30 | export let Request: Shims['Request'] | undefined = undefined; 31 | export let Response: Shims['Response'] | undefined = undefined; 32 | export let Headers: Shims['Headers'] | undefined = undefined; 33 | export let FormData: Shims['FormData'] | undefined = undefined; 34 | export let Blob: Shims['Blob'] | undefined = undefined; 35 | export let File: Shims['File'] | undefined = undefined; 36 | export let ReadableStream: Shims['ReadableStream'] | undefined = undefined; 37 | export let getMultipartRequestOptions: Shims['getMultipartRequestOptions'] | undefined = undefined; 38 | export let getDefaultAgent: Shims['getDefaultAgent'] | undefined = undefined; 39 | export let fileFromPath: Shims['fileFromPath'] | undefined = undefined; 40 | export let isFsReadStream: Shims['isFsReadStream'] | undefined = undefined; 41 | 42 | export function setShims(shims: Shims, options: { auto: boolean } = { auto: false }) { 43 | if (auto) { 44 | throw new Error( 45 | `you must \`import '@browserbasehq/sdk/shims/${shims.kind}'\` before importing anything else from @browserbasehq/sdk`, 46 | ); 47 | } 48 | if (kind) { 49 | throw new Error( 50 | `can't \`import '@browserbasehq/sdk/shims/${shims.kind}'\` after \`import '@browserbasehq/sdk/shims/${kind}'\``, 51 | ); 52 | } 53 | auto = options.auto; 54 | kind = shims.kind; 55 | fetch = shims.fetch; 56 | Request = shims.Request; 57 | Response = shims.Response; 58 | Headers = shims.Headers; 59 | FormData = shims.FormData; 60 | Blob = shims.Blob; 61 | File = shims.File; 62 | ReadableStream = shims.ReadableStream; 63 | getMultipartRequestOptions = shims.getMultipartRequestOptions; 64 | getDefaultAgent = shims.getDefaultAgent; 65 | fileFromPath = shims.fileFromPath; 66 | isFsReadStream = shims.isFsReadStream; 67 | } 68 | -------------------------------------------------------------------------------- /src/_shims/web-runtime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | import { MultipartBody } from './MultipartBody'; 5 | import { type RequestOptions } from '../core'; 6 | import { type Shims } from './registry'; 7 | 8 | export function getRuntime({ manuallyImported }: { manuallyImported?: boolean } = {}): Shims { 9 | const recommendation = 10 | manuallyImported ? 11 | `You may need to use polyfills` 12 | : `Add one of these imports before your first \`import … from '@browserbasehq/sdk'\`: 13 | - \`import '@browserbasehq/sdk/shims/node'\` (if you're running on Node) 14 | - \`import '@browserbasehq/sdk/shims/web'\` (otherwise) 15 | `; 16 | 17 | let _fetch, _Request, _Response, _Headers; 18 | try { 19 | // @ts-ignore 20 | _fetch = fetch; 21 | // @ts-ignore 22 | _Request = Request; 23 | // @ts-ignore 24 | _Response = Response; 25 | // @ts-ignore 26 | _Headers = Headers; 27 | } catch (error) { 28 | throw new Error( 29 | `this environment is missing the following Web Fetch API type: ${ 30 | (error as any).message 31 | }. ${recommendation}`, 32 | ); 33 | } 34 | 35 | return { 36 | kind: 'web', 37 | fetch: _fetch, 38 | Request: _Request, 39 | Response: _Response, 40 | Headers: _Headers, 41 | FormData: 42 | // @ts-ignore 43 | typeof FormData !== 'undefined' ? FormData : ( 44 | class FormData { 45 | // @ts-ignore 46 | constructor() { 47 | throw new Error( 48 | `file uploads aren't supported in this environment yet as 'FormData' is undefined. ${recommendation}`, 49 | ); 50 | } 51 | } 52 | ), 53 | Blob: 54 | typeof Blob !== 'undefined' ? Blob : ( 55 | class Blob { 56 | constructor() { 57 | throw new Error( 58 | `file uploads aren't supported in this environment yet as 'Blob' is undefined. ${recommendation}`, 59 | ); 60 | } 61 | } 62 | ), 63 | File: 64 | // @ts-ignore 65 | typeof File !== 'undefined' ? File : ( 66 | class File { 67 | // @ts-ignore 68 | constructor() { 69 | throw new Error( 70 | `file uploads aren't supported in this environment yet as 'File' is undefined. ${recommendation}`, 71 | ); 72 | } 73 | } 74 | ), 75 | ReadableStream: 76 | // @ts-ignore 77 | typeof ReadableStream !== 'undefined' ? ReadableStream : ( 78 | class ReadableStream { 79 | // @ts-ignore 80 | constructor() { 81 | throw new Error( 82 | `streaming isn't supported in this environment yet as 'ReadableStream' is undefined. ${recommendation}`, 83 | ); 84 | } 85 | } 86 | ), 87 | getMultipartRequestOptions: async >( 88 | // @ts-ignore 89 | form: FormData, 90 | opts: RequestOptions, 91 | ): Promise> => ({ 92 | ...opts, 93 | body: new MultipartBody(form) as any, 94 | }), 95 | getDefaultAgent: (url: string) => undefined, 96 | fileFromPath: () => { 97 | throw new Error( 98 | 'The `fileFromPath` function is only supported in Node. See the README for more details: https://www.github.com/browserbase/sdk-node#file-uploads', 99 | ); 100 | }, 101 | isFsReadStream: (value: any) => false, 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /src/_shims/web-types.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | export type Agent = any; 5 | 6 | declare const _fetch: typeof fetch; 7 | export { _fetch as fetch }; 8 | 9 | type _Request = Request; 10 | export { _Request as Request }; 11 | 12 | type _RequestInfo = RequestInfo; 13 | export { type _RequestInfo as RequestInfo }; 14 | 15 | type _RequestInit = RequestInit; 16 | export { type _RequestInit as RequestInit }; 17 | 18 | type _Response = Response; 19 | export { _Response as Response }; 20 | 21 | type _ResponseInit = ResponseInit; 22 | export { type _ResponseInit as ResponseInit }; 23 | 24 | type _ResponseType = ResponseType; 25 | export { type _ResponseType as ResponseType }; 26 | 27 | type _BodyInit = BodyInit; 28 | export { type _BodyInit as BodyInit }; 29 | 30 | type _Headers = Headers; 31 | export { _Headers as Headers }; 32 | 33 | type _HeadersInit = HeadersInit; 34 | export { type _HeadersInit as HeadersInit }; 35 | 36 | type EndingType = 'native' | 'transparent'; 37 | 38 | export interface BlobPropertyBag { 39 | endings?: EndingType; 40 | type?: string; 41 | } 42 | 43 | export interface FilePropertyBag extends BlobPropertyBag { 44 | lastModified?: number; 45 | } 46 | 47 | export type FileFromPathOptions = Omit; 48 | 49 | type _FormData = FormData; 50 | declare const _FormData: typeof FormData; 51 | export { _FormData as FormData }; 52 | 53 | type _File = File; 54 | declare const _File: typeof File; 55 | export { _File as File }; 56 | 57 | type _Blob = Blob; 58 | declare const _Blob: typeof Blob; 59 | export { _Blob as Blob }; 60 | 61 | export declare class Readable { 62 | readable: boolean; 63 | readonly readableEnded: boolean; 64 | readonly readableFlowing: boolean | null; 65 | readonly readableHighWaterMark: number; 66 | readonly readableLength: number; 67 | readonly readableObjectMode: boolean; 68 | destroyed: boolean; 69 | read(size?: number): any; 70 | pause(): this; 71 | resume(): this; 72 | isPaused(): boolean; 73 | destroy(error?: Error): this; 74 | [Symbol.asyncIterator](): AsyncIterableIterator; 75 | } 76 | 77 | export declare class FsReadStream extends Readable { 78 | path: {}; // node type is string | Buffer 79 | } 80 | 81 | type _ReadableStream = ReadableStream; 82 | declare const _ReadableStream: typeof ReadableStream; 83 | export { _ReadableStream as ReadableStream }; 84 | -------------------------------------------------------------------------------- /src/_shims/web-types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/_shims/web-types.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Disclaimer: modules in _shims aren't intended to be imported by SDK users. 3 | */ 4 | -------------------------------------------------------------------------------- /src/error.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { castToError, Headers } from './core'; 4 | 5 | export class BrowserbaseError extends Error {} 6 | 7 | export class APIError< 8 | TStatus extends number | undefined = number | undefined, 9 | THeaders extends Headers | undefined = Headers | undefined, 10 | TError extends Object | undefined = Object | undefined, 11 | > extends BrowserbaseError { 12 | /** HTTP status for the response that caused the error */ 13 | readonly status: TStatus; 14 | /** HTTP headers for the response that caused the error */ 15 | readonly headers: THeaders; 16 | /** JSON body of the response that caused the error */ 17 | readonly error: TError; 18 | 19 | constructor(status: TStatus, error: TError, message: string | undefined, headers: THeaders) { 20 | super(`${APIError.makeMessage(status, error, message)}`); 21 | this.status = status; 22 | this.headers = headers; 23 | this.error = error; 24 | } 25 | 26 | private static makeMessage(status: number | undefined, error: any, message: string | undefined) { 27 | const msg = 28 | error?.message ? 29 | typeof error.message === 'string' ? 30 | error.message 31 | : JSON.stringify(error.message) 32 | : error ? JSON.stringify(error) 33 | : message; 34 | 35 | if (status && msg) { 36 | return `${status} ${msg}`; 37 | } 38 | if (status) { 39 | return `${status} status code (no body)`; 40 | } 41 | if (msg) { 42 | return msg; 43 | } 44 | return '(no status code or body)'; 45 | } 46 | 47 | static generate( 48 | status: number | undefined, 49 | errorResponse: Object | undefined, 50 | message: string | undefined, 51 | headers: Headers | undefined, 52 | ): APIError { 53 | if (!status || !headers) { 54 | return new APIConnectionError({ message, cause: castToError(errorResponse) }); 55 | } 56 | 57 | const error = errorResponse as Record; 58 | 59 | if (status === 400) { 60 | return new BadRequestError(status, error, message, headers); 61 | } 62 | 63 | if (status === 401) { 64 | return new AuthenticationError(status, error, message, headers); 65 | } 66 | 67 | if (status === 403) { 68 | return new PermissionDeniedError(status, error, message, headers); 69 | } 70 | 71 | if (status === 404) { 72 | return new NotFoundError(status, error, message, headers); 73 | } 74 | 75 | if (status === 409) { 76 | return new ConflictError(status, error, message, headers); 77 | } 78 | 79 | if (status === 422) { 80 | return new UnprocessableEntityError(status, error, message, headers); 81 | } 82 | 83 | if (status === 429) { 84 | return new RateLimitError(status, error, message, headers); 85 | } 86 | 87 | if (status >= 500) { 88 | return new InternalServerError(status, error, message, headers); 89 | } 90 | 91 | return new APIError(status, error, message, headers); 92 | } 93 | } 94 | 95 | export class APIUserAbortError extends APIError { 96 | constructor({ message }: { message?: string } = {}) { 97 | super(undefined, undefined, message || 'Request was aborted.', undefined); 98 | } 99 | } 100 | 101 | export class APIConnectionError extends APIError { 102 | constructor({ message, cause }: { message?: string | undefined; cause?: Error | undefined }) { 103 | super(undefined, undefined, message || 'Connection error.', undefined); 104 | // in some environments the 'cause' property is already declared 105 | // @ts-ignore 106 | if (cause) this.cause = cause; 107 | } 108 | } 109 | 110 | export class APIConnectionTimeoutError extends APIConnectionError { 111 | constructor({ message }: { message?: string } = {}) { 112 | super({ message: message ?? 'Request timed out.' }); 113 | } 114 | } 115 | 116 | export class BadRequestError extends APIError<400, Headers> {} 117 | 118 | export class AuthenticationError extends APIError<401, Headers> {} 119 | 120 | export class PermissionDeniedError extends APIError<403, Headers> {} 121 | 122 | export class NotFoundError extends APIError<404, Headers> {} 123 | 124 | export class ConflictError extends APIError<409, Headers> {} 125 | 126 | export class UnprocessableEntityError extends APIError<422, Headers> {} 127 | 128 | export class RateLimitError extends APIError<429, Headers> {} 129 | 130 | export class InternalServerError extends APIError {} 131 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { type Agent } from './_shims/index'; 4 | import * as Core from './core'; 5 | import * as Errors from './error'; 6 | import * as Uploads from './uploads'; 7 | import * as API from './resources/index'; 8 | import { 9 | Context, 10 | ContextCreateParams, 11 | ContextCreateResponse, 12 | ContextUpdateResponse, 13 | Contexts, 14 | } from './resources/contexts'; 15 | import { Extension, ExtensionCreateParams, Extensions } from './resources/extensions'; 16 | import { Project, ProjectListResponse, ProjectUsage, Projects } from './resources/projects'; 17 | import { 18 | Session, 19 | SessionCreateParams, 20 | SessionCreateResponse, 21 | SessionListParams, 22 | SessionListResponse, 23 | SessionLiveURLs, 24 | SessionRetrieveResponse, 25 | SessionUpdateParams, 26 | Sessions, 27 | } from './resources/sessions/sessions'; 28 | 29 | export interface ClientOptions { 30 | /** 31 | * Your [Browserbase API Key](https://www.browserbase.com/settings). 32 | */ 33 | apiKey?: string | undefined; 34 | 35 | /** 36 | * Override the default base URL for the API, e.g., "https://api.example.com/v2/" 37 | * 38 | * Defaults to process.env['BROWSERBASE_BASE_URL']. 39 | */ 40 | baseURL?: string | null | undefined; 41 | 42 | /** 43 | * The maximum amount of time (in milliseconds) that the client should wait for a response 44 | * from the server before timing out a single request. 45 | * 46 | * Note that request timeouts are retried by default, so in a worst-case scenario you may wait 47 | * much longer than this timeout before the promise succeeds or fails. 48 | */ 49 | timeout?: number | undefined; 50 | 51 | /** 52 | * An HTTP agent used to manage HTTP(S) connections. 53 | * 54 | * If not provided, an agent will be constructed by default in the Node.js environment, 55 | * otherwise no agent is used. 56 | */ 57 | httpAgent?: Agent | undefined; 58 | 59 | /** 60 | * Specify a custom `fetch` function implementation. 61 | * 62 | * If not provided, we use `node-fetch` on Node.js and otherwise expect that `fetch` is 63 | * defined globally. 64 | */ 65 | fetch?: Core.Fetch | undefined; 66 | 67 | /** 68 | * The maximum number of times that the client will retry a request in case of a 69 | * temporary failure, like a network error or a 5XX error from the server. 70 | * 71 | * @default 2 72 | */ 73 | maxRetries?: number | undefined; 74 | 75 | /** 76 | * Default headers to include with every request to the API. 77 | * 78 | * These can be removed in individual requests by explicitly setting the 79 | * header to `undefined` or `null` in request options. 80 | */ 81 | defaultHeaders?: Core.Headers | undefined; 82 | 83 | /** 84 | * Default query parameters to include with every request to the API. 85 | * 86 | * These can be removed in individual requests by explicitly setting the 87 | * param to `undefined` in request options. 88 | */ 89 | defaultQuery?: Core.DefaultQuery | undefined; 90 | } 91 | 92 | /** 93 | * API Client for interfacing with the Browserbase API. 94 | */ 95 | export class Browserbase extends Core.APIClient { 96 | apiKey: string; 97 | 98 | private _options: ClientOptions; 99 | 100 | /** 101 | * API Client for interfacing with the Browserbase API. 102 | * 103 | * @param {string | undefined} [opts.apiKey=process.env['BROWSERBASE_API_KEY'] ?? undefined] 104 | * @param {string} [opts.baseURL=process.env['BROWSERBASE_BASE_URL'] ?? https://api.browserbase.com] - Override the default base URL for the API. 105 | * @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out. 106 | * @param {number} [opts.httpAgent] - An HTTP agent used to manage HTTP(s) connections. 107 | * @param {Core.Fetch} [opts.fetch] - Specify a custom `fetch` function implementation. 108 | * @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request. 109 | * @param {Core.Headers} opts.defaultHeaders - Default headers to include with every request to the API. 110 | * @param {Core.DefaultQuery} opts.defaultQuery - Default query parameters to include with every request to the API. 111 | */ 112 | constructor({ 113 | baseURL = Core.readEnv('BROWSERBASE_BASE_URL'), 114 | apiKey = Core.readEnv('BROWSERBASE_API_KEY'), 115 | ...opts 116 | }: ClientOptions = {}) { 117 | if (apiKey === undefined) { 118 | throw new Errors.BrowserbaseError( 119 | "The BROWSERBASE_API_KEY environment variable is missing or empty; either provide it, or instantiate the Browserbase client with an apiKey option, like new Browserbase({ apiKey: 'My API Key' }).", 120 | ); 121 | } 122 | 123 | const options: ClientOptions = { 124 | apiKey, 125 | ...opts, 126 | baseURL: baseURL || `https://api.browserbase.com`, 127 | }; 128 | 129 | super({ 130 | baseURL: options.baseURL!, 131 | timeout: options.timeout ?? 60000 /* 1 minute */, 132 | httpAgent: options.httpAgent, 133 | maxRetries: options.maxRetries, 134 | fetch: options.fetch, 135 | }); 136 | 137 | this._options = options; 138 | 139 | this.apiKey = apiKey; 140 | } 141 | 142 | contexts: API.Contexts = new API.Contexts(this); 143 | extensions: API.Extensions = new API.Extensions(this); 144 | projects: API.Projects = new API.Projects(this); 145 | sessions: API.Sessions = new API.Sessions(this); 146 | 147 | protected override defaultQuery(): Core.DefaultQuery | undefined { 148 | return this._options.defaultQuery; 149 | } 150 | 151 | protected override defaultHeaders(opts: Core.FinalRequestOptions): Core.Headers { 152 | return { 153 | ...super.defaultHeaders(opts), 154 | ...this._options.defaultHeaders, 155 | }; 156 | } 157 | 158 | protected override authHeaders(opts: Core.FinalRequestOptions): Core.Headers { 159 | return { 'X-BB-API-Key': this.apiKey }; 160 | } 161 | 162 | static Browserbase = this; 163 | static DEFAULT_TIMEOUT = 60000; // 1 minute 164 | 165 | static BrowserbaseError = Errors.BrowserbaseError; 166 | static APIError = Errors.APIError; 167 | static APIConnectionError = Errors.APIConnectionError; 168 | static APIConnectionTimeoutError = Errors.APIConnectionTimeoutError; 169 | static APIUserAbortError = Errors.APIUserAbortError; 170 | static NotFoundError = Errors.NotFoundError; 171 | static ConflictError = Errors.ConflictError; 172 | static RateLimitError = Errors.RateLimitError; 173 | static BadRequestError = Errors.BadRequestError; 174 | static AuthenticationError = Errors.AuthenticationError; 175 | static InternalServerError = Errors.InternalServerError; 176 | static PermissionDeniedError = Errors.PermissionDeniedError; 177 | static UnprocessableEntityError = Errors.UnprocessableEntityError; 178 | 179 | static toFile = Uploads.toFile; 180 | static fileFromPath = Uploads.fileFromPath; 181 | } 182 | 183 | Browserbase.Contexts = Contexts; 184 | Browserbase.Extensions = Extensions; 185 | Browserbase.Projects = Projects; 186 | Browserbase.Sessions = Sessions; 187 | export declare namespace Browserbase { 188 | export type RequestOptions = Core.RequestOptions; 189 | 190 | export { 191 | Contexts as Contexts, 192 | type Context as Context, 193 | type ContextCreateResponse as ContextCreateResponse, 194 | type ContextUpdateResponse as ContextUpdateResponse, 195 | type ContextCreateParams as ContextCreateParams, 196 | }; 197 | 198 | export { 199 | Extensions as Extensions, 200 | type Extension as Extension, 201 | type ExtensionCreateParams as ExtensionCreateParams, 202 | }; 203 | 204 | export { 205 | Projects as Projects, 206 | type Project as Project, 207 | type ProjectUsage as ProjectUsage, 208 | type ProjectListResponse as ProjectListResponse, 209 | }; 210 | 211 | export { 212 | Sessions as Sessions, 213 | type Session as Session, 214 | type SessionLiveURLs as SessionLiveURLs, 215 | type SessionCreateResponse as SessionCreateResponse, 216 | type SessionRetrieveResponse as SessionRetrieveResponse, 217 | type SessionListResponse as SessionListResponse, 218 | type SessionCreateParams as SessionCreateParams, 219 | type SessionUpdateParams as SessionUpdateParams, 220 | type SessionListParams as SessionListParams, 221 | }; 222 | } 223 | 224 | export { toFile, fileFromPath } from './uploads'; 225 | export { 226 | BrowserbaseError, 227 | APIError, 228 | APIConnectionError, 229 | APIConnectionTimeoutError, 230 | APIUserAbortError, 231 | NotFoundError, 232 | ConflictError, 233 | RateLimitError, 234 | BadRequestError, 235 | AuthenticationError, 236 | InternalServerError, 237 | PermissionDeniedError, 238 | UnprocessableEntityError, 239 | } from './error'; 240 | 241 | export default Browserbase; 242 | -------------------------------------------------------------------------------- /src/lib/.keep: -------------------------------------------------------------------------------- 1 | File generated from our OpenAPI spec by Stainless. 2 | 3 | This directory can be used to store custom files to expand the SDK. 4 | It is ignored by Stainless code generation and its content (other than this keep file) won't be touched. 5 | -------------------------------------------------------------------------------- /src/resource.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import type { Browserbase } from './index'; 4 | 5 | export class APIResource { 6 | protected _client: Browserbase; 7 | 8 | constructor(client: Browserbase) { 9 | this._client = client; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/resources.ts: -------------------------------------------------------------------------------- 1 | export * from './resources/index'; 2 | -------------------------------------------------------------------------------- /src/resources/contexts.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { APIResource } from '../resource'; 4 | import * as Core from '../core'; 5 | 6 | export class Contexts extends APIResource { 7 | /** 8 | * Create a Context 9 | */ 10 | create(body: ContextCreateParams, options?: Core.RequestOptions): Core.APIPromise { 11 | return this._client.post('/v1/contexts', { body, ...options }); 12 | } 13 | 14 | /** 15 | * Context 16 | */ 17 | retrieve(id: string, options?: Core.RequestOptions): Core.APIPromise { 18 | return this._client.get(`/v1/contexts/${id}`, options); 19 | } 20 | 21 | /** 22 | * Update Context 23 | */ 24 | update(id: string, options?: Core.RequestOptions): Core.APIPromise { 25 | return this._client.put(`/v1/contexts/${id}`, options); 26 | } 27 | } 28 | 29 | export interface Context { 30 | id: string; 31 | 32 | createdAt: string; 33 | 34 | /** 35 | * The Project ID linked to the uploaded Context. 36 | */ 37 | projectId: string; 38 | 39 | updatedAt: string; 40 | } 41 | 42 | export interface ContextCreateResponse { 43 | id: string; 44 | 45 | /** 46 | * The cipher algorithm used to encrypt the user-data-directory. AES-256-CBC is 47 | * currently the only supported algorithm. 48 | */ 49 | cipherAlgorithm: string; 50 | 51 | /** 52 | * The initialization vector size used to encrypt the user-data-directory. 53 | * [Read more about how to use it](/features/contexts). 54 | */ 55 | initializationVectorSize: number; 56 | 57 | /** 58 | * The public key to encrypt the user-data-directory. 59 | */ 60 | publicKey: string; 61 | 62 | /** 63 | * An upload URL to upload a custom user-data-directory. 64 | */ 65 | uploadUrl: string; 66 | } 67 | 68 | export interface ContextUpdateResponse { 69 | id: string; 70 | 71 | /** 72 | * The cipher algorithm used to encrypt the user-data-directory. AES-256-CBC is 73 | * currently the only supported algorithm. 74 | */ 75 | cipherAlgorithm: string; 76 | 77 | /** 78 | * The initialization vector size used to encrypt the user-data-directory. 79 | * [Read more about how to use it](/features/contexts). 80 | */ 81 | initializationVectorSize: number; 82 | 83 | /** 84 | * The public key to encrypt the user-data-directory. 85 | */ 86 | publicKey: string; 87 | 88 | /** 89 | * An upload URL to upload a custom user-data-directory. 90 | */ 91 | uploadUrl: string; 92 | } 93 | 94 | export interface ContextCreateParams { 95 | /** 96 | * The Project ID. Can be found in 97 | * [Settings](https://www.browserbase.com/settings). 98 | */ 99 | projectId: string; 100 | } 101 | 102 | export declare namespace Contexts { 103 | export { 104 | type Context as Context, 105 | type ContextCreateResponse as ContextCreateResponse, 106 | type ContextUpdateResponse as ContextUpdateResponse, 107 | type ContextCreateParams as ContextCreateParams, 108 | }; 109 | } 110 | -------------------------------------------------------------------------------- /src/resources/extensions.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { APIResource } from '../resource'; 4 | import * as Core from '../core'; 5 | 6 | export class Extensions extends APIResource { 7 | /** 8 | * Upload an Extension 9 | */ 10 | create(body: ExtensionCreateParams, options?: Core.RequestOptions): Core.APIPromise { 11 | return this._client.post('/v1/extensions', Core.multipartFormRequestOptions({ body, ...options })); 12 | } 13 | 14 | /** 15 | * Extension 16 | */ 17 | retrieve(id: string, options?: Core.RequestOptions): Core.APIPromise { 18 | return this._client.get(`/v1/extensions/${id}`, options); 19 | } 20 | 21 | /** 22 | * Delete Extension 23 | */ 24 | delete(id: string, options?: Core.RequestOptions): Core.APIPromise { 25 | return this._client.delete(`/v1/extensions/${id}`, { 26 | ...options, 27 | headers: { Accept: '*/*', ...options?.headers }, 28 | }); 29 | } 30 | } 31 | 32 | export interface Extension { 33 | id: string; 34 | 35 | createdAt: string; 36 | 37 | fileName: string; 38 | 39 | /** 40 | * The Project ID linked to the uploaded Extension. 41 | */ 42 | projectId: string; 43 | 44 | updatedAt: string; 45 | } 46 | 47 | export interface ExtensionCreateParams { 48 | file: Core.Uploadable; 49 | } 50 | 51 | export declare namespace Extensions { 52 | export { type Extension as Extension, type ExtensionCreateParams as ExtensionCreateParams }; 53 | } 54 | -------------------------------------------------------------------------------- /src/resources/index.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | export { 4 | Contexts, 5 | type Context, 6 | type ContextCreateResponse, 7 | type ContextUpdateResponse, 8 | type ContextCreateParams, 9 | } from './contexts'; 10 | export { Extensions, type Extension, type ExtensionCreateParams } from './extensions'; 11 | export { Projects, type Project, type ProjectUsage, type ProjectListResponse } from './projects'; 12 | export { 13 | Sessions, 14 | type Session, 15 | type SessionLiveURLs, 16 | type SessionCreateResponse, 17 | type SessionRetrieveResponse, 18 | type SessionListResponse, 19 | type SessionCreateParams, 20 | type SessionUpdateParams, 21 | type SessionListParams, 22 | } from './sessions/sessions'; 23 | -------------------------------------------------------------------------------- /src/resources/projects.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { APIResource } from '../resource'; 4 | import * as Core from '../core'; 5 | 6 | export class Projects extends APIResource { 7 | /** 8 | * Project 9 | */ 10 | retrieve(id: string, options?: Core.RequestOptions): Core.APIPromise { 11 | return this._client.get(`/v1/projects/${id}`, options); 12 | } 13 | 14 | /** 15 | * List projects 16 | */ 17 | list(options?: Core.RequestOptions): Core.APIPromise { 18 | return this._client.get('/v1/projects', options); 19 | } 20 | 21 | /** 22 | * Project Usage 23 | */ 24 | usage(id: string, options?: Core.RequestOptions): Core.APIPromise { 25 | return this._client.get(`/v1/projects/${id}/usage`, options); 26 | } 27 | } 28 | 29 | export interface Project { 30 | id: string; 31 | 32 | /** 33 | * The maximum number of sessions that this project can run concurrently. 34 | */ 35 | concurrency: number; 36 | 37 | createdAt: string; 38 | 39 | defaultTimeout: number; 40 | 41 | name: string; 42 | 43 | ownerId: string; 44 | 45 | updatedAt: string; 46 | } 47 | 48 | export interface ProjectUsage { 49 | browserMinutes: number; 50 | 51 | proxyBytes: number; 52 | } 53 | 54 | export type ProjectListResponse = Array; 55 | 56 | export declare namespace Projects { 57 | export { 58 | type Project as Project, 59 | type ProjectUsage as ProjectUsage, 60 | type ProjectListResponse as ProjectListResponse, 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /src/resources/sessions.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | export * from './sessions/index'; 4 | -------------------------------------------------------------------------------- /src/resources/sessions/downloads.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { APIResource } from '../../resource'; 4 | import * as Core from '../../core'; 5 | import { type Response } from '../../_shims/index'; 6 | 7 | export class Downloads extends APIResource { 8 | /** 9 | * Session Downloads 10 | */ 11 | list(id: string, options?: Core.RequestOptions): Core.APIPromise { 12 | return this._client.get(`/v1/sessions/${id}/downloads`, { 13 | ...options, 14 | headers: { Accept: 'application/zip', ...options?.headers }, 15 | __binaryResponse: true, 16 | }); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/resources/sessions/index.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | export { Downloads } from './downloads'; 4 | export { Logs, type SessionLog, type LogListResponse } from './logs'; 5 | export { Recording, type SessionRecording, type RecordingRetrieveResponse } from './recording'; 6 | export { 7 | Sessions, 8 | type Session, 9 | type SessionLiveURLs, 10 | type SessionCreateResponse, 11 | type SessionRetrieveResponse, 12 | type SessionListResponse, 13 | type SessionCreateParams, 14 | type SessionUpdateParams, 15 | type SessionListParams, 16 | } from './sessions'; 17 | export { Uploads, type UploadCreateResponse, type UploadCreateParams } from './uploads'; 18 | -------------------------------------------------------------------------------- /src/resources/sessions/logs.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { APIResource } from '../../resource'; 4 | import * as Core from '../../core'; 5 | 6 | export class Logs extends APIResource { 7 | /** 8 | * Session Logs 9 | */ 10 | list(id: string, options?: Core.RequestOptions): Core.APIPromise { 11 | return this._client.get(`/v1/sessions/${id}/logs`, options); 12 | } 13 | } 14 | 15 | export interface SessionLog { 16 | method: string; 17 | 18 | pageId: number; 19 | 20 | sessionId: string; 21 | 22 | frameId?: string; 23 | 24 | loaderId?: string; 25 | 26 | request?: SessionLog.Request; 27 | 28 | response?: SessionLog.Response; 29 | 30 | /** 31 | * milliseconds that have elapsed since the UNIX epoch 32 | */ 33 | timestamp?: number; 34 | } 35 | 36 | export namespace SessionLog { 37 | export interface Request { 38 | params: Record; 39 | 40 | rawBody: string; 41 | 42 | /** 43 | * milliseconds that have elapsed since the UNIX epoch 44 | */ 45 | timestamp?: number; 46 | } 47 | 48 | export interface Response { 49 | rawBody: string; 50 | 51 | result: Record; 52 | 53 | /** 54 | * milliseconds that have elapsed since the UNIX epoch 55 | */ 56 | timestamp?: number; 57 | } 58 | } 59 | 60 | export type LogListResponse = Array; 61 | 62 | export declare namespace Logs { 63 | export { type SessionLog as SessionLog, type LogListResponse as LogListResponse }; 64 | } 65 | -------------------------------------------------------------------------------- /src/resources/sessions/recording.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { APIResource } from '../../resource'; 4 | import * as Core from '../../core'; 5 | 6 | export class Recording extends APIResource { 7 | /** 8 | * Session Recording 9 | */ 10 | retrieve(id: string, options?: Core.RequestOptions): Core.APIPromise { 11 | return this._client.get(`/v1/sessions/${id}/recording`, options); 12 | } 13 | } 14 | 15 | export interface SessionRecording { 16 | /** 17 | * See 18 | * [rrweb documentation](https://github.com/rrweb-io/rrweb/blob/master/docs/recipes/dive-into-event.md). 19 | */ 20 | data: Record; 21 | 22 | sessionId: string; 23 | 24 | /** 25 | * milliseconds that have elapsed since the UNIX epoch 26 | */ 27 | timestamp: number; 28 | 29 | type: number; 30 | } 31 | 32 | export type RecordingRetrieveResponse = Array; 33 | 34 | export declare namespace Recording { 35 | export { 36 | type SessionRecording as SessionRecording, 37 | type RecordingRetrieveResponse as RecordingRetrieveResponse, 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /src/resources/sessions/uploads.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { APIResource } from '../../resource'; 4 | import * as Core from '../../core'; 5 | 6 | export class Uploads extends APIResource { 7 | /** 8 | * Create Session Uploads 9 | */ 10 | create( 11 | id: string, 12 | body: UploadCreateParams, 13 | options?: Core.RequestOptions, 14 | ): Core.APIPromise { 15 | return this._client.post( 16 | `/v1/sessions/${id}/uploads`, 17 | Core.multipartFormRequestOptions({ body, ...options }), 18 | ); 19 | } 20 | } 21 | 22 | export interface UploadCreateResponse { 23 | message: string; 24 | } 25 | 26 | export interface UploadCreateParams { 27 | file: Core.Uploadable; 28 | } 29 | 30 | export declare namespace Uploads { 31 | export { type UploadCreateResponse as UploadCreateResponse, type UploadCreateParams as UploadCreateParams }; 32 | } 33 | -------------------------------------------------------------------------------- /src/shims/node.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import * as types from '../_shims/node-types'; 3 | import { setShims } from '../_shims/registry'; 4 | import { getRuntime } from '../_shims/node-runtime'; 5 | setShims(getRuntime()); 6 | 7 | declare module '../_shims/manual-types' { 8 | export namespace manual { 9 | // @ts-ignore 10 | export type Agent = types.Agent; 11 | // @ts-ignore 12 | export import fetch = types.fetch; 13 | // @ts-ignore 14 | export type Request = types.Request; 15 | // @ts-ignore 16 | export type RequestInfo = types.RequestInfo; 17 | // @ts-ignore 18 | export type RequestInit = types.RequestInit; 19 | // @ts-ignore 20 | export type Response = types.Response; 21 | // @ts-ignore 22 | export type ResponseInit = types.ResponseInit; 23 | // @ts-ignore 24 | export type ResponseType = types.ResponseType; 25 | // @ts-ignore 26 | export type BodyInit = types.BodyInit; 27 | // @ts-ignore 28 | export type Headers = types.Headers; 29 | // @ts-ignore 30 | export type HeadersInit = types.HeadersInit; 31 | // @ts-ignore 32 | export type BlobPropertyBag = types.BlobPropertyBag; 33 | // @ts-ignore 34 | export type FilePropertyBag = types.FilePropertyBag; 35 | // @ts-ignore 36 | export type FileFromPathOptions = types.FileFromPathOptions; 37 | // @ts-ignore 38 | export import FormData = types.FormData; 39 | // @ts-ignore 40 | export import File = types.File; 41 | // @ts-ignore 42 | export import Blob = types.Blob; 43 | // @ts-ignore 44 | export type Readable = types.Readable; 45 | // @ts-ignore 46 | export type FsReadStream = types.FsReadStream; 47 | // @ts-ignore 48 | export import ReadableStream = types.ReadableStream; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/shims/web.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import * as types from '../_shims/web-types'; 3 | import { setShims } from '../_shims/registry'; 4 | import { getRuntime } from '../_shims/web-runtime'; 5 | setShims(getRuntime({ manuallyImported: true })); 6 | 7 | declare module '../_shims/manual-types' { 8 | export namespace manual { 9 | // @ts-ignore 10 | export type Agent = types.Agent; 11 | // @ts-ignore 12 | export import fetch = types.fetch; 13 | // @ts-ignore 14 | export type Request = types.Request; 15 | // @ts-ignore 16 | export type RequestInfo = types.RequestInfo; 17 | // @ts-ignore 18 | export type RequestInit = types.RequestInit; 19 | // @ts-ignore 20 | export type Response = types.Response; 21 | // @ts-ignore 22 | export type ResponseInit = types.ResponseInit; 23 | // @ts-ignore 24 | export type ResponseType = types.ResponseType; 25 | // @ts-ignore 26 | export type BodyInit = types.BodyInit; 27 | // @ts-ignore 28 | export type Headers = types.Headers; 29 | // @ts-ignore 30 | export type HeadersInit = types.HeadersInit; 31 | // @ts-ignore 32 | export type BlobPropertyBag = types.BlobPropertyBag; 33 | // @ts-ignore 34 | export type FilePropertyBag = types.FilePropertyBag; 35 | // @ts-ignore 36 | export type FileFromPathOptions = types.FileFromPathOptions; 37 | // @ts-ignore 38 | export import FormData = types.FormData; 39 | // @ts-ignore 40 | export import File = types.File; 41 | // @ts-ignore 42 | export import Blob = types.Blob; 43 | // @ts-ignore 44 | export type Readable = types.Readable; 45 | // @ts-ignore 46 | export type FsReadStream = types.FsReadStream; 47 | // @ts-ignore 48 | export import ReadableStream = types.ReadableStream; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/uploads.ts: -------------------------------------------------------------------------------- 1 | import { type RequestOptions } from './core'; 2 | import { 3 | FormData, 4 | File, 5 | type Blob, 6 | type FilePropertyBag, 7 | getMultipartRequestOptions, 8 | type FsReadStream, 9 | isFsReadStream, 10 | } from './_shims/index'; 11 | import { MultipartBody } from './_shims/MultipartBody'; 12 | export { fileFromPath } from './_shims/index'; 13 | 14 | type BlobLikePart = string | ArrayBuffer | ArrayBufferView | BlobLike | Uint8Array | DataView; 15 | export type BlobPart = string | ArrayBuffer | ArrayBufferView | Blob | Uint8Array | DataView; 16 | 17 | /** 18 | * Typically, this is a native "File" class. 19 | * 20 | * We provide the {@link toFile} utility to convert a variety of objects 21 | * into the File class. 22 | * 23 | * For convenience, you can also pass a fetch Response, or in Node, 24 | * the result of fs.createReadStream(). 25 | */ 26 | export type Uploadable = FileLike | ResponseLike | FsReadStream; 27 | 28 | /** 29 | * Intended to match web.Blob, node.Blob, node-fetch.Blob, etc. 30 | */ 31 | export interface BlobLike { 32 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */ 33 | readonly size: number; 34 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */ 35 | readonly type: string; 36 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */ 37 | text(): Promise; 38 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */ 39 | slice(start?: number, end?: number): BlobLike; 40 | // unfortunately @types/node-fetch@^2.6.4 doesn't type the arrayBuffer method 41 | } 42 | 43 | /** 44 | * Intended to match web.File, node.File, node-fetch.File, etc. 45 | */ 46 | export interface FileLike extends BlobLike { 47 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */ 48 | readonly lastModified: number; 49 | /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */ 50 | readonly name: string; 51 | } 52 | 53 | /** 54 | * Intended to match web.Response, node.Response, node-fetch.Response, etc. 55 | */ 56 | export interface ResponseLike { 57 | url: string; 58 | blob(): Promise; 59 | } 60 | 61 | export const isResponseLike = (value: any): value is ResponseLike => 62 | value != null && 63 | typeof value === 'object' && 64 | typeof value.url === 'string' && 65 | typeof value.blob === 'function'; 66 | 67 | export const isFileLike = (value: any): value is FileLike => 68 | value != null && 69 | typeof value === 'object' && 70 | typeof value.name === 'string' && 71 | typeof value.lastModified === 'number' && 72 | isBlobLike(value); 73 | 74 | /** 75 | * The BlobLike type omits arrayBuffer() because @types/node-fetch@^2.6.4 lacks it; but this check 76 | * adds the arrayBuffer() method type because it is available and used at runtime 77 | */ 78 | export const isBlobLike = (value: any): value is BlobLike & { arrayBuffer(): Promise } => 79 | value != null && 80 | typeof value === 'object' && 81 | typeof value.size === 'number' && 82 | typeof value.type === 'string' && 83 | typeof value.text === 'function' && 84 | typeof value.slice === 'function' && 85 | typeof value.arrayBuffer === 'function'; 86 | 87 | export const isUploadable = (value: any): value is Uploadable => { 88 | return isFileLike(value) || isResponseLike(value) || isFsReadStream(value); 89 | }; 90 | 91 | export type ToFileInput = Uploadable | Exclude | AsyncIterable; 92 | 93 | /** 94 | * Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats 95 | * @param value the raw content of the file. Can be an {@link Uploadable}, {@link BlobLikePart}, or {@link AsyncIterable} of {@link BlobLikePart}s 96 | * @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible 97 | * @param {Object=} options additional properties 98 | * @param {string=} options.type the MIME type of the content 99 | * @param {number=} options.lastModified the last modified timestamp 100 | * @returns a {@link File} with the given properties 101 | */ 102 | export async function toFile( 103 | value: ToFileInput | PromiseLike, 104 | name?: string | null | undefined, 105 | options?: FilePropertyBag | undefined, 106 | ): Promise { 107 | // If it's a promise, resolve it. 108 | value = await value; 109 | 110 | // If we've been given a `File` we don't need to do anything 111 | if (isFileLike(value)) { 112 | return value; 113 | } 114 | 115 | if (isResponseLike(value)) { 116 | const blob = await value.blob(); 117 | name ||= new URL(value.url).pathname.split(/[\\/]/).pop() ?? 'unknown_file'; 118 | 119 | // we need to convert the `Blob` into an array buffer because the `Blob` class 120 | // that `node-fetch` defines is incompatible with the web standard which results 121 | // in `new File` interpreting it as a string instead of binary data. 122 | const data = isBlobLike(blob) ? [(await blob.arrayBuffer()) as any] : [blob]; 123 | 124 | return new File(data, name, options); 125 | } 126 | 127 | const bits = await getBytes(value); 128 | 129 | name ||= getName(value) ?? 'unknown_file'; 130 | 131 | if (!options?.type) { 132 | const type = (bits[0] as any)?.type; 133 | if (typeof type === 'string') { 134 | options = { ...options, type }; 135 | } 136 | } 137 | 138 | return new File(bits, name, options); 139 | } 140 | 141 | async function getBytes(value: ToFileInput): Promise> { 142 | let parts: Array = []; 143 | if ( 144 | typeof value === 'string' || 145 | ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc. 146 | value instanceof ArrayBuffer 147 | ) { 148 | parts.push(value); 149 | } else if (isBlobLike(value)) { 150 | parts.push(await value.arrayBuffer()); 151 | } else if ( 152 | isAsyncIterableIterator(value) // includes Readable, ReadableStream, etc. 153 | ) { 154 | for await (const chunk of value) { 155 | parts.push(chunk as BlobPart); // TODO, consider validating? 156 | } 157 | } else { 158 | throw new Error( 159 | `Unexpected data type: ${typeof value}; constructor: ${ 160 | value?.constructor?.name 161 | }; props: ${propsForError(value)}`, 162 | ); 163 | } 164 | 165 | return parts; 166 | } 167 | 168 | function propsForError(value: any): string { 169 | const props = Object.getOwnPropertyNames(value); 170 | return `[${props.map((p) => `"${p}"`).join(', ')}]`; 171 | } 172 | 173 | function getName(value: any): string | undefined { 174 | return ( 175 | getStringFromMaybeBuffer(value.name) || 176 | getStringFromMaybeBuffer(value.filename) || 177 | // For fs.ReadStream 178 | getStringFromMaybeBuffer(value.path)?.split(/[\\/]/).pop() 179 | ); 180 | } 181 | 182 | const getStringFromMaybeBuffer = (x: string | Buffer | unknown): string | undefined => { 183 | if (typeof x === 'string') return x; 184 | if (typeof Buffer !== 'undefined' && x instanceof Buffer) return String(x); 185 | return undefined; 186 | }; 187 | 188 | const isAsyncIterableIterator = (value: any): value is AsyncIterableIterator => 189 | value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function'; 190 | 191 | export const isMultipartBody = (body: any): body is MultipartBody => 192 | body && typeof body === 'object' && body.body && body[Symbol.toStringTag] === 'MultipartBody'; 193 | 194 | /** 195 | * Returns a multipart/form-data request if any part of the given request body contains a File / Blob value. 196 | * Otherwise returns the request as is. 197 | */ 198 | export const maybeMultipartFormRequestOptions = async >( 199 | opts: RequestOptions, 200 | ): Promise> => { 201 | if (!hasUploadableValue(opts.body)) return opts; 202 | 203 | const form = await createForm(opts.body); 204 | return getMultipartRequestOptions(form, opts); 205 | }; 206 | 207 | export const multipartFormRequestOptions = async >( 208 | opts: RequestOptions, 209 | ): Promise> => { 210 | const form = await createForm(opts.body); 211 | return getMultipartRequestOptions(form, opts); 212 | }; 213 | 214 | export const createForm = async >(body: T | undefined): Promise => { 215 | const form = new FormData(); 216 | await Promise.all(Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value))); 217 | return form; 218 | }; 219 | 220 | const hasUploadableValue = (value: unknown): boolean => { 221 | if (isUploadable(value)) return true; 222 | if (Array.isArray(value)) return value.some(hasUploadableValue); 223 | if (value && typeof value === 'object') { 224 | for (const k in value) { 225 | if (hasUploadableValue((value as any)[k])) return true; 226 | } 227 | } 228 | return false; 229 | }; 230 | 231 | const addFormValue = async (form: FormData, key: string, value: unknown): Promise => { 232 | if (value === undefined) return; 233 | if (value == null) { 234 | throw new TypeError( 235 | `Received null for "${key}"; to pass null in FormData, you must use the string 'null'`, 236 | ); 237 | } 238 | 239 | // TODO: make nested formats configurable 240 | if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { 241 | form.append(key, String(value)); 242 | } else if (isUploadable(value)) { 243 | const file = await toFile(value); 244 | form.append(key, file as File); 245 | } else if (Array.isArray(value)) { 246 | await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry))); 247 | } else if (typeof value === 'object') { 248 | await Promise.all( 249 | Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop)), 250 | ); 251 | } else { 252 | throw new TypeError( 253 | `Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`, 254 | ); 255 | } 256 | }; 257 | -------------------------------------------------------------------------------- /src/version.ts: -------------------------------------------------------------------------------- 1 | export const VERSION = '2.6.0'; // x-release-please-version 2 | -------------------------------------------------------------------------------- /tests/api-resources/contexts.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Browserbase from '@browserbasehq/sdk'; 4 | import { Response } from 'node-fetch'; 5 | 6 | const client = new Browserbase({ 7 | apiKey: 'My API Key', 8 | baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', 9 | }); 10 | 11 | describe('resource contexts', () => { 12 | test('create: only required params', async () => { 13 | const responsePromise = client.contexts.create({ projectId: 'projectId' }); 14 | const rawResponse = await responsePromise.asResponse(); 15 | expect(rawResponse).toBeInstanceOf(Response); 16 | const response = await responsePromise; 17 | expect(response).not.toBeInstanceOf(Response); 18 | const dataAndResponse = await responsePromise.withResponse(); 19 | expect(dataAndResponse.data).toBe(response); 20 | expect(dataAndResponse.response).toBe(rawResponse); 21 | }); 22 | 23 | test('create: required and optional params', async () => { 24 | const response = await client.contexts.create({ projectId: 'projectId' }); 25 | }); 26 | 27 | test('retrieve', async () => { 28 | const responsePromise = client.contexts.retrieve('id'); 29 | const rawResponse = await responsePromise.asResponse(); 30 | expect(rawResponse).toBeInstanceOf(Response); 31 | const response = await responsePromise; 32 | expect(response).not.toBeInstanceOf(Response); 33 | const dataAndResponse = await responsePromise.withResponse(); 34 | expect(dataAndResponse.data).toBe(response); 35 | expect(dataAndResponse.response).toBe(rawResponse); 36 | }); 37 | 38 | test('retrieve: request options instead of params are passed correctly', async () => { 39 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 40 | await expect(client.contexts.retrieve('id', { path: '/_stainless_unknown_path' })).rejects.toThrow( 41 | Browserbase.NotFoundError, 42 | ); 43 | }); 44 | 45 | test('update', async () => { 46 | const responsePromise = client.contexts.update('id'); 47 | const rawResponse = await responsePromise.asResponse(); 48 | expect(rawResponse).toBeInstanceOf(Response); 49 | const response = await responsePromise; 50 | expect(response).not.toBeInstanceOf(Response); 51 | const dataAndResponse = await responsePromise.withResponse(); 52 | expect(dataAndResponse.data).toBe(response); 53 | expect(dataAndResponse.response).toBe(rawResponse); 54 | }); 55 | 56 | test('update: request options instead of params are passed correctly', async () => { 57 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 58 | await expect(client.contexts.update('id', { path: '/_stainless_unknown_path' })).rejects.toThrow( 59 | Browserbase.NotFoundError, 60 | ); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /tests/api-resources/extensions.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Browserbase, { toFile } from '@browserbasehq/sdk'; 4 | import { Response } from 'node-fetch'; 5 | 6 | const client = new Browserbase({ 7 | apiKey: 'My API Key', 8 | baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', 9 | }); 10 | 11 | describe('resource extensions', () => { 12 | test('create: only required params', async () => { 13 | const responsePromise = client.extensions.create({ 14 | file: await toFile(Buffer.from('# my file contents'), 'README.md'), 15 | }); 16 | const rawResponse = await responsePromise.asResponse(); 17 | expect(rawResponse).toBeInstanceOf(Response); 18 | const response = await responsePromise; 19 | expect(response).not.toBeInstanceOf(Response); 20 | const dataAndResponse = await responsePromise.withResponse(); 21 | expect(dataAndResponse.data).toBe(response); 22 | expect(dataAndResponse.response).toBe(rawResponse); 23 | }); 24 | 25 | test('create: required and optional params', async () => { 26 | const response = await client.extensions.create({ 27 | file: await toFile(Buffer.from('# my file contents'), 'README.md'), 28 | }); 29 | }); 30 | 31 | test('retrieve', async () => { 32 | const responsePromise = client.extensions.retrieve('id'); 33 | const rawResponse = await responsePromise.asResponse(); 34 | expect(rawResponse).toBeInstanceOf(Response); 35 | const response = await responsePromise; 36 | expect(response).not.toBeInstanceOf(Response); 37 | const dataAndResponse = await responsePromise.withResponse(); 38 | expect(dataAndResponse.data).toBe(response); 39 | expect(dataAndResponse.response).toBe(rawResponse); 40 | }); 41 | 42 | test('retrieve: request options instead of params are passed correctly', async () => { 43 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 44 | await expect(client.extensions.retrieve('id', { path: '/_stainless_unknown_path' })).rejects.toThrow( 45 | Browserbase.NotFoundError, 46 | ); 47 | }); 48 | 49 | test('delete', async () => { 50 | const responsePromise = client.extensions.delete('id'); 51 | const rawResponse = await responsePromise.asResponse(); 52 | expect(rawResponse).toBeInstanceOf(Response); 53 | const response = await responsePromise; 54 | expect(response).not.toBeInstanceOf(Response); 55 | const dataAndResponse = await responsePromise.withResponse(); 56 | expect(dataAndResponse.data).toBe(response); 57 | expect(dataAndResponse.response).toBe(rawResponse); 58 | }); 59 | 60 | test('delete: request options instead of params are passed correctly', async () => { 61 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 62 | await expect(client.extensions.delete('id', { path: '/_stainless_unknown_path' })).rejects.toThrow( 63 | Browserbase.NotFoundError, 64 | ); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /tests/api-resources/projects.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Browserbase from '@browserbasehq/sdk'; 4 | import { Response } from 'node-fetch'; 5 | 6 | const client = new Browserbase({ 7 | apiKey: 'My API Key', 8 | baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', 9 | }); 10 | 11 | describe('resource projects', () => { 12 | test('retrieve', async () => { 13 | const responsePromise = client.projects.retrieve('id'); 14 | const rawResponse = await responsePromise.asResponse(); 15 | expect(rawResponse).toBeInstanceOf(Response); 16 | const response = await responsePromise; 17 | expect(response).not.toBeInstanceOf(Response); 18 | const dataAndResponse = await responsePromise.withResponse(); 19 | expect(dataAndResponse.data).toBe(response); 20 | expect(dataAndResponse.response).toBe(rawResponse); 21 | }); 22 | 23 | test('retrieve: request options instead of params are passed correctly', async () => { 24 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 25 | await expect(client.projects.retrieve('id', { path: '/_stainless_unknown_path' })).rejects.toThrow( 26 | Browserbase.NotFoundError, 27 | ); 28 | }); 29 | 30 | test('list', async () => { 31 | const responsePromise = client.projects.list(); 32 | const rawResponse = await responsePromise.asResponse(); 33 | expect(rawResponse).toBeInstanceOf(Response); 34 | const response = await responsePromise; 35 | expect(response).not.toBeInstanceOf(Response); 36 | const dataAndResponse = await responsePromise.withResponse(); 37 | expect(dataAndResponse.data).toBe(response); 38 | expect(dataAndResponse.response).toBe(rawResponse); 39 | }); 40 | 41 | test('list: request options instead of params are passed correctly', async () => { 42 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 43 | await expect(client.projects.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( 44 | Browserbase.NotFoundError, 45 | ); 46 | }); 47 | 48 | test('usage', async () => { 49 | const responsePromise = client.projects.usage('id'); 50 | const rawResponse = await responsePromise.asResponse(); 51 | expect(rawResponse).toBeInstanceOf(Response); 52 | const response = await responsePromise; 53 | expect(response).not.toBeInstanceOf(Response); 54 | const dataAndResponse = await responsePromise.withResponse(); 55 | expect(dataAndResponse.data).toBe(response); 56 | expect(dataAndResponse.response).toBe(rawResponse); 57 | }); 58 | 59 | test('usage: request options instead of params are passed correctly', async () => { 60 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 61 | await expect(client.projects.usage('id', { path: '/_stainless_unknown_path' })).rejects.toThrow( 62 | Browserbase.NotFoundError, 63 | ); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /tests/api-resources/sessions/downloads.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Browserbase from '@browserbasehq/sdk'; 4 | 5 | const client = new Browserbase({ 6 | apiKey: 'My API Key', 7 | baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', 8 | }); 9 | 10 | describe('resource downloads', () => { 11 | test('list: request options instead of params are passed correctly', async () => { 12 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 13 | await expect(client.sessions.downloads.list('id', { path: '/_stainless_unknown_path' })).rejects.toThrow( 14 | Browserbase.NotFoundError, 15 | ); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/api-resources/sessions/logs.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Browserbase from '@browserbasehq/sdk'; 4 | import { Response } from 'node-fetch'; 5 | 6 | const client = new Browserbase({ 7 | apiKey: 'My API Key', 8 | baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', 9 | }); 10 | 11 | describe('resource logs', () => { 12 | test('list', async () => { 13 | const responsePromise = client.sessions.logs.list('id'); 14 | const rawResponse = await responsePromise.asResponse(); 15 | expect(rawResponse).toBeInstanceOf(Response); 16 | const response = await responsePromise; 17 | expect(response).not.toBeInstanceOf(Response); 18 | const dataAndResponse = await responsePromise.withResponse(); 19 | expect(dataAndResponse.data).toBe(response); 20 | expect(dataAndResponse.response).toBe(rawResponse); 21 | }); 22 | 23 | test('list: request options instead of params are passed correctly', async () => { 24 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 25 | await expect(client.sessions.logs.list('id', { path: '/_stainless_unknown_path' })).rejects.toThrow( 26 | Browserbase.NotFoundError, 27 | ); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /tests/api-resources/sessions/recording.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Browserbase from '@browserbasehq/sdk'; 4 | import { Response } from 'node-fetch'; 5 | 6 | const client = new Browserbase({ 7 | apiKey: 'My API Key', 8 | baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', 9 | }); 10 | 11 | describe('resource recording', () => { 12 | test('retrieve', async () => { 13 | const responsePromise = client.sessions.recording.retrieve('id'); 14 | const rawResponse = await responsePromise.asResponse(); 15 | expect(rawResponse).toBeInstanceOf(Response); 16 | const response = await responsePromise; 17 | expect(response).not.toBeInstanceOf(Response); 18 | const dataAndResponse = await responsePromise.withResponse(); 19 | expect(dataAndResponse.data).toBe(response); 20 | expect(dataAndResponse.response).toBe(rawResponse); 21 | }); 22 | 23 | test('retrieve: request options instead of params are passed correctly', async () => { 24 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 25 | await expect( 26 | client.sessions.recording.retrieve('id', { path: '/_stainless_unknown_path' }), 27 | ).rejects.toThrow(Browserbase.NotFoundError); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /tests/api-resources/sessions/sessions.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Browserbase from '@browserbasehq/sdk'; 4 | import { Response } from 'node-fetch'; 5 | 6 | const client = new Browserbase({ 7 | apiKey: 'My API Key', 8 | baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', 9 | }); 10 | 11 | describe('resource sessions', () => { 12 | test('create: only required params', async () => { 13 | const responsePromise = client.sessions.create({ projectId: 'projectId' }); 14 | const rawResponse = await responsePromise.asResponse(); 15 | expect(rawResponse).toBeInstanceOf(Response); 16 | const response = await responsePromise; 17 | expect(response).not.toBeInstanceOf(Response); 18 | const dataAndResponse = await responsePromise.withResponse(); 19 | expect(dataAndResponse.data).toBe(response); 20 | expect(dataAndResponse.response).toBe(rawResponse); 21 | }); 22 | 23 | test('create: required and optional params', async () => { 24 | const response = await client.sessions.create({ 25 | projectId: 'projectId', 26 | browserSettings: { 27 | advancedStealth: true, 28 | blockAds: true, 29 | captchaImageSelector: 'captchaImageSelector', 30 | captchaInputSelector: 'captchaInputSelector', 31 | context: { id: 'id', persist: true }, 32 | extensionId: 'extensionId', 33 | fingerprint: { 34 | browsers: ['chrome'], 35 | devices: ['desktop'], 36 | httpVersion: '1', 37 | locales: ['string'], 38 | operatingSystems: ['android'], 39 | screen: { maxHeight: 0, maxWidth: 0, minHeight: 0, minWidth: 0 }, 40 | }, 41 | logSession: true, 42 | recordSession: true, 43 | solveCaptchas: true, 44 | viewport: { height: 0, width: 0 }, 45 | }, 46 | extensionId: 'extensionId', 47 | keepAlive: true, 48 | proxies: true, 49 | region: 'us-west-2', 50 | timeout: 60, 51 | userMetadata: { foo: 'bar' }, 52 | }); 53 | }); 54 | 55 | test('retrieve', async () => { 56 | const responsePromise = client.sessions.retrieve('id'); 57 | const rawResponse = await responsePromise.asResponse(); 58 | expect(rawResponse).toBeInstanceOf(Response); 59 | const response = await responsePromise; 60 | expect(response).not.toBeInstanceOf(Response); 61 | const dataAndResponse = await responsePromise.withResponse(); 62 | expect(dataAndResponse.data).toBe(response); 63 | expect(dataAndResponse.response).toBe(rawResponse); 64 | }); 65 | 66 | test('retrieve: request options instead of params are passed correctly', async () => { 67 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 68 | await expect(client.sessions.retrieve('id', { path: '/_stainless_unknown_path' })).rejects.toThrow( 69 | Browserbase.NotFoundError, 70 | ); 71 | }); 72 | 73 | test('update: only required params', async () => { 74 | const responsePromise = client.sessions.update('id', { 75 | projectId: 'projectId', 76 | status: 'REQUEST_RELEASE', 77 | }); 78 | const rawResponse = await responsePromise.asResponse(); 79 | expect(rawResponse).toBeInstanceOf(Response); 80 | const response = await responsePromise; 81 | expect(response).not.toBeInstanceOf(Response); 82 | const dataAndResponse = await responsePromise.withResponse(); 83 | expect(dataAndResponse.data).toBe(response); 84 | expect(dataAndResponse.response).toBe(rawResponse); 85 | }); 86 | 87 | test('update: required and optional params', async () => { 88 | const response = await client.sessions.update('id', { 89 | projectId: 'projectId', 90 | status: 'REQUEST_RELEASE', 91 | }); 92 | }); 93 | 94 | test('list', async () => { 95 | const responsePromise = client.sessions.list(); 96 | const rawResponse = await responsePromise.asResponse(); 97 | expect(rawResponse).toBeInstanceOf(Response); 98 | const response = await responsePromise; 99 | expect(response).not.toBeInstanceOf(Response); 100 | const dataAndResponse = await responsePromise.withResponse(); 101 | expect(dataAndResponse.data).toBe(response); 102 | expect(dataAndResponse.response).toBe(rawResponse); 103 | }); 104 | 105 | test('list: request options instead of params are passed correctly', async () => { 106 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 107 | await expect(client.sessions.list({ path: '/_stainless_unknown_path' })).rejects.toThrow( 108 | Browserbase.NotFoundError, 109 | ); 110 | }); 111 | 112 | test('list: request options and params are passed correctly', async () => { 113 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 114 | await expect( 115 | client.sessions.list({ q: 'q', status: 'RUNNING' }, { path: '/_stainless_unknown_path' }), 116 | ).rejects.toThrow(Browserbase.NotFoundError); 117 | }); 118 | 119 | test('debug', async () => { 120 | const responsePromise = client.sessions.debug('id'); 121 | const rawResponse = await responsePromise.asResponse(); 122 | expect(rawResponse).toBeInstanceOf(Response); 123 | const response = await responsePromise; 124 | expect(response).not.toBeInstanceOf(Response); 125 | const dataAndResponse = await responsePromise.withResponse(); 126 | expect(dataAndResponse.data).toBe(response); 127 | expect(dataAndResponse.response).toBe(rawResponse); 128 | }); 129 | 130 | test('debug: request options instead of params are passed correctly', async () => { 131 | // ensure the request options are being passed correctly by passing an invalid HTTP method in order to cause an error 132 | await expect(client.sessions.debug('id', { path: '/_stainless_unknown_path' })).rejects.toThrow( 133 | Browserbase.NotFoundError, 134 | ); 135 | }); 136 | }); 137 | -------------------------------------------------------------------------------- /tests/api-resources/sessions/uploads.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import Browserbase, { toFile } from '@browserbasehq/sdk'; 4 | import { Response } from 'node-fetch'; 5 | 6 | const client = new Browserbase({ 7 | apiKey: 'My API Key', 8 | baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', 9 | }); 10 | 11 | describe('resource uploads', () => { 12 | test('create: only required params', async () => { 13 | const responsePromise = client.sessions.uploads.create('id', { 14 | file: await toFile(Buffer.from('# my file contents'), 'README.md'), 15 | }); 16 | const rawResponse = await responsePromise.asResponse(); 17 | expect(rawResponse).toBeInstanceOf(Response); 18 | const response = await responsePromise; 19 | expect(response).not.toBeInstanceOf(Response); 20 | const dataAndResponse = await responsePromise.withResponse(); 21 | expect(dataAndResponse.data).toBe(response); 22 | expect(dataAndResponse.response).toBe(rawResponse); 23 | }); 24 | 25 | test('create: required and optional params', async () => { 26 | const response = await client.sessions.uploads.create('id', { 27 | file: await toFile(Buffer.from('# my file contents'), 'README.md'), 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /tests/form.test.ts: -------------------------------------------------------------------------------- 1 | import { multipartFormRequestOptions, createForm } from '@browserbasehq/sdk/core'; 2 | import { Blob } from '@browserbasehq/sdk/_shims/index'; 3 | import { toFile } from '@browserbasehq/sdk'; 4 | 5 | describe('form data validation', () => { 6 | test('valid values do not error', async () => { 7 | await multipartFormRequestOptions({ 8 | body: { 9 | foo: 'foo', 10 | string: 1, 11 | bool: true, 12 | file: await toFile(Buffer.from('some-content')), 13 | blob: new Blob(['Some content'], { type: 'text/plain' }), 14 | }, 15 | }); 16 | }); 17 | 18 | test('null', async () => { 19 | await expect(() => 20 | multipartFormRequestOptions({ 21 | body: { 22 | null: null, 23 | }, 24 | }), 25 | ).rejects.toThrow(TypeError); 26 | }); 27 | 28 | test('undefined is stripped', async () => { 29 | const form = await createForm({ 30 | foo: undefined, 31 | bar: 'baz', 32 | }); 33 | expect(form.has('foo')).toBe(false); 34 | expect(form.get('bar')).toBe('baz'); 35 | }); 36 | 37 | test('nested undefined property is stripped', async () => { 38 | const form = await createForm({ 39 | bar: { 40 | baz: undefined, 41 | }, 42 | }); 43 | expect(Array.from(form.entries())).toEqual([]); 44 | 45 | const form2 = await createForm({ 46 | bar: { 47 | foo: 'string', 48 | baz: undefined, 49 | }, 50 | }); 51 | expect(Array.from(form2.entries())).toEqual([['bar[foo]', 'string']]); 52 | }); 53 | 54 | test('nested undefined array item is stripped', async () => { 55 | const form = await createForm({ 56 | bar: [undefined, undefined], 57 | }); 58 | expect(Array.from(form.entries())).toEqual([]); 59 | 60 | const form2 = await createForm({ 61 | bar: [undefined, 'foo'], 62 | }); 63 | expect(Array.from(form2.entries())).toEqual([['bar[]', 'foo']]); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /tests/responses.test.ts: -------------------------------------------------------------------------------- 1 | import { createResponseHeaders } from '@browserbasehq/sdk/core'; 2 | import { Headers } from '@browserbasehq/sdk/_shims/index'; 3 | 4 | describe('response parsing', () => { 5 | // TODO: test unicode characters 6 | test('headers are case agnostic', async () => { 7 | const headers = createResponseHeaders(new Headers({ 'Content-Type': 'foo', Accept: 'text/plain' })); 8 | expect(headers['content-type']).toEqual('foo'); 9 | expect(headers['Content-type']).toEqual('foo'); 10 | expect(headers['Content-Type']).toEqual('foo'); 11 | expect(headers['accept']).toEqual('text/plain'); 12 | expect(headers['Accept']).toEqual('text/plain'); 13 | expect(headers['Hello-World']).toBeUndefined(); 14 | }); 15 | 16 | test('duplicate headers are concatenated', () => { 17 | const headers = createResponseHeaders( 18 | new Headers([ 19 | ['Content-Type', 'text/xml'], 20 | ['Content-Type', 'application/json'], 21 | ]), 22 | ); 23 | expect(headers['content-type']).toBe('text/xml, application/json'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /tests/stringifyQuery.test.ts: -------------------------------------------------------------------------------- 1 | // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. 2 | 3 | import { Browserbase } from '@browserbasehq/sdk'; 4 | 5 | const { stringifyQuery } = Browserbase.prototype as any; 6 | 7 | describe(stringifyQuery, () => { 8 | for (const [input, expected] of [ 9 | [{ a: '1', b: 2, c: true }, 'a=1&b=2&c=true'], 10 | [{ a: null, b: false, c: undefined }, 'a=&b=false'], 11 | [{ 'a/b': 1.28341 }, `${encodeURIComponent('a/b')}=1.28341`], 12 | [ 13 | { 'a/b': 'c/d', 'e=f': 'g&h' }, 14 | `${encodeURIComponent('a/b')}=${encodeURIComponent('c/d')}&${encodeURIComponent( 15 | 'e=f', 16 | )}=${encodeURIComponent('g&h')}`, 17 | ], 18 | ]) { 19 | it(`${JSON.stringify(input)} -> ${expected}`, () => { 20 | expect(stringifyQuery(input)).toEqual(expected); 21 | }); 22 | } 23 | 24 | for (const value of [[], {}, new Date()]) { 25 | it(`${JSON.stringify(value)} -> `, () => { 26 | expect(() => stringifyQuery({ value })).toThrow(`Cannot stringify type ${typeof value}`); 27 | }); 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /tests/uploads.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { toFile, type ResponseLike } from '@browserbasehq/sdk/uploads'; 3 | import { File } from '@browserbasehq/sdk/_shims/index'; 4 | 5 | class MyClass { 6 | name: string = 'foo'; 7 | } 8 | 9 | function mockResponse({ url, content }: { url: string; content?: Blob }): ResponseLike { 10 | return { 11 | url, 12 | blob: async () => content as any, 13 | }; 14 | } 15 | 16 | describe('toFile', () => { 17 | it('throws a helpful error for mismatched types', async () => { 18 | await expect( 19 | // @ts-expect-error intentionally mismatched type 20 | toFile({ foo: 'string' }), 21 | ).rejects.toThrowErrorMatchingInlineSnapshot( 22 | `"Unexpected data type: object; constructor: Object; props: ["foo"]"`, 23 | ); 24 | 25 | await expect( 26 | // @ts-expect-error intentionally mismatched type 27 | toFile(new MyClass()), 28 | ).rejects.toThrowErrorMatchingInlineSnapshot( 29 | `"Unexpected data type: object; constructor: MyClass; props: ["name"]"`, 30 | ); 31 | }); 32 | 33 | it('disallows string at the type-level', async () => { 34 | // @ts-expect-error we intentionally do not type support for `string` 35 | // to help people avoid passing a file path 36 | const file = await toFile('contents'); 37 | expect(file.text()).resolves.toEqual('contents'); 38 | }); 39 | 40 | it('extracts a file name from a Response', async () => { 41 | const response = mockResponse({ url: 'https://example.com/my/audio.mp3' }); 42 | const file = await toFile(response); 43 | expect(file.name).toEqual('audio.mp3'); 44 | }); 45 | 46 | it('extracts a file name from a File', async () => { 47 | const input = new File(['foo'], 'input.jsonl'); 48 | const file = await toFile(input); 49 | expect(file.name).toEqual('input.jsonl'); 50 | }); 51 | 52 | it('extracts a file name from a ReadStream', async () => { 53 | const input = fs.createReadStream('tests/uploads.test.ts'); 54 | const file = await toFile(input); 55 | expect(file.name).toEqual('uploads.test.ts'); 56 | }); 57 | 58 | it('does not copy File objects', async () => { 59 | const input = new File(['foo'], 'input.jsonl', { type: 'jsonl' }); 60 | const file = await toFile(input); 61 | expect(file).toBe(input); 62 | expect(file.name).toEqual('input.jsonl'); 63 | expect(file.type).toBe('jsonl'); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /tsc-multi.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { "extname": ".js", "module": "commonjs" }, 4 | { "extname": ".mjs", "module": "esnext" } 5 | ], 6 | "projects": ["tsconfig.build.json"] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["dist/src"], 4 | "exclude": ["dist/src/_shims/*-deno.ts"], 5 | "compilerOptions": { 6 | "rootDir": "./dist/src", 7 | "paths": { 8 | "@browserbasehq/sdk/*": ["dist/src/*"], 9 | "@browserbasehq/sdk": ["dist/src/index.ts"] 10 | }, 11 | "noEmit": false, 12 | "declaration": true, 13 | "declarationMap": true, 14 | "outDir": "dist", 15 | "pretty": true, 16 | "sourceMap": true 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["dist-deno"], 4 | "exclude": [], 5 | "compilerOptions": { 6 | "rootDir": "./dist-deno", 7 | "lib": ["es2020", "DOM"], 8 | "paths": { 9 | "@browserbasehq/sdk/_shims/auto/*": ["deno/_shims/auto/*-deno"], 10 | "@browserbasehq/sdk/*": ["deno/*"], 11 | "@browserbasehq/sdk": ["deno/index.ts"] 12 | }, 13 | "noEmit": true, 14 | "declaration": true, 15 | "declarationMap": true, 16 | "outDir": "dist-deno", 17 | "pretty": true, 18 | "sourceMap": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.dist-src.json: -------------------------------------------------------------------------------- 1 | { 2 | // this config is included in the published src directory to prevent TS errors 3 | // from appearing when users go to source, and VSCode opens the source .ts file 4 | // via declaration maps 5 | "include": ["index.ts"], 6 | "compilerOptions": { 7 | "target": "es2015", 8 | "lib": ["DOM"], 9 | "moduleResolution": "node" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "tests", "examples"], 3 | "exclude": ["src/_shims/**/*-deno.ts"], 4 | "compilerOptions": { 5 | "target": "es2020", 6 | "lib": ["es2020"], 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "esModuleInterop": true, 10 | "baseUrl": "./", 11 | "paths": { 12 | "@browserbasehq/sdk/_shims/auto/*": ["src/_shims/auto/*-node"], 13 | "@browserbasehq/sdk/*": ["src/*"], 14 | "@browserbasehq/sdk": ["src/index.ts"] 15 | }, 16 | "noEmit": true, 17 | 18 | "resolveJsonModule": true, 19 | 20 | "forceConsistentCasingInFileNames": true, 21 | 22 | "strict": true, 23 | "noImplicitAny": true, 24 | "strictNullChecks": true, 25 | "strictFunctionTypes": true, 26 | "strictBindCallApply": true, 27 | "strictPropertyInitialization": true, 28 | "noImplicitThis": true, 29 | "noImplicitReturns": true, 30 | "alwaysStrict": true, 31 | "exactOptionalPropertyTypes": true, 32 | "noUncheckedIndexedAccess": true, 33 | "noImplicitOverride": true, 34 | "noPropertyAccessFromIndexSignature": true, 35 | "isolatedModules": false, 36 | 37 | "skipLibCheck": true 38 | } 39 | } 40 | --------------------------------------------------------------------------------