mirror of
https://github.com/Kong/insomnia.git
synced 2026-08-04 11:52:33 -04:00
Feat/ia merge (#9904)
* refactor: route fs backed cleanup (#9806) * refactor: shared browser safe helper cleanup (#9810) * refactor: shared browser-safe helper cleanup * style: run eslint autofix * fix: preserve empty url handling * fix: address remaining copilot comments on pr3 * remove loader class * fix: unhandledrejection error (#9774) * fix: resolve sentry promise error (#9786) * fix: resolve sentry promise error * fix: leave fallback when error * fix: improve credential validation handling in GitRepoForm to avoid a loop of re-loading the list of repos and branches (#9820) * add e2e and cli skills (#9818) * add e2e and cli skills * address feedback * address feedback * move to claude * feat: konnect integration proxy url and regex support (#9811) * chore: move konnect sync behind feature flag (#9832) * chore: isolate gRPC proto file preparation behind IPC boundary (#9828) * chore: isolate gRPC proto file preparation behind IPC boundary Move proto temp-file creation out of the renderer by adding a grpc.writeProtoFile IPC handler (main process) and wiring it up in the preload bridge. The renderer's ProtoFilesModal previously called writeProtoFile() directly, pulling node:fs / node:os / node:path into the renderer bundle. It now calls window.main.grpc.writeProtoFile(protoFile._id) instead. Changes: - src/main/ipc/electron.ts: add 'grpc.writeProtoFile' to HandleChannels - src/main/ipc/grpc.ts: export writeProtoFileById helper, add to gRPCBridgeAPI, register ipcMainHandle('grpc.writeProtoFile') - src/entry.preload.ts: wire grpc.writeProtoFile via ipcRenderer.invoke - src/ui/components/modals/proto-files-modal.tsx: remove direct write-proto-file import; use window.main.grpc.writeProtoFile in the directory-import validation loop - config/renderer-node-import-baseline.json: remove 5 stale/resolved baseline entries (proto-directory-loader.tsx x2 already gone; write-proto-file.ts fs/os/path x3 now main-process-only) - src/main/ipc/__tests__/grpc.test.ts: add writeProtoFileById unit tests as contract coverage for the new privileged bridge path Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: validate proto syntax in writeProtoFileById IPC handler The directory-import validation loop relied on writeProtoFile for proto content validation, but writeProtoFile only writes the temp file without parsing. Add a protoLoader.load call inside writeProtoFileById so invalid proto syntax throws before the result is returned to the renderer. Also update the test to mock @grpc/proto-loader.load and assert it is called with the correct file path and includeDirs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: konnect integration strips nunjucks templates on sync (#9831) * fix(Git Sync): auto-resolve non-YAML file conflicts to remote during merge (#9798) * fix: filter conflict paths to include only YAML files * fix: enhance conflict resolution by auto-resolving non-YAML files to theirs * fix: keep buffer raw so that binary files are not corrupted Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: enhance merge conflict handling by introducing auto-resolved conflicts for non-YAML files * fix: add test for handling merge conflicts, ensuring only YAML conflicts are returned * fix: prevent HEAD update during auto-resolve of merge conflicts * fix: enhance merge conflict resolution by auto-completing merges when all conflicts are non-YAML --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * feat: konnect integration expressions support (#9830) * Show more specific error when creating mock route fails (#9841) * fix: insomnia-ai-plugin uses securedPath (INS-2244) (#9748) * feat: add custom npm registry mirror setting for plugin installation (#9837) * feat: default user-agent for cURL imports [INS-2416] (#9838) * feat: default user-agent for cURL imports * respect disableAppVersionUserAgent setting * fix: view transition error - [INS-2316] (#9792) * fix: view transition error * fix * change default behavior when delete cloud sync workspaces (#9844) * feat: integrate v3 user endpoints (#9785) * feat: integrate v3 user endpoints * feat: use public sdk for insomnia-api * chore: applied PoLP to workflows (#9840) * chore: resolve GHA warning annotations and reduce CI time [INS-2312] (#9839) * fix: resolves INS-2366 (#9852) * fix: resolves INS-2366 dependency issues * Refactor:use electron store for oauth session (#9851) * move oauth session to electron storage * create electron storage bridge * use electronStorage bridge for managing oauth window handles * fix build * move key to constants * tolerate changing userData folder path * Update packages/insomnia/src/main/ipc/electron-storage.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * init store * fix singleton class * feedback * feedback * Update packages/insomnia/src/main/electron-storage.ts Co-authored-by: aikido-pr-checks[bot] <169896070+aikido-pr-checks[bot]@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: aikido-pr-checks[bot] <169896070+aikido-pr-checks[bot]@users.noreply.github.com> * chore: decouple releases (#9842) * INS-2145 Decouple releases * fix security error * fix * check version * refactor: auth header to main (#9834) * remove deprecated baseUrl * add failing test * fix AI playwright runs * move getAuthHeader to main * address feedback about dynamic import * move oauth 1 + 2 flow to main * handle bad cookie * handle bad apikey * fix imports * block main process imports * extract plugins * fix vite config * console log * move init store * Fix OAuth imports after rebase Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * clean up * Revert config changes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * clean up hawk * use bridge * update node require * remove this * define process type * remove 14 * ignore reports folder * fix e2e tests * address feedback * remove unused * tidy constants * feat: add getOAuth2Token IPC bridge Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix tests --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: Support pin and unpin websocket and socketio requests (#9865) * support pin websocket and socketio requests * feat(Git Sync): Add support for canonical repository output (#9789) * initial support for canonical repo output (#9739) * Feat/git repo output sync queue (#9790) * feat: implement SyncQueue for serial async task processing * refactor: enhance repo file watcher for improved sync and error handling - Replace NeDB client with a unified disk client for all file operations. - Introduce a serial queue to manage sync tasks and prevent race conditions. - Implement content-hash deduplication to avoid unnecessary file imports. - Add problem tracking for YAML files with conflicts or parse errors. - Streamline watcher start/stop logic and improve notification handling. - Ensure immediate DB→FS flush before git operations to maintain consistency. - Enhance import logic to handle workspace deletions and renames effectively. * refactor: simplify projectRoutableFSClient by removing unused parameters and consolidating logic * feat: add git.db-synced event listener for revalidation in Root component * feat: add button to open local repository folder in ProjectSettingsForm * refactor: remove unused GitProjectNeDBClient * refactor: update imports to use services for workspace and workspaceMeta * refactor: update models usage to services in git repo migration and project settings form * refactor: streamline file watcher initialization and import process * feat: ensure immediate processing of pending debounced imports in RepoFileWatcher * refactor: improve file rename handling in RepoFileWatcher to prevent data loss * feat: enhance RepoFileWatcher to track last written hash and sync mtime for improved file management * refactor: remove unused parameters from upsertDocs in RepoFileWatcher for cleaner code * fix revalidator (#9826) * fix: handle detached HEAD during rebase in getCurrentBranch method (#9843) * fix: handle detached HEAD during rebase in getCurrentBranch method * fix: add return type to getCurrentBranch method * fix: refresh ui after sync (#9848) * fix: (git cli)skip flush problematic files (#9846) * fix: skip flush problematic files * fix * feat: (git cli)ux for invalide status (#9836) * feat: ux for invalide status * update ux * fix * fix * add tab warning * del log * feat(Git Sync): Handle non-origin remotes (#9833) * feat(git): detect non-origin branch tracking and guard sync operations - Add getBranchTrackingRemote(), getRemoteUrl(), getBranchRemoteInfo() to GitVCS - Add getBranchRemoteInfo IPC endpoint with BranchRemoteInfo interface - Add assertBranchOnOrigin() guard to push, pull, fetch, commitAndPush - canPushLoader returns { canPush: false } for non-origin branches - Add unit tests for remote detection methods * feat(git): add support for non-origin branch tracking and display warnings in UI * Show local git repo path [INS-2315] (#9858) * Update the style of local git folder path in project setting modal * Add Git CLI tip in commit changes modal * Repo Migration flow [INS-2256] (#9824) * initial support for canonical repo output (#9739) * feat: enhance git repository migration with concurrency guard and symlink handling * feat: enhance git repository migration with config sanitization and file overwrite handling * feat: implement repo migration version tracking and improve migration idempotency * feat: add runAllGitRepoMigrations function and migration view for Git projects Co-authored-by: Copilot <copilot@github.com> * fix: reset initial migration status to 'default' in MigrationView component * refactor: simplify MigrationView component and update navigation logic * refactor: remove legacy directory structure migration from loadGitRepository function * feat: enhance runAllGitRepoMigrations to return logs and improve error handling in MigrationView * feat: update runAllGitRepoMigrations to return detailed logs and failed projects; enhance MigrationView to handle migration results * feat: optimize runAllGitRepoMigrations by batch-fetching git repositories and improving project filtering * feat: introduce CURRENT_MIGRATION_VERSION constant for migration tracking and update references in git-repo-migration and router * feat: handle failed projects in runAllGitRepoMigrations by converting them to local projects Co-authored-by: Copilot <copilot@github.com> * feat: integrate CURRENT_MIGRATION_VERSION for migration tracking and update router logic to handle migration screen visibility * feat: reorder import statements in ProjectSettingsForm for consistency * feat: update MigrationStatus type and related logic for better error handling * feat: enhance migration logging with detailed error stack and include CURRENT_MIGRATION_VERSION in logs * feat: simplify migration logging messages for clarity and consistency * feat: improve migration check logic to prioritize version stamp over disk layout * feat: add tests for migrateRepoStructureIfNeeded function to ensure migration logic correctness * feat: update migration logic to re-run when old git/ directory exists, ensuring correct migration handling * test: update migration tests to ensure directory existence checks are accurate * refactor: remove redundant useEffect for localStorage in Component * feat: enhance path validation in runAllGitRepoMigrations to prevent path traversal vulnerabilities * feat: enhance path handling in migration functions to prevent directory traversal vulnerabilities * feat: enhance directory traversal protection in moveDirectoryContents function --------- Co-authored-by: James Gatz <jamesgatzos@gmail.com> Co-authored-by: Copilot <copilot@github.com> * fix: Delete old folders (#9867) * refactor: remove unused migration version handling from localStorage * fix: update directory removal logic to handle non-empty directories --------- Co-authored-by: Curry Yang <163384738+CurryYangxx@users.noreply.github.com> Co-authored-by: yaoweiprc <6896642+yaoweiprc@users.noreply.github.com> Co-authored-by: Pavlos Koutoglou <pkoutoglou@gmail.com> Co-authored-by: Copilot <copilot@github.com> * refactor: move sync code to main (#9827) * move sync code to main * improve sync tests * update plan * test: reset cloud sync smoke state Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * guard against bad test env * fix skill * remove new test * udpate plan * with proxy * checkpoint * move files * autofix * update plan * make all sync bridge async * fix window imports * refactor: move main-only sync helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix lint * move chunkArray tests * smaller interfaces * move store under vcs * move cloud-sync to main * create a second vcs for pull operations * added a invoke wrapper to remove error prefixes * rebase error --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: update insomnia-plugin-ai (#9862) * fix: bump node-libcurl and add ipv6 tests (#9869) * feat: revamping pre/post scripting sandbox (#9794) * feat: revamping pre/post scripting sandbox * feat: added UI setting to enable/disable specific checks * fix false positives * revert * fix: user can not resolve conflict in app (#9872) * fix: conflict ux * fix * fix * Git server for smoke test [INS-2258] (#9816) * Git server for smoke test * Try to solve flaky test * feat: remove unused Git hook samples and add Credentials tab functionality - Deleted various sample Git hook scripts from the git-server fixture, including post-update, pre-applypatch, pre-commit, pre-merge-commit, pre-push, pre-rebase, pre-receive, prepare-commit-msg, push-to-checkout, sendemail-validate, and update hooks. - Introduced a new PreferencesCredentialsTab class to manage Git credentials within the Insomnia Preferences. - Updated the PreferencesPage to include the new Credentials tab for Git credentials management. - Enhanced the ProjectPage with a method to create a Git Sync project, including branch creation and switching. - Added comprehensive tests for Git Sync functionality, including creating branches, committing changes, and merging branches. - Updated UI components to support new features, including data-testid attributes for better testability. Co-authored-by: Copilot <copilot@github.com> * feat: update path import and add Git sync tests * revert package.json * Update package.json * feat: add new dependencies for Git HTTP mock server and related utilities * refactor: remove commented-out code in addAccessTokenGitCredential function * fix: update export tests to use toHaveLength for file count assertions --------- Co-authored-by: Copilot <copilot@github.com> * feat: import deep-link login experience [INS-2416] (#9860) * refactor: replace node:url with URL in cert and proxy match (#9515) * refactor: import to main (#9809) * squash * re add comments * fix process fork * update base line 18 left * revert * check cert url without node * fix handlerId * exclude url matches cert host from scope * fix rebase * Fix style issue that file list in the middle of commit modal is collapsed [INS-2315] (#9875) * Fix style issue that file list in the middle of commit modal is collapsed * fix: update links to Git Sync documentation in staging modal and project settings form * Chore: playwright dx v2 (#9876) * Update E2E test for git sync [INS-2258] (#9878) * Add more test cases for git sync * tmp * Update package.json * feat: update migration image and urls (#9868) * feat: update migration image and path for improved clarity * feat: update error message and support links in migration view * feat(Git Sync): Downgrade -> Upgrade path (#9882) * feat: add mechanism to flush newer DB workspaces to disk during downgrade * feat: implement effective Git repository ID handling for project connections * feat: enhance Git repository ID handling for improved project queries and updates * Chore: refine e2e docs by agent for agent (#9881) * improve agent docs * added error context note * chore: Security update for dependencies and github workflows (#9884) * chore: resolves INS-2457, INS-2458, INS-2459, and INS-2460. * feat: include app version in window title [INS-2465] (#9888) * feat(Git Sync): improve git migration onboarding UX and local file system access [INS-2462] (#9890) * feat(Migration): enhance migration summary with total projects count and improve UI feedback * fix(Migration): clarify update instructions and improve user messaging * style(ManualCommitForm): adjust text sizes for improved readability * style(StagingModal): adjust layout and spacing for improved UI consistency * fix(ManualCommitForm): update clipboard text to include 'cd' command for easier navigation * fix(ProjectSettingsForm): update repository path copy functionality and add option to open in file system * fix(ManualCommitForm): enhance file system interaction with tooltips for better user guidance * fix(ProjectSettingsForm): add tooltip for 'Open in file system' button to enhance user guidance * fix(GitProjectSyncDropdown): add 'Open folder' action to sync dropdown for easier access to repository path * fix(Component): display relative path of current issue in modal for better context * fix(git-service): count only successfully migrated projects in totalProjects * fix(project-settings-form): platform-aware shell quoting for cd command * fix(git-project-staging-modal): platform-aware shell quoting for cd command * fix(project-settings-form): update aria-label to reflect cd command clipboard content * fix(git-project-staging-modal): update aria-label to reflect shell command clipboard content * fix(ManualCommitForm): replace tooltip with dialog for enhanced information display * fix(MigrationView): update migrated count calculation to reflect total projects --------- Co-authored-by: James Gatz <jamesgatzos@gmail.com> * chore: normalize konnect api responses (#9895) * feat(Git Sync): enhance migration view with best practices note and UI improvements (#9900) * chore: comment out smctl credentials save in workflow (#9898) the command is no longer executed while keeping it in place for future reference. * feat: migrate model imports, base types, org model and helpers (#9802) * fix * fix vcsinstance * fix type issues --------- Co-authored-by: Jack Kavanagh <jackkav@gmail.com> Co-authored-by: James Gatz <jamesgatzos@gmail.com> Co-authored-by: Shelby <13246465+shelby-moore@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: yaoweiprc <6896642+yaoweiprc@users.noreply.github.com> Co-authored-by: kwburns-kong <kyle.burns@konghq.com> Co-authored-by: jeremyjpj0916 <31913027+jeremyjpj0916@users.noreply.github.com> Co-authored-by: Ryan Willis <ryan.willis@konghq.com> Co-authored-by: Kent Wang <kent.wang@konghq.com> Co-authored-by: Alison Sabuwala <alison.sabuwala1024@gmail.com> Co-authored-by: aikido-pr-checks[bot] <169896070+aikido-pr-checks[bot]@users.noreply.github.com> Co-authored-by: Jay Wu <jay.wu@konghq.com> Co-authored-by: Pavlos Koutoglou <pkoutoglou@gmail.com> Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Fares Osman <43153226+fiosman@users.noreply.github.com> Co-authored-by: Bingbing <ZxBing0066@gmail.com> Co-authored-by: Vivek Thuravupala <2700229+godfrzero@users.noreply.github.com>
This commit is contained in:
58
.claude/skills/fix-test-cli-ci/SKILL.md
Normal file
58
.claude/skills/fix-test-cli-ci/SKILL.md
Normal file
@@ -0,0 +1,58 @@
|
||||
---
|
||||
name: fix-test-cli-ci
|
||||
description: 'Debug failures from the test-cli.yml workflow locally. Use when insomnia-inso bundle tests fail in CI, especially node-vs-electron dependency/runtime mismatches.'
|
||||
argument-hint: 'Provide the failing test-cli.yml logs, a link to the failing workflow run, or the failing test name from npm run test:bundle -w insomnia-inso'
|
||||
---
|
||||
|
||||
# Fix test-cli.yml CI Failures
|
||||
|
||||
## When to Use
|
||||
|
||||
- `.github/workflows/test-cli.yml` or the `Test CLI` workflow failed in CI.
|
||||
- `inso` bundle tests fail locally or in GitHub Actions.
|
||||
- You suspect node module/runtime differences between Node.js and Electron.
|
||||
|
||||
## Procedure
|
||||
|
||||
1. Start with the failing CI evidence.
|
||||
- Use the provided logs or workflow run link to identify whether the failure happened during the `test:bundle` run, the packaged binary run, or the build/package setup before the tests.
|
||||
- Note the failing test name and the first actionable error message before reproducing locally.
|
||||
2. Ensure Node.js native dependencies are installed (not Electron-targeted variants):
|
||||
```bash
|
||||
npm run install-libcurl-node
|
||||
```
|
||||
3. Build `insomnia-inso` to generate `dist`:
|
||||
```bash
|
||||
npm run build -w insomnia-inso
|
||||
```
|
||||
4. Start the smoke-test echo server used by the CLI tests:
|
||||
```bash
|
||||
npm run serve -w insomnia-smoke-test
|
||||
```
|
||||
5. In another terminal, run the bundled CLI test suite:
|
||||
```bash
|
||||
npm run test:bundle -w insomnia-inso
|
||||
```
|
||||
6. If the CI failure was in the packaged binary path or the bundle suite passes locally, run:
|
||||
```bash
|
||||
npm run test:binary -w insomnia-inso
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Keep the smoke-test server running while bundle tests execute.
|
||||
- Common failure patterns:
|
||||
- Errors involving `@getinsomnia/node-libcurl`, `NODE_MODULE_VERSION`, `dlopen`, or native module loading usually mean the Node-targeted libcurl binary needs to be reinstalled with `npm run install-libcurl-node`.
|
||||
- Connection failures to `http://localhost:4010` usually mean the smoke-test server is not running.
|
||||
- Missing `dist` output or missing `binaries/inso` usually means `npm run build -w insomnia-inso` or the package step needs to be rerun.
|
||||
- Success criteria:
|
||||
- `npm run test:bundle -w insomnia-inso` exits successfully and Vitest reports the `inso dev bundle` tests as passing.
|
||||
- If validating the packaged binary path, `npm run test:binary -w insomnia-inso` also exits successfully.
|
||||
|
||||
## Teardown
|
||||
|
||||
- Stop the `npm run serve -w insomnia-smoke-test` process once validation is complete.
|
||||
- Reinstall the electron-targeted libcurl binary for continued Electron development:
|
||||
```bash
|
||||
npm run install-libcurl-electron
|
||||
```
|
||||
16
.codegraph/.gitignore
vendored
Normal file
16
.codegraph/.gitignore
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
# CodeGraph data files
|
||||
# These are local to each machine and should not be committed
|
||||
|
||||
# Database
|
||||
*.db
|
||||
*.db-wal
|
||||
*.db-shm
|
||||
|
||||
# Cache
|
||||
cache/
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Hook markers
|
||||
.dirty
|
||||
5
.github/workflows/homebrew.yml
vendored
5
.github/workflows/homebrew.yml
vendored
@@ -7,12 +7,15 @@ on:
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
update_homebrew:
|
||||
timeout-minutes: 5
|
||||
name: Update Insomnia Formula
|
||||
# must be macos, linux-brew doesn't have casks
|
||||
runs-on: macos-latest
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Set up Homebrew
|
||||
id: set-up-homebrew
|
||||
@@ -22,7 +25,7 @@ jobs:
|
||||
|
||||
- name: Cache Homebrew Bundler RubyGems
|
||||
id: cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
||||
with:
|
||||
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
|
||||
key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}
|
||||
|
||||
34
.github/workflows/release-build.yml
vendored
34
.github/workflows/release-build.yml
vendored
@@ -6,6 +6,8 @@ on:
|
||||
- 'release/**'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -15,11 +17,10 @@ jobs:
|
||||
generate-sbom-and-upload-assets:
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
packages: write
|
||||
contents: write # publish sbom to GH releases/tag assets
|
||||
contents: read # Required for actions/checkout
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
# Perform SCA / SBOM analysis for the entire monorepo code repository
|
||||
# Produces SCA(SBOM and CVE) report
|
||||
@@ -35,6 +36,9 @@ jobs:
|
||||
build-and-upload-release-artifacts:
|
||||
timeout-minutes: 45
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
env:
|
||||
INSO_PACKAGE_NAME: insomnia-inso
|
||||
INSO_DOCKER_TAR: inso-docker-image.tar
|
||||
@@ -58,10 +62,10 @@ jobs:
|
||||
# csc_key_password_secret: ''
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -124,7 +128,7 @@ jobs:
|
||||
# smctl will be used in the next step for signing
|
||||
- name: Setup Software Trust Manager
|
||||
if: runner.os == 'Windows'
|
||||
uses: digicert/code-signing-software-trust-action@9b30180369343eb1ce0dcbebb933cfa3e17b6cc8 # v1
|
||||
uses: digicert/code-signing-software-trust-action@9b30180369343eb1ce0dcbebb933cfa3e17b6cc8 # v1.0.0
|
||||
with:
|
||||
simple-signing-mode: true
|
||||
env:
|
||||
@@ -137,7 +141,7 @@ jobs:
|
||||
if: runner.os == 'Windows'
|
||||
shell: bash
|
||||
run: |
|
||||
smctl credentials save ${SM_API_KEY} ${SM_CLIENT_CERT_PASSWORD}
|
||||
# smctl credentials save ${SM_API_KEY} ${SM_CLIENT_CERT_PASSWORD}
|
||||
NODE_OPTIONS='--max_old_space_size=6144' npm run package:windows:unpacked -w insomnia
|
||||
env:
|
||||
SM_HOST: ${{ vars.DIGICERT_SM_HOST }}
|
||||
@@ -164,7 +168,7 @@ jobs:
|
||||
# this installs smctl as well
|
||||
- name: Code-sign unpacked .exe (Windows only)
|
||||
if: runner.os == 'Windows'
|
||||
uses: digicert/code-signing-software-trust-action@9b30180369343eb1ce0dcbebb933cfa3e17b6cc8 # v1
|
||||
uses: digicert/code-signing-software-trust-action@9b30180369343eb1ce0dcbebb933cfa3e17b6cc8 # v1.0.0
|
||||
with:
|
||||
simple-signing-mode: true
|
||||
# If the below 2 parameters are supplied, then smctl executable is invoked to attempt the signing.
|
||||
@@ -242,7 +246,7 @@ jobs:
|
||||
|
||||
- name: Code-sign inso exe (Windows only)
|
||||
if: runner.os == 'Windows'
|
||||
uses: digicert/code-signing-software-trust-action@9b30180369343eb1ce0dcbebb933cfa3e17b6cc8 # v1
|
||||
uses: digicert/code-signing-software-trust-action@9b30180369343eb1ce0dcbebb933cfa3e17b6cc8 # v1.0.0
|
||||
with:
|
||||
simple-signing-mode: true
|
||||
# If the below 2 parameters are supplied, then smctl executable is invoked to attempt the signing.
|
||||
@@ -273,7 +277,7 @@ jobs:
|
||||
|
||||
- name: Notarize Inso CLI installer (macOS only)
|
||||
if: runner.os == 'macOS'
|
||||
uses: lando/notarize-action@b5c3ef16cf2fbcf2af26dc58c90255ec242abeed # v2
|
||||
uses: lando/notarize-action@b5c3ef16cf2fbcf2af26dc58c90255ec242abeed # v2.0.2
|
||||
with:
|
||||
product-path: ./packages/${{ env.INSO_PACKAGE_NAME }}/artifacts/inso-${{ matrix.os }}-${{ env.INSO_VERSION }}.pkg
|
||||
primary-bundle-id: com.insomnia.inso
|
||||
@@ -290,7 +294,7 @@ jobs:
|
||||
|
||||
- name: Notarize Inso CLI binary (macOS only)
|
||||
if: runner.os == 'macOS'
|
||||
uses: lando/notarize-action@b5c3ef16cf2fbcf2af26dc58c90255ec242abeed # v2
|
||||
uses: lando/notarize-action@b5c3ef16cf2fbcf2af26dc58c90255ec242abeed # v2.0.2
|
||||
with:
|
||||
product-path: ./packages/${{ env.INSO_PACKAGE_NAME }}/binaries/inso
|
||||
primary-bundle-id: com.insomnia.inso-binary
|
||||
@@ -303,7 +307,7 @@ jobs:
|
||||
|
||||
- name: Login to Docker Hub
|
||||
if: runner.os == 'Linux' && runner.arch == 'X64'
|
||||
uses: docker/login-action@3d58c274f17dffee475a5520cbe67f0a882c4dbb # v2.1.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_REGISTRY_USER }}
|
||||
password: ${{ secrets.DOCKER_REGISTRY_TOKEN }}
|
||||
@@ -329,7 +333,7 @@ jobs:
|
||||
SYFT_SOURCE_NAME: ${{ env.INSO_DOCKER_TAR }}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
if-no-files-found: ignore
|
||||
name: ${{ runner.os }}-${{ runner.arch }}-artifacts
|
||||
@@ -347,7 +351,7 @@ jobs:
|
||||
packages/insomnia-inso/artifacts/*
|
||||
|
||||
- name: Upload source assets for Sentry
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ runner.os }}-${{ runner.arch }}-sentry
|
||||
path: |
|
||||
@@ -359,6 +363,8 @@ jobs:
|
||||
timeout-minutes: ${{ fromJSON(vars.GHA_DEFAULT_TIMEOUT) }}
|
||||
needs: build-and-upload-release-artifacts
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Get release version
|
||||
id: release_version
|
||||
|
||||
52
.github/workflows/release-publish.yml
vendored
52
.github/workflows/release-publish.yml
vendored
@@ -18,6 +18,8 @@ env:
|
||||
INSO_DOCKER_IMAGE: &INSO_DOCKER_IMAGE 'kong/inso' # By default, registry is docker.io
|
||||
NOTARY_REPOSITORY: &NOTARY_REPOSITORY 'kong/notary' # All signatures will be pushed to public notary repository
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
timeout-minutes: 30
|
||||
@@ -35,15 +37,24 @@ jobs:
|
||||
contents: write # Required to upload assets. Issue: https://github.com/slsa-framework/slsa-github-generator/tree/main/internal/builders/container#known-issues
|
||||
packages: write
|
||||
steps:
|
||||
- name: Calculate Release Branch
|
||||
env:
|
||||
VERSION: ${{ github.event.inputs.version }}
|
||||
run: |
|
||||
MAJOR_MINOR=$(echo "$VERSION" | cut -d. -f1,2)
|
||||
|
||||
# Rewrite the release branch to follow our new flow
|
||||
echo "RELEASE_BRANCH=release/$MAJOR_MINOR" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout branch # Check out the release branch
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ env.RELEASE_BRANCH }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -57,8 +68,18 @@ jobs:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||
|
||||
- name: Compare input version with package.json version
|
||||
env:
|
||||
VERSION: ${{ github.event.inputs.version }}
|
||||
run: |
|
||||
PKG_VERSION=$(jq .version packages/insomnia-inso/package.json -rj)
|
||||
if [ "$PKG_VERSION" != "$VERSION" ]; then
|
||||
echo "Input version $VERSION does not match package.json version $PKG_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Download all artifacts from release-build.yml
|
||||
uses: dawidd6/action-download-artifact@268677152d06ba59fcec7a7f0b5d961b6ccd7e1e # v2
|
||||
uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20
|
||||
with:
|
||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||
workflow: release-build.yml
|
||||
@@ -82,13 +103,13 @@ jobs:
|
||||
ELECTRON_ARTIFACT_BASE64_FILE: ${{runner.temp}}/electron_digests_file.text
|
||||
|
||||
- name: Calculate CLI Binary base64 file handle
|
||||
uses: slsa-framework/slsa-github-generator/actions/generator/generic/create-base64-subjects-from-file@5a775b367a56d5bd118a224a811bba288150a563 # v2.0.0
|
||||
uses: slsa-framework/slsa-github-generator/actions/generator/generic/create-base64-subjects-from-file@f7dd8c54c2067bafc12ca7a55595d5ee9b75204a # v2.1.0
|
||||
id: cli_binary_hashes
|
||||
with:
|
||||
path: ${{ env.CLI_ARTIFACT_BASE64_FILE }}
|
||||
|
||||
- name: Calculate Electron Binary base64 file handle
|
||||
uses: slsa-framework/slsa-github-generator/actions/generator/generic/create-base64-subjects-from-file@5a775b367a56d5bd118a224a811bba288150a563 # v2.0.0
|
||||
uses: slsa-framework/slsa-github-generator/actions/generator/generic/create-base64-subjects-from-file@f7dd8c54c2067bafc12ca7a55595d5ee9b75204a # v2.1.0
|
||||
id: electron_binary_hashes
|
||||
with:
|
||||
path: ${{ env.ELECTRON_ARTIFACT_BASE64_FILE }}
|
||||
@@ -137,7 +158,7 @@ jobs:
|
||||
docker image ls
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.1.0
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_REGISTRY_USER }}
|
||||
password: ${{ secrets.DOCKER_REGISTRY_TOKEN }}
|
||||
@@ -259,21 +280,6 @@ jobs:
|
||||
--package-type insomnia
|
||||
${{ env.IS_PRERELEASE == 'true' && '--internal' || '--publish' }}
|
||||
|
||||
- name: Configure Git user
|
||||
uses: Homebrew/actions/git-user-config@266845213695c3047d210b2e8fbc42ecdaf45802 # master
|
||||
with:
|
||||
username: ${{ (github.event_name == 'workflow_dispatch' && github.actor) || 'insomnia-infra' }}
|
||||
|
||||
- name: Merge git branch into develop
|
||||
run: |
|
||||
remote_repo="https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
|
||||
git checkout develop
|
||||
git merge --no-ff ${{ env.RELEASE_BRANCH }}
|
||||
git status
|
||||
git push "${remote_repo}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
artifact-provenance:
|
||||
needs: [publish]
|
||||
permissions:
|
||||
@@ -290,7 +296,7 @@ jobs:
|
||||
- product: inso
|
||||
binary_artifacts_subject_as_file: ${{ needs.publish.outputs.INSO_BINARY_ARTIFACTS_SUBJECTS_AS_FILE }}
|
||||
# need to use non hash version because of: https://github.com/slsa-framework/slsa-github-generator/issues/3498
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
|
||||
with:
|
||||
base64-subjects-as-file: '${{ matrix.binary_artifacts_subject_as_file }}'
|
||||
upload-assets: true
|
||||
@@ -306,7 +312,7 @@ jobs:
|
||||
packages: write # Required for publishing provenance. Issue: https://github.com/slsa-framework/slsa-github-generator/tree/main/internal/builders/container#known-issues
|
||||
# need to use non hash version because of: https://github.com/slsa-framework/slsa-github-generator/issues/3498
|
||||
contents: write
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
|
||||
with:
|
||||
image: *INSO_DOCKER_IMAGE
|
||||
digest: ${{ needs.publish.outputs.INSO_DOCKER_IMAGE_DIGEST }}
|
||||
|
||||
13
.github/workflows/release-recurring.yml
vendored
13
.github/workflows/release-recurring.yml
vendored
@@ -15,6 +15,9 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
env:
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build-and-upload-artifacts:
|
||||
timeout-minutes: 45
|
||||
@@ -23,6 +26,7 @@ jobs:
|
||||
if: ${{ !startsWith(github.head_ref, 'release/') }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
@@ -38,10 +42,10 @@ jobs:
|
||||
build-targets: tar.gz
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -64,6 +68,7 @@ jobs:
|
||||
run: npm --workspaces version prerelease --preid="alpha-pr-$(git rev-parse --short HEAD)" --no-git-tag-version
|
||||
|
||||
- name: Package
|
||||
if: ${{ matrix.os != 'windows-latest' }}
|
||||
shell: bash
|
||||
run: NODE_OPTIONS='--max_old_space_size=6144' BUILD_TARGETS='${{ matrix.build-targets }}' npm run app-package
|
||||
|
||||
@@ -83,7 +88,7 @@ jobs:
|
||||
INSOMNIA_UPDATES_URL: http://localhost:4010
|
||||
|
||||
- name: Upload smoke test traces
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
if: failure()
|
||||
with:
|
||||
if-no-files-found: ignore
|
||||
@@ -91,7 +96,7 @@ jobs:
|
||||
path: packages/insomnia-smoke-test/traces
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
if-no-files-found: ignore
|
||||
name: ${{ matrix.os }}-artifacts-${{ github.run_number }}
|
||||
|
||||
41
.github/workflows/release-start.yml
vendored
41
.github/workflows/release-start.yml
vendored
@@ -16,20 +16,24 @@ on:
|
||||
required: false
|
||||
description: force version of the release (e.g. 9.0.0) if previous release was successful, this should auto increment
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
setup-release-branch:
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
ref: develop
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -49,23 +53,32 @@ jobs:
|
||||
|
||||
- name: App version (stable, with a specific version)
|
||||
if: github.event.inputs.channel == 'stable' && github.event.inputs.version
|
||||
run: npm --workspaces version "${{ github.event.inputs.version }}"
|
||||
env:
|
||||
VERSION: ${{ github.event.inputs.version }}
|
||||
run: npm --workspaces version "$VERSION"
|
||||
|
||||
- name: App version (alpha/beta, patch latest alpha/beta)
|
||||
if: github.event.inputs.channel != 'stable' && !github.event.inputs.version
|
||||
run: npm --workspaces version --preid "${{ github.event.inputs.channel }}" prerelease
|
||||
env:
|
||||
CHANNEL: ${{ github.event.inputs.channel }}
|
||||
run: npm --workspaces version --preid "$CHANNEL" prerelease
|
||||
|
||||
- name: App version (alpha/beta, with a specific version)
|
||||
if: github.event.inputs.channel != 'stable' && github.event.inputs.version
|
||||
run: npm --workspaces version "${{ github.event.inputs.version }}"
|
||||
env:
|
||||
VERSION: ${{ github.event.inputs.version }}
|
||||
run: npm --workspaces version "$VERSION"
|
||||
|
||||
# ############################################################
|
||||
|
||||
- name: Get version
|
||||
shell: bash
|
||||
run: |
|
||||
echo "RELEASE_VERSION=$(node -e "console.log(require('./packages/insomnia/package.json').version)")" >> $GITHUB_ENV
|
||||
echo "RELEASE_BRANCH=release/$(node -e "console.log(require('./packages/insomnia/package.json').version)")" >> $GITHUB_ENV
|
||||
VERSION=$(node -p "require('./packages/insomnia/package.json').version")
|
||||
MAJOR_MINOR=$(echo $VERSION | cut -d. -f1,2)
|
||||
|
||||
echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "RELEASE_BRANCH=release/$MAJOR_MINOR" >> $GITHUB_ENV
|
||||
|
||||
- name: Create Branch # Create a branch if it doesn't exist
|
||||
uses: peterjgrainger/action-create-branch@c2800a3a9edbba2218da6861fa46496cf8f3195a # v2.2.0
|
||||
@@ -75,7 +88,7 @@ jobs:
|
||||
branch: ${{ env.RELEASE_BRANCH }}
|
||||
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ env.RELEASE_BRANCH }}
|
||||
persist-credentials: false
|
||||
@@ -95,15 +108,21 @@ jobs:
|
||||
|
||||
- name: (Re-run) App version (stable, with a specific version)
|
||||
if: github.event.inputs.channel == 'stable' && github.event.inputs.version
|
||||
run: npm --workspaces version "${{ github.event.inputs.version }}"
|
||||
env:
|
||||
VERSION: ${{ github.event.inputs.version }}
|
||||
run: npm --workspaces version "$VERSION"
|
||||
|
||||
- name: (Re-run) App version (alpha/beta, patch latest alpha/beta)
|
||||
if: github.event.inputs.channel != 'stable' && !github.event.inputs.version
|
||||
run: npm --workspaces version --preid "${{ github.event.inputs.channel }}" prerelease
|
||||
env:
|
||||
CHANNEL: ${{ github.event.inputs.channel }}
|
||||
run: npm --workspaces version --preid "$CHANNEL" prerelease
|
||||
|
||||
- name: (Re-run) App version (alpha/beta, with a specific version)
|
||||
if: github.event.inputs.channel != 'stable' && github.event.inputs.version
|
||||
run: npm --workspaces version "${{ github.event.inputs.version }}"
|
||||
env:
|
||||
VERSION: ${{ github.event.inputs.version }}
|
||||
run: npm --workspaces version "$VERSION"
|
||||
|
||||
# ############################################################
|
||||
|
||||
|
||||
8
.github/workflows/sast.yml
vendored
8
.github/workflows/sast.yml
vendored
@@ -8,20 +8,18 @@ on:
|
||||
- release/*
|
||||
workflow_dispatch: {}
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
semgrep:
|
||||
timeout-minutes: 5
|
||||
name: Semgrep SAST
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
if: (github.actor != 'dependabot[bot]')
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: Kong/public-shared-actions/security-actions/semgrep@a18abf762d6e2444bcbfd20de70451ea1e3bc1b1 # 4.0.1
|
||||
|
||||
13
.github/workflows/test-cli.yml
vendored
13
.github/workflows/test-cli.yml
vendored
@@ -15,20 +15,21 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
Test:
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'npm'
|
||||
@@ -82,7 +83,7 @@ jobs:
|
||||
VERSION: ${{ steps.inso-variables.outputs.inso-version }}
|
||||
|
||||
- name: Upload Inso CLI artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
if-no-files-found: ignore
|
||||
name: ${{ steps.inso-variables.outputs.pkg-name }}
|
||||
|
||||
69
.github/workflows/test-e2e.yml
vendored
69
.github/workflows/test-e2e.yml
vendored
@@ -15,20 +15,21 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
Test:
|
||||
timeout-minutes: 40
|
||||
build:
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
cache: npm
|
||||
@@ -45,13 +46,57 @@ jobs:
|
||||
- name: Build app for smoke tests
|
||||
run: NODE_OPTIONS='--max_old_space_size=6144' npm run app-build
|
||||
|
||||
- name: Smoke test electron app
|
||||
run: npm run test:build -w packages/insomnia-smoke-test -- --project=Smoke
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: app-build
|
||||
path: packages/insomnia/build/
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload smoke test
|
||||
uses: actions/upload-artifact@v7
|
||||
test:
|
||||
needs: build
|
||||
timeout-minutes: 25
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
shardIndex: [1, 2, 3, 4, 5, 6]
|
||||
shardTotal: [6]
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
cache: npm
|
||||
cache-dependency-path: package-lock.json
|
||||
registry-url: 'https://npm.pkg.github.com'
|
||||
scope: '@kong'
|
||||
|
||||
- name: Install packages
|
||||
run: npm ci
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
|
||||
|
||||
- name: Download build artifact
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: app-build
|
||||
path: packages/insomnia/build/
|
||||
|
||||
- name: Smoke test electron app (shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }})
|
||||
run: npm run test:build -w packages/insomnia-smoke-test -- --project=Smoke --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
||||
|
||||
- name: Upload smoke test traces
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
if-no-files-found: ignore
|
||||
name: ubuntu-smoke-test-traces-${{ github.run_number }}
|
||||
name: ubuntu-smoke-test-traces-${{ github.run_number }}-shard-${{ matrix.shardIndex }}
|
||||
path: packages/insomnia-smoke-test/traces
|
||||
|
||||
12
.github/workflows/test.yml
vendored
12
.github/workflows/test.yml
vendored
@@ -15,6 +15,8 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
Test:
|
||||
timeout-minutes: 20
|
||||
@@ -25,10 +27,10 @@ jobs:
|
||||
packages: read
|
||||
steps:
|
||||
- name: Checkout branch
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
cache: npm
|
||||
@@ -56,14 +58,14 @@ jobs:
|
||||
|
||||
- name: Checkout base branch (cycle comparison)
|
||||
if: github.event_name == 'pull_request' && always()
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.base.ref }}
|
||||
path: insomnia-base
|
||||
|
||||
- name: Setup Node (base branch tree)
|
||||
if: github.event_name == 'pull_request' && always()
|
||||
uses: actions/setup-node@v6
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
with:
|
||||
node-version-file: insomnia-base/.nvmrc
|
||||
cache: npm
|
||||
@@ -81,7 +83,7 @@ jobs:
|
||||
|
||||
- name: Check Circular References
|
||||
if: github.event_name == 'pull_request' && always()
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
4
.github/workflows/update-changelog.yml
vendored
4
.github/workflows/update-changelog.yml
vendored
@@ -5,6 +5,8 @@ on:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-24.04
|
||||
@@ -17,7 +19,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.event.release.target_commitish }}
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -22,6 +22,7 @@ node_modules/
|
||||
.yarn-integrity
|
||||
.env
|
||||
.idea
|
||||
.reports
|
||||
*.iml
|
||||
.DS_Store
|
||||
*test-plugins
|
||||
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -43,5 +43,6 @@
|
||||
],
|
||||
"[cpp]": {
|
||||
"editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd"
|
||||
}
|
||||
},
|
||||
"editor.formatOnPaste": true
|
||||
}
|
||||
|
||||
@@ -65,6 +65,9 @@ Organization
|
||||
- **HTTP Calls:** Use `insomniaFetch()` for Insomnia backend APIs. Use plain `fetch()` for external/third-party APIs.
|
||||
- **Styling:** Tailwind utility classes only. Use `clsx`/`tailwind-merge` for conditionals. Use React Aria for interactive HTML elements.
|
||||
- **Testing:** Use **Vitest** (unit) and **Playwright** (E2E). Co-locate unit tests as `filename.test.ts`. Use `vi.mock()`. Prefer testing logic via loaders over mounting components.
|
||||
- **E2E tests** live in `packages/insomnia-smoke-test/`. Full docs: [`packages/insomnia-smoke-test/README.md`](packages/insomnia-smoke-test/README.md).
|
||||
- Run E2E from repo root: `npm run test:smoke:dev` (filter: `npm run test:smoke:dev -- <title-substring>`).
|
||||
- New test imports: `import { test } from '../../playwright/test'` and `import { expect } from '@playwright/test'`.
|
||||
|
||||
## Sensitive Data
|
||||
- **Vault system (AES-GCM):** For environment secrets (`EnvironmentKvPairDataType.SECRET`).
|
||||
|
||||
@@ -61,7 +61,7 @@ There are a few notable directories inside it:
|
||||
- `/src/ui` React components and styling.
|
||||
- `/src/common` Utilities used across both main and render processes.
|
||||
- `/src/plugins` Logic around installation and usage of plugins.
|
||||
- `/src/models` DB models used to store user data.
|
||||
- `/src/insomnia-data` Data models, services and database for managing application state.
|
||||
- `/src/network` Sending requests and performing auth (e.g. OAuth 2).
|
||||
- `/src/templating` Nunjucks and rendering related code.
|
||||
- `/src/sync` and `/src/account` Team sync and account stuff.
|
||||
|
||||
@@ -12,6 +12,23 @@ import globals from 'globals';
|
||||
import tseslint from 'typescript-eslint';
|
||||
|
||||
const rendererBuiltinSpecifiers = [...builtinModules, ...builtinModules.map(moduleName => `node:${moduleName}`)];
|
||||
const generalRestrictedImportPatterns = [
|
||||
// Shouldn't import packages by relative path
|
||||
{
|
||||
group: ['**/*/insomnia-api/**'],
|
||||
message: "Please use 'insomnia-api' instead of relative paths",
|
||||
},
|
||||
// Block relative paths to insomnia-data
|
||||
{
|
||||
group: ['./**/insomnia-data', './**/insomnia-data/**', '../**/insomnia-data', '../**/insomnia-data/**'],
|
||||
message: "Please use '~/insomnia-data' instead of relative paths",
|
||||
},
|
||||
// Only allow ~/insomnia-data and ~/insomnia-data/node
|
||||
{
|
||||
regex: '^~/insomnia-data/(?!node($|/)).+',
|
||||
message: "Only '~/insomnia-data' and '~/insomnia-data/node' are allowed",
|
||||
},
|
||||
];
|
||||
const rendererNodeMigrationOffenders = [
|
||||
'packages/insomnia/src/common/misc.ts',
|
||||
'packages/insomnia/src/common/significant-diff-detection.ts',
|
||||
@@ -86,23 +103,6 @@ export default defineConfig([
|
||||
'playwright/no-wait-for-timeout': 'error',
|
||||
},
|
||||
},
|
||||
// nodeIntegration: false section
|
||||
{
|
||||
files: [
|
||||
'packages/insomnia/src/ui/**/*.{ts,tsx}',
|
||||
'packages/insomnia/src/routes/**/*.{ts,tsx}',
|
||||
'packages/insomnia/src/common/**/*.{ts,tsx}',
|
||||
],
|
||||
ignores: rendererNodeRestrictionIgnores,
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
paths: rendererBuiltinSpecifiers,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// React hooks section
|
||||
{
|
||||
files: ['packages/insomnia/src/**/*.{ts,tsx}'],
|
||||
@@ -168,23 +168,25 @@ export default defineConfig([
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
patterns: [
|
||||
// Shouldn't import packages by relative path
|
||||
{
|
||||
group: ['**/*/insomnia-api/**'],
|
||||
message: "Please use 'insomnia-api' instead of relative paths",
|
||||
},
|
||||
// Block relative paths to insomnia-data
|
||||
{
|
||||
group: ['./**/insomnia-data', './**/insomnia-data/**', '../**/insomnia-data', '../**/insomnia-data/**'],
|
||||
message: "Please use '~/insomnia-data' instead of relative paths",
|
||||
},
|
||||
// Only allow ~/insomnia-data and ~/insomnia-data/node
|
||||
{
|
||||
regex: '^~/insomnia-data/(?!node($|/)).+',
|
||||
message: "Only '~/insomnia-data' and '~/insomnia-data/node' are allowed",
|
||||
},
|
||||
],
|
||||
patterns: generalRestrictedImportPatterns,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// nodeIntegration: false section
|
||||
{
|
||||
files: [
|
||||
'packages/insomnia/src/ui/**/*.{ts,tsx}',
|
||||
'packages/insomnia/src/routes/**/*.{ts,tsx}',
|
||||
'packages/insomnia/src/common/**/*.{ts,tsx}',
|
||||
],
|
||||
ignores: rendererNodeRestrictionIgnores,
|
||||
rules: {
|
||||
'no-restricted-imports': [
|
||||
'error',
|
||||
{
|
||||
paths: rendererBuiltinSpecifiers,
|
||||
patterns: generalRestrictedImportPatterns,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
313
package-lock.json
generated
313
package-lock.json
generated
@@ -18,7 +18,7 @@
|
||||
"packages/insomnia-scripting-environment"
|
||||
],
|
||||
"dependencies": {
|
||||
"@getinsomnia/node-libcurl": "3.2.1",
|
||||
"@getinsomnia/node-libcurl": "3.2.2",
|
||||
"ajv": "^8.17.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -3527,10 +3527,16 @@
|
||||
"node": "^20.17.0 || >=22.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@getinsomnia/insomnia-v3-fetch": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@getinsomnia/insomnia-v3-fetch/-/insomnia-v3-fetch-1.0.1.tgz",
|
||||
"integrity": "sha512-v/0lZ6Fz700xLd+YgqqsAi50HTuZ6l/klc2z8G4v7PgWAtEG2O4tIUY/9yn1WtdH21SQtgBz9kWxIxTPUGtzUQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@getinsomnia/node-libcurl": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@getinsomnia/node-libcurl/-/node-libcurl-3.2.1.tgz",
|
||||
"integrity": "sha512-QaB81JOAvAxkGUNeXIBrERuUhONf+d46f7ZTyPg6ayc3q48NyAhQUznNcbajynbCJjXRsJBqb0qAY8pK2ShNGQ==",
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@getinsomnia/node-libcurl/-/node-libcurl-3.2.2.tgz",
|
||||
"integrity": "sha512-O4FTODtfwvLpnk+pyPHCIINCmu8Io+UaDDmcY2bU73VumAu6p/p/egQ6ndUOOId6OQ7V2jyAppZFgNb+fev/Uw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3572,9 +3578,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@google/genai": {
|
||||
"version": "1.48.0",
|
||||
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.48.0.tgz",
|
||||
"integrity": "sha512-plonYK4ML2PrxsRD9SeqmFt76eREWkQdPCglOA6aYDzL1AAbE+7PUnT54SvpWGfws13L0AZEqGSpL7+1IPnTxQ==",
|
||||
"version": "1.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.50.1.tgz",
|
||||
"integrity": "sha512-YbkX7H9+1Pt8wOt7DDREy8XSoiL6fRDzZQRyaVBarFf8MR3zHGqVdvM4cLbDXqPhxqvegZShgfxb8kw9C7YhAQ==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -4028,20 +4034,55 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@kong/insomnia-plugin-ai": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://npm.pkg.github.com/download/@kong/insomnia-plugin-ai/1.0.9/1819b2d915bf1dcfc74211c2bb08005bc18813e6",
|
||||
"integrity": "sha512-9dj+9IsgJlW9fKC3XRAVEI2165jA+m11Gbyx6Z7cbqXne+ksupGrcmU4HnndkRKMCoIlNH10f2VGVgN8n8vhWA==",
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://npm.pkg.github.com/download/@kong/insomnia-plugin-ai/1.0.11/787a5587e7bcbcea66430dcec379253644ef00c7",
|
||||
"integrity": "sha512-qF1+PD950aqDA5l3W7N4VFN3BAeLTzOs62+Ig6dLcP6uv4SNjnTgcRZ3ubRhScfPT+V5hB0qIUbG0We3fFEGvg==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@apidevtools/swagger-parser": "^10.1.0",
|
||||
"@google/genai": "^1.38.0",
|
||||
"@apidevtools/swagger-parser": "^12.1.0",
|
||||
"@google/genai": "^1.49.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"node-llama-cpp": "^3.15.0",
|
||||
"openai": "^6.17.0",
|
||||
"node-llama-cpp": "^3.18.1",
|
||||
"openai": "^6.34.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-linux-x64-gnu": "^4.55.3"
|
||||
"@rollup/rollup-linux-x64-gnu": "^4.60.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@kong/insomnia-plugin-ai/node_modules/@apidevtools/json-schema-ref-parser": {
|
||||
"version": "14.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.0.1.tgz",
|
||||
"integrity": "sha512-Oc96zvmxx1fqoSEdUmfmvvb59/KDOnUoJ7s2t7bISyAn0XEz57LCCw8k2Y4Pf3mwKaZLMciESALORLgfe2frCw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"js-yaml": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/philsturgeon"
|
||||
}
|
||||
},
|
||||
"node_modules/@kong/insomnia-plugin-ai/node_modules/@apidevtools/swagger-parser": {
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-12.1.0.tgz",
|
||||
"integrity": "sha512-e5mJoswsnAX0jG+J09xHFYQXb/bUc5S3pLpMxUuRUA2H8T2kni3yEoyz2R3Dltw5f4A6j6rPNMpWTK+iVDFlng==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@apidevtools/json-schema-ref-parser": "14.0.1",
|
||||
"@apidevtools/openapi-schemas": "^2.1.0",
|
||||
"@apidevtools/swagger-methods": "^3.0.2",
|
||||
"ajv": "^8.17.1",
|
||||
"ajv-draft-04": "^1.0.0",
|
||||
"call-me-maybe": "^1.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"openapi-types": ">=7"
|
||||
}
|
||||
},
|
||||
"node_modules/@kong/insomnia-plugin-external-vault": {
|
||||
@@ -5125,13 +5166,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.55.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.1.tgz",
|
||||
"integrity": "sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==",
|
||||
"version": "1.59.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz",
|
||||
"integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.55.1"
|
||||
"playwright": "1.59.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -5165,9 +5206,9 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/codegen": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
|
||||
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
|
||||
"integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/eventemitter": {
|
||||
@@ -5193,9 +5234,9 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/inquire": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
|
||||
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.1.tgz",
|
||||
"integrity": "sha512-mnzgDV26ueAvk7rsbt9L7bE0SuAoqyuys/sMMrmVcN5x9VsxpcG3rqAUSgDyLp0UZlmNfIbQ4fHfCtreVBk8Ew==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/path": {
|
||||
@@ -5211,9 +5252,9 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/utf8": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
|
||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz",
|
||||
"integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@ravanallc/grpc-server-reflection": {
|
||||
@@ -9040,9 +9081,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@stoplight/spectral-core": {
|
||||
"version": "1.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@stoplight/spectral-core/-/spectral-core-1.21.0.tgz",
|
||||
"integrity": "sha512-oj4e/FrDLUhBRocIW+lRMKlJ/q/rDZw61HkLbTFsdMd+f/FTkli2xHNB1YC6n1mrMKjjvy7XlUuFkC7XxtgbWw==",
|
||||
"version": "1.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@stoplight/spectral-core/-/spectral-core-1.22.0.tgz",
|
||||
"integrity": "sha512-4hTxMDs4TFUG4/jKjaZttA65gNuV2PCKI9+51I+J4nL6ylo17DlbW+sl6byKnBuV/85HxaV33ri5fEGlp8lTSA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@stoplight/better-ajv-errors": "1.0.3",
|
||||
@@ -9054,17 +9095,17 @@
|
||||
"@stoplight/types": "~13.6.0",
|
||||
"@types/es-aggregate-error": "^1.0.2",
|
||||
"@types/json-schema": "^7.0.11",
|
||||
"ajv": "^8.17.1",
|
||||
"ajv": "^8.18.0",
|
||||
"ajv-errors": "~3.0.0",
|
||||
"ajv-formats": "~2.1.1",
|
||||
"es-aggregate-error": "^1.0.7",
|
||||
"expr-eval-fork": "^3.0.1",
|
||||
"jsonpath-plus": "^10.3.0",
|
||||
"lodash": "~4.17.23",
|
||||
"lodash": "^4.18.1",
|
||||
"lodash.topath": "^4.5.2",
|
||||
"minimatch": "3.1.2",
|
||||
"minimatch": "^3.1.4",
|
||||
"nimma": "0.2.3",
|
||||
"pony-cause": "^1.1.1",
|
||||
"simple-eval": "1.0.1",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -9095,25 +9136,19 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@stoplight/spectral-core/node_modules/brace-expansion": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
|
||||
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
|
||||
"integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@stoplight/spectral-core/node_modules/lodash": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@stoplight/spectral-core/node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
@@ -9150,9 +9185,9 @@
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@stoplight/spectral-functions": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@stoplight/spectral-functions/-/spectral-functions-1.10.1.tgz",
|
||||
"integrity": "sha512-obu8ZfoHxELOapfGsCJixKZXZcffjg+lSoNuttpmUFuDzVLT3VmH8QkPXfOGOL5Pz80BR35ClNAToDkdnYIURg==",
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@stoplight/spectral-functions/-/spectral-functions-1.10.2.tgz",
|
||||
"integrity": "sha512-PIfPUgTRo8EtAnL1MIrzhHoUuojSaE8shGSMaHS3BxGyc8d079BE5+TqJa1/WLUb9YT9JQnZ0Aj4xfi8NcJOIw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@stoplight/better-ajv-errors": "1.0.3",
|
||||
@@ -9160,11 +9195,11 @@
|
||||
"@stoplight/spectral-core": "^1.19.4",
|
||||
"@stoplight/spectral-formats": "^1.8.1",
|
||||
"@stoplight/spectral-runtime": "^1.1.2",
|
||||
"ajv": "^8.17.1",
|
||||
"ajv": "^8.18.0",
|
||||
"ajv-draft-04": "~1.0.0",
|
||||
"ajv-errors": "~3.0.0",
|
||||
"ajv-formats": "~2.1.1",
|
||||
"lodash": "~4.17.21",
|
||||
"lodash": "^4.18.1",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -9188,12 +9223,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@stoplight/spectral-functions/node_modules/lodash": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@stoplight/spectral-functions/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
@@ -9257,9 +9286,9 @@
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@stoplight/spectral-ruleset-bundler": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@stoplight/spectral-ruleset-bundler/-/spectral-ruleset-bundler-1.6.3.tgz",
|
||||
"integrity": "sha512-AQFRO6OCKg8SZJUupnr3+OzI1LrMieDTEUHsYgmaRpNiDRPvzImE3bzM1KyQg99q58kTQyZ8kpr7sG8Lp94RRA==",
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@stoplight/spectral-ruleset-bundler/-/spectral-ruleset-bundler-1.7.0.tgz",
|
||||
"integrity": "sha512-PpIdj5Wje0T7ktxY8EUzBWLU0+mGGQHznT8nlQxTMnRhWLNYsm6HvSZDXLtMi+86yqvTuf7loJy6JvLBDzHGAA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@rollup/plugin-commonjs": "~22.0.2",
|
||||
@@ -9275,7 +9304,7 @@
|
||||
"@stoplight/types": "^13.6.0",
|
||||
"@types/node": "*",
|
||||
"pony-cause": "1.1.1",
|
||||
"rollup": "~2.79.2",
|
||||
"rollup": "~2.80.0",
|
||||
"tslib": "^2.8.1",
|
||||
"validate-npm-package-name": "3.0.0"
|
||||
},
|
||||
@@ -9342,9 +9371,9 @@
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@stoplight/spectral-rulesets": {
|
||||
"version": "1.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@stoplight/spectral-rulesets/-/spectral-rulesets-1.22.0.tgz",
|
||||
"integrity": "sha512-l2EY2jiKKLsvnPfGy+pXC0LeGsbJzcQP5G/AojHgf+cwN//VYxW1Wvv4WKFx/CLmLxc42mJYF2juwWofjWYNIQ==",
|
||||
"version": "1.22.1",
|
||||
"resolved": "https://registry.npmjs.org/@stoplight/spectral-rulesets/-/spectral-rulesets-1.22.1.tgz",
|
||||
"integrity": "sha512-DaaQJioKuYkRsOuKIJfX2ek7G7f6OCU3CI3K7ABaOcTFMiHj29SJLDdb04mCjXZFXMlXHjmCl2ZpKW6heieXpw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@asyncapi/specs": "^6.8.0",
|
||||
@@ -9356,11 +9385,11 @@
|
||||
"@stoplight/spectral-runtime": "^1.1.2",
|
||||
"@stoplight/types": "^13.6.0",
|
||||
"@types/json-schema": "^7.0.7",
|
||||
"ajv": "^8.17.1",
|
||||
"ajv": "^8.18.0",
|
||||
"ajv-formats": "~2.1.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"leven": "3.1.0",
|
||||
"lodash": "~4.17.21",
|
||||
"lodash": "^4.18.1",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -9384,12 +9413,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@stoplight/spectral-rulesets/node_modules/lodash": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@stoplight/spectral-rulesets/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
@@ -11419,6 +11442,18 @@
|
||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-walk": {
|
||||
"version": "8.3.5",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
|
||||
"integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"acorn": "^8.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||
@@ -14618,9 +14653,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz",
|
||||
"integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==",
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.1.tgz",
|
||||
"integrity": "sha512-JahakDAIg1gyOm7dlgWSDjV4n7Ip2PKR55NIT6jrMfIgLFgWo81vdr1/QGqWtFNRqXP9UV71oVePtjqS2ebnPw==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
@@ -16296,6 +16331,15 @@
|
||||
"integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/expr-eval-fork": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/expr-eval-fork/-/expr-eval-fork-3.0.3.tgz",
|
||||
"integrity": "sha512-BhC+hbc5lIVjygr840n5DEkW3MQq7H9o+mc1/N7Z5uIiCFVyESLL5DIE7LNq4CYUNxy+XjA+3jRrL/h0Kt2xcg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
||||
@@ -16651,6 +16695,12 @@
|
||||
"node": "^12.20 || >= 14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
||||
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/file-entry-cache": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
|
||||
@@ -17316,6 +17366,24 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/git-http-backend": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmmirror.com/git-http-backend/-/git-http-backend-1.1.2.tgz",
|
||||
"integrity": "sha512-Gx7n/kyCEXGFZlCGmbsEsyeyabLs8XWeb+E/6842up7p3PktQS2/8rlNfB6hCagnW0pJ13Tn8E3yhOkKS6ihdg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"git-side-band-message": "~0.0.3",
|
||||
"inherits": "~2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/git-side-band-message": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/git-side-band-message/-/git-side-band-message-0.0.3.tgz",
|
||||
"integrity": "sha512-4Rq4xm1+zqCkmuHxRbGdA5ActF7F4UfgK8uI0B7ZfSkByZfikRuF7mqHlvqmycvqos7jpXNkgsZK7DThLLHG3w==",
|
||||
"dev": true,
|
||||
"license": "BSD"
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
@@ -19581,12 +19649,6 @@
|
||||
"integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jshint/node_modules/lodash": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jshint/node_modules/minimatch": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz",
|
||||
@@ -22865,9 +22927,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/openai": {
|
||||
"version": "6.33.0",
|
||||
"resolved": "https://registry.npmjs.org/openai/-/openai-6.33.0.tgz",
|
||||
"integrity": "sha512-xAYN1W3YsDXJWA5F277135YfkEk6H7D3D6vWwRhJ3OEkzRgcyK8z/P5P9Gyi/wB4N8kK9kM5ZjprfvyHagKmpw==",
|
||||
"version": "6.34.0",
|
||||
"resolved": "https://registry.npmjs.org/openai/-/openai-6.34.0.tgz",
|
||||
"integrity": "sha512-yEr2jdGf4tVFYG6ohmr3pF6VJuveP0EA/sS8TBx+4Eq5NT10alu5zg2dmxMXMgqpihRDQlFGpRt2XwsGj+Fyxw==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
@@ -23444,13 +23506,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.55.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.1.tgz",
|
||||
"integrity": "sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==",
|
||||
"version": "1.59.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz",
|
||||
"integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.55.1"
|
||||
"playwright-core": "1.59.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -23463,9 +23525,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.55.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.1.tgz",
|
||||
"integrity": "sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==",
|
||||
"version": "1.59.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz",
|
||||
"integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -23927,22 +23989,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/protobufjs": {
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz",
|
||||
"integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
|
||||
"version": "7.5.6",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.6.tgz",
|
||||
"integrity": "sha512-M71sTMB146U3u0di3yup8iM+zv8yPRNQVr1KK4tyBitl3qFvEGucq/rGDRShD2rsJhtN02RJaJ7j5X5hmy8SJg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.2",
|
||||
"@protobufjs/base64": "^1.1.2",
|
||||
"@protobufjs/codegen": "^2.0.4",
|
||||
"@protobufjs/codegen": "^2.0.5",
|
||||
"@protobufjs/eventemitter": "^1.1.0",
|
||||
"@protobufjs/fetch": "^1.1.0",
|
||||
"@protobufjs/float": "^1.0.2",
|
||||
"@protobufjs/inquire": "^1.1.0",
|
||||
"@protobufjs/inquire": "^1.1.1",
|
||||
"@protobufjs/path": "^1.1.2",
|
||||
"@protobufjs/pool": "^1.1.0",
|
||||
"@protobufjs/utf8": "^1.1.0",
|
||||
"@protobufjs/utf8": "^1.1.1",
|
||||
"@types/node": ">=13.7.0",
|
||||
"long": "^5.0.0"
|
||||
},
|
||||
@@ -24871,9 +24933,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "2.79.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz",
|
||||
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
|
||||
"version": "2.80.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz",
|
||||
"integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
@@ -25581,18 +25643,6 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/simple-eval": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-eval/-/simple-eval-1.0.1.tgz",
|
||||
"integrity": "sha512-LH7FpTAkeD+y5xQC4fzS+tFtaNlvt3Ib1zKzvhjv/Y+cioV4zIuw4IZr2yhRLu67CWL7FR9/6KXKnjRoZTvGGQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jsep": "^1.3.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-get": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
||||
@@ -27422,9 +27472,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz",
|
||||
"integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==",
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz",
|
||||
"integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.18.1"
|
||||
@@ -29122,7 +29172,7 @@
|
||||
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||
"@fortawesome/react-fontawesome": "^3.0.2",
|
||||
"@getinsomnia/node-libcurl": "3.2.1",
|
||||
"@getinsomnia/node-libcurl": "3.2.2",
|
||||
"@getinsomnia/srp-js": "1.0.0-alpha.1",
|
||||
"@grpc/grpc-js": "^1.13.3",
|
||||
"@grpc/proto-loader": "^0.7.13",
|
||||
@@ -29135,13 +29185,15 @@
|
||||
"@seald-io/nedb": "^4.1.1",
|
||||
"@segment/analytics-node": "2.2.1",
|
||||
"@sentry/electron": "^6.5.0",
|
||||
"@stoplight/spectral-core": "^1.20.0",
|
||||
"@stoplight/spectral-core": "^1.22.0",
|
||||
"@stoplight/spectral-formats": "^1.8.2",
|
||||
"@stoplight/spectral-ruleset-bundler": "1.6.3",
|
||||
"@stoplight/spectral-rulesets": "^1.22.0",
|
||||
"@stoplight/spectral-ruleset-bundler": "1.7.0",
|
||||
"@stoplight/spectral-rulesets": "^1.22.1",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tanstack/react-virtual": "3.13.12",
|
||||
"@xmldom/xmldom": "^0.9.8",
|
||||
"acorn": "^8.16.0",
|
||||
"acorn-walk": "^8.3.5",
|
||||
"ajv": "^8.17.1",
|
||||
"apiconnect-wsdl": "2.0.36",
|
||||
"aws4": "^1.13.2",
|
||||
@@ -29161,10 +29213,11 @@
|
||||
"decompress": "^4.2.1",
|
||||
"deep-equal": "2.2.3",
|
||||
"diff-match-patch-ts": "^0.6.0",
|
||||
"dompurify": "^3.2.5",
|
||||
"dompurify": "^3.4.1",
|
||||
"electron-context-menu": "^3.6.1",
|
||||
"electron-updater": "^6.6.2",
|
||||
"fastq": "^1.19.1",
|
||||
"fflate": "^0.8.2",
|
||||
"fuzzysort": "^1.9.0",
|
||||
"graphql": "^16.10.0",
|
||||
"graphql-ws": "^5.16.2",
|
||||
@@ -29213,7 +29266,7 @@
|
||||
"tinykeys": "^3.0.0",
|
||||
"tough-cookie": "^4.1.4",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"undici": "^7.16.0",
|
||||
"undici": "^7.25.0",
|
||||
"uuid": "^9.0.1",
|
||||
"vkbeautify": "^0.99.3",
|
||||
"ws": "^8.18.1",
|
||||
@@ -29279,13 +29332,16 @@
|
||||
"vite": "^7.1.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@kong/insomnia-plugin-ai": "^1.0.9",
|
||||
"@kong/insomnia-plugin-ai": "^1.0.11",
|
||||
"@kong/insomnia-plugin-external-vault": "0.1.4-dev.20251224090833"
|
||||
}
|
||||
},
|
||||
"packages/insomnia-api": {
|
||||
"version": "12.5.1-alpha.0",
|
||||
"license": "Apache-2.0"
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@getinsomnia/insomnia-v3-fetch": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"packages/insomnia-inso": {
|
||||
"version": "12.5.1-alpha.0",
|
||||
@@ -29293,10 +29349,10 @@
|
||||
"dependencies": {
|
||||
"@seald-io/nedb": "^4.1.1",
|
||||
"@segment/analytics-node": "^2.2.1",
|
||||
"@stoplight/spectral-core": "^1.20.0",
|
||||
"@stoplight/spectral-core": "^1.22.0",
|
||||
"@stoplight/spectral-formats": "^1.8.2",
|
||||
"@stoplight/spectral-ruleset-bundler": "1.6.3",
|
||||
"@stoplight/spectral-rulesets": "^1.22.0",
|
||||
"@stoplight/spectral-ruleset-bundler": "1.7.0",
|
||||
"@stoplight/spectral-rulesets": "^1.22.1",
|
||||
"@stoplight/types": "^14.1.1",
|
||||
"commander": "^12.1.0",
|
||||
"consola": "^2.15.3",
|
||||
@@ -29360,7 +29416,7 @@
|
||||
"version": "12.5.1-alpha.0",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.55.1",
|
||||
"@playwright/test": "1.59.1",
|
||||
"@ravanallc/grpc-server-reflection": "^0.1.6",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/oidc-provider": "^8.8.1",
|
||||
@@ -29370,6 +29426,7 @@
|
||||
"esbuild-runner": "2.2.2",
|
||||
"express": "^4.21.2",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"git-http-backend": "^1.1.2",
|
||||
"graphql": "^16.10.0",
|
||||
"graphql-http": "^1.22.4",
|
||||
"http-errors": "^2.0.0",
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
"install-libcurl-node": "node-pre-gyp install --directory node_modules/@getinsomnia/node-libcurl --update-binary --runtime=node --target=24.14.0",
|
||||
"inso-start": "npm start -w insomnia-inso",
|
||||
"inso-package": "npm run build -w insomnia-inso && npm run package -w insomnia-inso",
|
||||
"watch:app": "cross-env PLAYWRIGHT=1 npm run build:electron-entrypoints -w insomnia && npm run start:dev-server -w insomnia",
|
||||
"app-build": "cross-env PLAYWRIGHT=1 npm run build -w insomnia",
|
||||
"watch:app": "npm run build:electron-entrypoints -w insomnia && npm run start:dev-server -w insomnia",
|
||||
"app-build": "npm run build -w insomnia",
|
||||
"app-package": "npm run package -w insomnia",
|
||||
"test:smoke:dev": "npm run test:dev -w insomnia-smoke-test -- --project=Smoke",
|
||||
"test:smoke:build": "npm run test:build -w insomnia-smoke-test -- --project=Smoke",
|
||||
@@ -76,10 +76,13 @@
|
||||
"overrides": {
|
||||
"ajv-draft-04": {
|
||||
"ajv": "$ajv"
|
||||
},
|
||||
"jshint": {
|
||||
"lodash": "4.18.1"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@getinsomnia/node-libcurl": "3.2.1",
|
||||
"@getinsomnia/node-libcurl": "3.2.2",
|
||||
"ajv": "^8.17.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,5 +11,5 @@ Uses npm workspace, so no need to install.
|
||||
### Import
|
||||
|
||||
```ts
|
||||
import { getUserProfile, type UserProfileResponse } from 'insomnia-api';
|
||||
import { getUserProfile, getEncryptionKeys, type User, type UserEncryptionKeys } from 'insomnia-api';
|
||||
```
|
||||
|
||||
@@ -22,7 +22,10 @@
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint . --ext .ts,.tsx --cache",
|
||||
"type-check": "tsc --noEmit --project tsconfig.json"
|
||||
"type-check": "tsc --noEmit --project tsconfig.json",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {}
|
||||
"dependencies": {
|
||||
"@getinsomnia/insomnia-v3-fetch": "^1.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
115
packages/insomnia-api/src/__tests__/user.test.ts
Normal file
115
packages/insomnia-api/src/__tests__/user.test.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { getEncryptionKeys, getUserProfile } from '../user';
|
||||
|
||||
const { mockFetch } = vi.hoisted(() => ({
|
||||
mockFetch: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../fetch', () => ({
|
||||
fetch: mockFetch,
|
||||
}));
|
||||
|
||||
describe('getUserProfile', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('returns first_name and last_name from the API response', async () => {
|
||||
mockFetch.mockResolvedValue({
|
||||
id: 'usr_abc123',
|
||||
email: 'jane@example.com',
|
||||
first_name: 'Jane',
|
||||
last_name: 'Doe',
|
||||
picture: 'https://example.com/pic.jpg',
|
||||
});
|
||||
|
||||
const result = await getUserProfile({ sessionId: 'sess_xyz' });
|
||||
|
||||
expect(result.first_name).toBe('Jane');
|
||||
expect(result.last_name).toBe('Doe');
|
||||
});
|
||||
|
||||
it('passes id through as-is', async () => {
|
||||
mockFetch.mockResolvedValue({
|
||||
id: 'usr_abc123',
|
||||
email: 'jane@example.com',
|
||||
first_name: 'Jane',
|
||||
last_name: 'Doe',
|
||||
picture: '',
|
||||
});
|
||||
|
||||
const result = await getUserProfile({ sessionId: 'sess_xyz' });
|
||||
|
||||
expect(result.id).toBe('usr_abc123');
|
||||
});
|
||||
|
||||
it('passes picture through as-is', async () => {
|
||||
mockFetch.mockResolvedValue({
|
||||
id: 'usr_abc123',
|
||||
email: 'jane@example.com',
|
||||
first_name: 'Jane',
|
||||
last_name: 'Doe',
|
||||
picture: 'https://example.com/pic.jpg',
|
||||
});
|
||||
|
||||
const result = await getUserProfile({ sessionId: 'sess_xyz' });
|
||||
|
||||
expect(result.picture).toBe('https://example.com/pic.jpg');
|
||||
});
|
||||
|
||||
it('calls fetch with the correct path and sessionId', async () => {
|
||||
mockFetch.mockResolvedValue({
|
||||
id: 'usr_abc123',
|
||||
email: 'jane@example.com',
|
||||
first_name: 'Jane',
|
||||
last_name: 'Doe',
|
||||
picture: '',
|
||||
});
|
||||
|
||||
await getUserProfile({ sessionId: 'sess_xyz' });
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith({ method: 'GET', path: '/v3/users/me', sessionId: 'sess_xyz' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEncryptionKeys', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('passes encryption key fields through as-is', async () => {
|
||||
mockFetch.mockResolvedValue({
|
||||
public_key: '{"kty":"RSA"}',
|
||||
enc_private_key: '{"iv":"abc"}',
|
||||
enc_symmetric_key: '{"iv":"def"}',
|
||||
salt_enc: 'deadbeef',
|
||||
enc_driver_key: null,
|
||||
});
|
||||
|
||||
const result = await getEncryptionKeys({ sessionId: 'sess_xyz' });
|
||||
|
||||
expect(result.public_key).toBe('{"kty":"RSA"}');
|
||||
expect(result.enc_private_key).toBe('{"iv":"abc"}');
|
||||
expect(result.enc_symmetric_key).toBe('{"iv":"def"}');
|
||||
expect(result.salt_enc).toBe('deadbeef');
|
||||
});
|
||||
|
||||
it('calls fetch with the correct path and sessionId', async () => {
|
||||
mockFetch.mockResolvedValue({
|
||||
public_key: '',
|
||||
enc_private_key: '',
|
||||
enc_symmetric_key: '',
|
||||
salt_enc: '',
|
||||
enc_driver_key: '',
|
||||
});
|
||||
|
||||
await getEncryptionKeys({ sessionId: 'sess_xyz' });
|
||||
|
||||
expect(mockFetch).toHaveBeenCalledWith({
|
||||
method: 'GET',
|
||||
path: '/v3/users/me/encryption-keys',
|
||||
sessionId: 'sess_xyz',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,9 @@
|
||||
import type { User, UserEncryptionKeys } from '@getinsomnia/insomnia-v3-fetch';
|
||||
|
||||
import { fetch } from './fetch';
|
||||
|
||||
export type { User, UserEncryptionKeys };
|
||||
|
||||
// POST /auth/logout
|
||||
export const logout = ({ sessionId }: { sessionId: string }) => {
|
||||
return fetch({
|
||||
@@ -9,66 +13,14 @@ export const logout = ({ sessionId }: { sessionId: string }) => {
|
||||
});
|
||||
};
|
||||
|
||||
// GET /auth/whoami
|
||||
interface WhoamiResponse {
|
||||
sessionAge: number;
|
||||
sessionExpiry: number;
|
||||
accountId: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
created: number;
|
||||
publicKey: string;
|
||||
encSymmetricKey: string;
|
||||
encPrivateKey: string;
|
||||
saltEnc: string;
|
||||
isPaymentRequired: boolean;
|
||||
isTrialing: boolean;
|
||||
isVerified: boolean;
|
||||
isAdmin: boolean;
|
||||
trialEnd: string;
|
||||
planName: string;
|
||||
planId: string;
|
||||
canManageTeams: boolean;
|
||||
maxTeamMembers: number;
|
||||
}
|
||||
|
||||
export const whoami = async ({ sessionId }: { sessionId: string }): Promise<WhoamiResponse> => {
|
||||
const response = await fetch<WhoamiResponse>({
|
||||
method: 'GET',
|
||||
path: '/auth/whoami',
|
||||
sessionId,
|
||||
});
|
||||
if (typeof response === 'string') {
|
||||
throw new TypeError('Unexpected plaintext response: ' + response);
|
||||
}
|
||||
if (response && !response?.encSymmetricKey) {
|
||||
throw new Error('Unexpected response: ' + JSON.stringify(response));
|
||||
}
|
||||
return response;
|
||||
// GET /v3/users/me
|
||||
export const getUserProfile = async ({ sessionId }: { sessionId: string }): Promise<User> => {
|
||||
return await fetch<User>({ method: 'GET', path: '/v3/users/me', sessionId });
|
||||
};
|
||||
|
||||
// GET /v1/user/profile
|
||||
export interface UserProfile {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
picture: string;
|
||||
bio: string;
|
||||
github: string;
|
||||
linkedin: string;
|
||||
twitter: string;
|
||||
identities: any;
|
||||
given_name: string;
|
||||
family_name: string;
|
||||
}
|
||||
|
||||
export const getUserProfile = async ({ sessionId }: { sessionId: string }) => {
|
||||
return fetch<UserProfile>({
|
||||
method: 'GET',
|
||||
path: '/v1/user/profile',
|
||||
sessionId,
|
||||
});
|
||||
// GET /v3/users/me/encryption-keys
|
||||
export const getEncryptionKeys = async ({ sessionId }: { sessionId: string }): Promise<UserEncryptionKeys> => {
|
||||
return fetch<UserEncryptionKeys>({ method: 'GET', path: '/v3/users/me/encryption-keys', sessionId });
|
||||
};
|
||||
|
||||
// GET /v1/billing/current-plan
|
||||
|
||||
7
packages/insomnia-api/vitest.config.ts
Normal file
7
packages/insomnia-api/vitest.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
environment: 'node',
|
||||
},
|
||||
});
|
||||
@@ -53,10 +53,10 @@
|
||||
"dependencies": {
|
||||
"@segment/analytics-node": "^2.2.1",
|
||||
"@seald-io/nedb": "^4.1.1",
|
||||
"@stoplight/spectral-core": "^1.20.0",
|
||||
"@stoplight/spectral-core": "^1.22.0",
|
||||
"@stoplight/spectral-formats": "^1.8.2",
|
||||
"@stoplight/spectral-ruleset-bundler": "1.6.3",
|
||||
"@stoplight/spectral-rulesets": "^1.22.0",
|
||||
"@stoplight/spectral-ruleset-bundler": "1.7.0",
|
||||
"@stoplight/spectral-rulesets": "^1.22.1",
|
||||
"@stoplight/types": "^14.1.1",
|
||||
"commander": "^12.1.0",
|
||||
"consola": "^2.15.3",
|
||||
|
||||
@@ -42,6 +42,8 @@ const shouldReturnSuccessCode = [
|
||||
'$PWD/packages/insomnia-inso/bin/inso run test -w packages/insomnia-inso/src/examples/folder-inheritance-document.yml spc_a8144e --verbose --disableCertValidation',
|
||||
|
||||
// run collection
|
||||
// with auth
|
||||
'$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-smoke-test/fixtures/auth-types.yaml wrk_ca4cb9',
|
||||
// export file
|
||||
'$PWD/packages/insomnia-inso/bin/inso run collection -w packages/insomnia-smoke-test/fixtures/simple.yaml -e production wrk_dc393c',
|
||||
// with regex filter
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { OAuth1SignatureMethod } from 'insomnia/src/network/o-auth-1/constants';
|
||||
import type { OAuth1SignatureMethod } from 'insomnia/src/common/constants';
|
||||
|
||||
import type { OAuth2ResponseType, RequestAuthentication } from '~/insomnia-data';
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Ajv, type ErrorObject } from 'ajv';
|
||||
import * as chai from 'chai';
|
||||
import { RESPONSE_CODE_REASONS } from 'insomnia/src/common/constants';
|
||||
import { readCurlResponse } from 'insomnia/src/models/helpers/response-operations';
|
||||
import type { sendCurlAndWriteTimelineError, sendCurlAndWriteTimelineResponse } from 'insomnia/src/network/network';
|
||||
|
||||
import { services } from '~/insomnia-data';
|
||||
|
||||
import { Cookie, type CookieOptions } from './cookies';
|
||||
import { CookieList } from './cookies';
|
||||
import { Header, type HeaderDefinition, HeaderList } from './headers';
|
||||
@@ -394,8 +395,7 @@ export async function readBodyFromPath(
|
||||
} else if (!response.bodyPath) {
|
||||
return '';
|
||||
}
|
||||
const nodejsReadCurlResponse = process.type === 'renderer' ? window.bridge.readCurlResponse : readCurlResponse;
|
||||
const readResponseResult = await nodejsReadCurlResponse({
|
||||
const readResponseResult = await services.helpers.readCurlResponse({
|
||||
bodyPath: response.bodyPath,
|
||||
bodyCompression: response.bodyCompression,
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { CurlRequestOutput } from 'insomnia/src/main/network/libcurl-promise';
|
||||
import { readCurlResponse } from 'insomnia/src/models/helpers/response-operations';
|
||||
import { Cookie } from 'tough-cookie';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import type { Settings } from '~/insomnia-data';
|
||||
import { services } from '~/insomnia-data';
|
||||
|
||||
import { RequestAuth } from './auth';
|
||||
import { fromPreRequestAuth } from './auth';
|
||||
@@ -264,8 +264,7 @@ async function curlOutputToResponse(
|
||||
originalRequest,
|
||||
});
|
||||
}
|
||||
const nodejsReadCurlResponse = process.type === 'renderer' ? window.bridge.readCurlResponse : readCurlResponse;
|
||||
const bodyResult = await nodejsReadCurlResponse({
|
||||
const bodyResult = await services.helpers.readCurlResponse({
|
||||
bodyPath: result.responseBodyPath,
|
||||
bodyCompression: result.patch.bodyCompression,
|
||||
});
|
||||
|
||||
@@ -1,144 +1,98 @@
|
||||
# Insomnia Smoke Tests
|
||||
|
||||
[](https://github.com/microsoft/playwright)
|
||||
Playwright E2E tests for the Insomnia desktop app.
|
||||
|
||||
This project contains the smoke testing suite for Insomnia App.
|
||||
> CLI smoke tests: [CLI.md](CLI.md)
|
||||
|
||||
> To find more about Inso CLI smoke tests, check [this document](CLI.md).
|
||||
## Test structure
|
||||
|
||||
- [Insomnia Smoke Tests](#insomnia-smoke-tests)
|
||||
- [Quick-start](#quick-start)
|
||||
- [Debugging and Developing Tests locally](#debugging-and-developing-tests-locally)
|
||||
- [Playwright VS Code extension](#playwright-vs-code-extension)
|
||||
- [Playwright Inspector](#playwright-inspector)
|
||||
- [Playwright Trace viewer](#playwright-trace-viewer)
|
||||
- [Additional Log levels](#additional-log-levels)
|
||||
- [Reproducing CI Failures](#reproducing-ci-failures)
|
||||
- [Getting traces from CI](#getting-traces-from-ci)
|
||||
- [Build and package methods](#build-and-package-methods)
|
||||
- [Non recurring tests](#non-recurring-tests)
|
||||
- [Refresh certs](#refresh-certs)
|
||||
```
|
||||
tests/
|
||||
smoke/ # Main suite — runs on every CI push (Ubuntu only)
|
||||
critical/ # Single critical-path test — runs on release
|
||||
migration/ # Data migration tests
|
||||
```
|
||||
|
||||
All commands below must be run from the **repo root**.
|
||||
|
||||
## Quick-start
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Clone the project
|
||||
- Run `npm install`
|
||||
|
||||
To run all tests:
|
||||
|
||||
- In one terminal run: `npm run watch:app` OR `npm run dev`
|
||||
- In another terminal run: `npm run test:smoke:dev`
|
||||
|
||||
To run single tests:
|
||||
|
||||
- Filter by the file or test title, e.g. `npm run test:smoke:dev -- oauth`
|
||||
- Use playwright UI `npm run test:dev -w insomnia-smoke-test -- --project=Smoke --ui` to show all tests by project
|
||||
|
||||
## Debugging and Developing Tests locally
|
||||
|
||||
### Playwright VS Code extension
|
||||
|
||||
In order to run/debug tests directly from VS Code:
|
||||
|
||||
- Install the [Playwright extension](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright).
|
||||
- With the extension installed, run on terminal `npm run watch:app`.
|
||||
|
||||
You can trigger tests from the `Testing` tab, or within the test files clicking the run button.
|
||||
|
||||

|
||||
|
||||
If no tests appear, you may need to run "Refresh playwright tests". This can be done from the command palette, or by using the button at the top of the `Testing` tab.
|
||||
|
||||

|
||||
|
||||
### Playwright Inspector
|
||||
|
||||
You can step through tests with playwright inspector: `PWDEBUG=1 npm run test:smoke:dev`
|
||||
|
||||
This is also useful to help create new tests.
|
||||
|
||||

|
||||
|
||||
### Playwright Trace viewer
|
||||
|
||||
We generate [Playwright Traces](https://playwright.dev/docs/trace-viewer) when tests run. These can be used to debug local and CI test failures.
|
||||
|
||||

|
||||
|
||||
To open a local trace viewer for a given test output, run:
|
||||
|
||||
```shell
|
||||
# Example:
|
||||
npx playwright show-trace packages/insomnia-smoke-test/traces/app-can-send-requests/trace.zip
|
||||
```bash
|
||||
npm install
|
||||
npm run test:smoke:dev # run all Smoke tests (dev mode)
|
||||
```
|
||||
|
||||
Alternatively you can upload this trace to [trace.playwright.dev](https://trace.playwright.dev/).
|
||||
Both the echo server (port 4010) and the Vite dev server start automatically.
|
||||
|
||||
### Additional Log levels
|
||||
**Filter to one test** — pass a substring of the test title or file name:
|
||||
|
||||
You can enable additional logging to help you debug tests:
|
||||
```bash
|
||||
npm run test:smoke:dev -- oauth
|
||||
```
|
||||
|
||||
- Playwright logs: `DEBUG=pw:api npm run test:smoke:dev`
|
||||
- Insomnia console logs: `DEBUG=pw:browser npm run test:smoke:dev`
|
||||
- WebServer console logs: `DEBUG=pw:WebServer npm run test:smoke:dev`
|
||||
**Interactive UI:**
|
||||
|
||||
## Reproducing CI Failures
|
||||
```bash
|
||||
npm run test:smoke:dev -- --ui
|
||||
```
|
||||
|
||||
### Getting traces from CI
|
||||
**Step-through debugger:**
|
||||
|
||||
Traces from CI execution can be found in the failed CI job's artifacts.
|
||||
```bash
|
||||
PWDEBUG=1 npm run test:smoke:dev
|
||||
```
|
||||
|
||||

|
||||
## Additional log levels
|
||||
|
||||
After downloading the artifacts, these can be extracted and loaded up into the [Trace viewer](#playwright-trace-viewer).
|
||||
```bash
|
||||
DEBUG=pw:api npm run test:smoke:dev # Playwright API logs
|
||||
DEBUG=pw:browser npm run test:smoke:dev # Insomnia console logs
|
||||
DEBUG=pw:WebServer npm run test:smoke:dev # Web server logs
|
||||
```
|
||||
|
||||
### Build and package methods
|
||||
## Traces and error context
|
||||
|
||||
It's possible to run the smoke tests for:
|
||||
On failure, two artifacts are written under `packages/insomnia-smoke-test/traces/<test-name>/`:
|
||||
|
||||
- A `build`, the JS bundle that is loaded into an electron client
|
||||
- A `package`, the executable binary (e.g. `.dmg` or `.exe`)
|
||||
- **`error-context.md`** — error details, ARIA page snapshot at point of failure, and annotated test source. Read this first.
|
||||
- **`trace.zip`** — full Playwright trace (network, screenshots, DOM snapshots).
|
||||
|
||||
For `build`:
|
||||
Open a trace:
|
||||
|
||||
```shell
|
||||
# Transpile js bundle
|
||||
```bash
|
||||
npx playwright show-trace packages/insomnia-smoke-test/traces/<test-name>/trace.zip
|
||||
```
|
||||
|
||||
Or upload to [trace.playwright.dev](https://trace.playwright.dev/).
|
||||
|
||||
CI traces are available as artifacts on failed workflow runs.
|
||||
|
||||
## Build / package modes
|
||||
|
||||
Run against a JS bundle (`build`) or a packaged binary (`package`) instead of the dev watcher:
|
||||
|
||||
```bash
|
||||
# build mode
|
||||
npm run app-build
|
||||
|
||||
# Run tests
|
||||
npm run test:smoke:build
|
||||
```
|
||||
|
||||
For `package`:
|
||||
|
||||
```shell
|
||||
# Build executable in /packages/insomnia/dist
|
||||
# package mode
|
||||
npm run app-package
|
||||
|
||||
# Run tests
|
||||
npm run test:smoke:package
|
||||
```
|
||||
|
||||
> Note: for local testing of the packaged app on macOS you need to change the entitlements temporarily to allow unsigned apps to run. You can do this by changing the `com.apple.security.cs.disable-library-validation` key in `entitlements.mac.inherit.plist` file to `true`. (Remember do not commit to the origin repo!)
|
||||
> macOS package mode: set `com.apple.security.cs.disable-library-validation` to `true` in `entitlements.mac.inherit.plist` to allow unsigned local binaries. Do not commit this change.
|
||||
|
||||
Each of the above commands will automatically run the Express server, so you do not need to take any extra steps.
|
||||
## Non-CI / pre-release tests
|
||||
|
||||
### Non recurring tests
|
||||
|
||||
Non recurring / non-CI tests, like pre-release ones, can be run using [Playwright VS Code extension](#playwright-vs-code-extension) or by running `test:dev` against the desired test file:
|
||||
|
||||
```shell
|
||||
npm run test:dev -w packages/insomnia-smoke-test -- preferences-interactions
|
||||
```bash
|
||||
npm run test:dev -w insomnia-smoke-test -- tests/smoke/preferences-interactions.test.ts
|
||||
```
|
||||
|
||||
### Refresh certs
|
||||
## Cert refresh
|
||||
|
||||
The certs might need to be replaced after 2026 to fix the custom ca cert test
|
||||
If the custom CA cert test fails after 2026:
|
||||
|
||||
```sh
|
||||
mkcert -install
|
||||
mkcert localhost
|
||||
mkcert -CAROOT
|
||||
```bash
|
||||
mkcert -install && mkcert localhost && mkcert -CAROOT
|
||||
```
|
||||
|
||||
95
packages/insomnia-smoke-test/fixtures/auth-types.yaml
Normal file
95
packages/insomnia-smoke-test/fixtures/auth-types.yaml
Normal file
@@ -0,0 +1,95 @@
|
||||
type: collection.insomnia.rest/5.0
|
||||
schema_version: "5.1"
|
||||
name: Auth tests
|
||||
meta:
|
||||
id: wrk_ca4cb9634c1045479b67b94f61725442
|
||||
created: 1776427934242
|
||||
modified: 1776427995078
|
||||
description: ""
|
||||
collection:
|
||||
- url: http://127.0.0.1:4010/auth/basic
|
||||
name: sends request with basic authentication
|
||||
meta:
|
||||
id: req_c29164bea31840a5a68eb67858759705
|
||||
created: 1636141100570
|
||||
modified: 1636142586648
|
||||
isPrivate: false
|
||||
description: ""
|
||||
sortKey: 0
|
||||
method: GET
|
||||
headers:
|
||||
- name: Authorization
|
||||
value: Basic dXNlcjpwYXNz
|
||||
disabled: true
|
||||
authentication:
|
||||
type: basic
|
||||
useISO88591: false
|
||||
username: user
|
||||
password: pass
|
||||
disabled: false
|
||||
settings:
|
||||
renderRequestBody: true
|
||||
encodeUrl: true
|
||||
followRedirects: global
|
||||
cookies:
|
||||
send: true
|
||||
store: true
|
||||
rebuildPath: true
|
||||
- url: http://127.0.0.1:4010/auth/oauth1
|
||||
name: sends request with oauth1
|
||||
meta:
|
||||
id: req_e713e4eeac1942a089e6c687b66c38ac
|
||||
created: 1776427942368
|
||||
modified: 1776427981837
|
||||
isPrivate: false
|
||||
description: ""
|
||||
sortKey: -1776427942368
|
||||
method: GET
|
||||
headers:
|
||||
- name: User-Agent
|
||||
value: insomnia/12.5.1-alpha.0
|
||||
description: ""
|
||||
disabled: false
|
||||
authentication:
|
||||
type: oauth1
|
||||
disabled: false
|
||||
signatureMethod: HMAC-SHA1
|
||||
consumerKey: key
|
||||
tokenKey: key
|
||||
tokenSecret: secret
|
||||
privateKey: ""
|
||||
version: "1.0"
|
||||
nonce: ""
|
||||
timestamp: ""
|
||||
callback: ""
|
||||
settings:
|
||||
renderRequestBody: true
|
||||
encodeUrl: true
|
||||
followRedirects: global
|
||||
cookies:
|
||||
send: true
|
||||
store: true
|
||||
rebuildPath: true
|
||||
cookieJar:
|
||||
name: Default Jar
|
||||
meta:
|
||||
id: jar_0ada3b0255e747a383ddf8848f88f4b2
|
||||
created: 1636140994434
|
||||
modified: 1637279629638
|
||||
cookies:
|
||||
- id: "429589439757017"
|
||||
key: foo
|
||||
value: bar
|
||||
domain: domain.com
|
||||
path: /
|
||||
secure: false
|
||||
httpOnly: false
|
||||
environments:
|
||||
name: Base Environment
|
||||
meta:
|
||||
id: env_482f5a98dfe64a948fff489dbd761e42
|
||||
created: 1636140994432
|
||||
modified: 1636140994432
|
||||
isPrivate: false
|
||||
data:
|
||||
customValue: fromEnvManager
|
||||
@@ -0,0 +1 @@
|
||||
ref: refs/heads/master
|
||||
@@ -0,0 +1,6 @@
|
||||
[core]
|
||||
repositoryformatversion = 0
|
||||
filemode = false
|
||||
bare = true
|
||||
symlinks = false
|
||||
ignorecase = true
|
||||
@@ -0,0 +1 @@
|
||||
Unnamed repository; edit this file 'description' to name the repository.
|
||||
@@ -0,0 +1,6 @@
|
||||
# git ls-files --others --exclude-from=.git/info/exclude
|
||||
# Lines that start with '#' are comments.
|
||||
# For a project mostly in C, the following would be a good set of
|
||||
# exclude patterns (uncomment them if you want to use them):
|
||||
# *.[oa]
|
||||
# *~
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
x<01><>A
|
||||
<EFBFBD>0@Ѯs<D1AE><73><05>d☁Rz<52>8<1D>P<1B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>'<27><>->_<><5F><EFBFBD><EFBFBD>z<07>ZT-b<>S`<60>؍0R<30>B<EFBFBD><42><EFBFBD>`<18><>$<24>gv&<26>u<EFBFBD><75>)t^<5E><>+E&
|
||||
<EFBFBD><EFBFBD><EFBFBD>}ߴl<DFB4>;]_G<5F><47><EFBFBD><EFBFBD><0F><><EFBFBD>f]ߓG<DF93>Ķ<EFBFBD>`N=?<3F><><EFBFBD>h<EFBFBD><68><02><>D,
|
||||
@@ -0,0 +1 @@
|
||||
73a07b09e3e790de0277df5ac98e0ad62ecf83d4
|
||||
65
packages/insomnia-smoke-test/fixtures/ipv6-collection.yaml
Normal file
65
packages/insomnia-smoke-test/fixtures/ipv6-collection.yaml
Normal file
@@ -0,0 +1,65 @@
|
||||
type: collection.insomnia.rest/5.0
|
||||
schema_version: "5.1"
|
||||
name: IPV6 Smoke tests
|
||||
meta:
|
||||
id: wrk_92410798721649cc8cd6f7c312a8764d
|
||||
created: 1736279960080
|
||||
modified: 1736279960080
|
||||
collection:
|
||||
- url: http://[::1]:4010/pets/1
|
||||
name: send JSON request
|
||||
meta:
|
||||
id: req_22084aca139d42c6878afdc1a99db23b
|
||||
created: 1636141014552
|
||||
modified: 1636707449231
|
||||
isPrivate: false
|
||||
method: GET
|
||||
headers:
|
||||
- name: test
|
||||
value: test
|
||||
settings:
|
||||
renderRequestBody: true
|
||||
encodeUrl: true
|
||||
followRedirects: global
|
||||
cookies:
|
||||
send: true
|
||||
store: true
|
||||
rebuildPath: true
|
||||
- url: http://[::1]:4010/file/dummy.csv
|
||||
name: sends dummy.csv request and shows rich response
|
||||
meta:
|
||||
id: req_ed583d913fec40c8b29f51b87b1843ef
|
||||
created: 1636141038448
|
||||
modified: 1636141047337
|
||||
isPrivate: false
|
||||
method: GET
|
||||
settings:
|
||||
renderRequestBody: true
|
||||
encodeUrl: true
|
||||
followRedirects: global
|
||||
cookies:
|
||||
send: true
|
||||
store: true
|
||||
rebuildPath: true
|
||||
cookieJar:
|
||||
name: Default Jar
|
||||
meta:
|
||||
id: jar_0a46909e87a24a05961f3b7c6b591d23
|
||||
created: 1636140994434
|
||||
modified: 1637279629638
|
||||
cookies:
|
||||
- key: foo
|
||||
value: bar
|
||||
domain: domain.com
|
||||
path: /
|
||||
creation: 2021-11-18T23:53:05.310Z
|
||||
id: "429589439757017"
|
||||
environments:
|
||||
name: Base Environment
|
||||
meta:
|
||||
id: env_6fda0ba942704e71a81a02e2bf5fe5ff
|
||||
created: 1636140994432
|
||||
modified: 1636140994432
|
||||
isPrivate: false
|
||||
data:
|
||||
customValue: fromEnvManager
|
||||
@@ -20,7 +20,7 @@
|
||||
"serve": "esr server/index.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "1.55.1",
|
||||
"@playwright/test": "1.59.1",
|
||||
"@ravanallc/grpc-server-reflection": "^0.1.6",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/oidc-provider": "^8.8.1",
|
||||
@@ -30,6 +30,7 @@
|
||||
"esbuild-runner": "2.2.2",
|
||||
"express": "^4.21.2",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"git-http-backend": "^1.1.2",
|
||||
"graphql": "^16.10.0",
|
||||
"graphql-http": "^1.22.4",
|
||||
"http-errors": "^2.0.0",
|
||||
|
||||
@@ -1,4 +1,33 @@
|
||||
import os from 'node:os';
|
||||
|
||||
import type { PlaywrightTestConfig } from '@playwright/test';
|
||||
const isWindows = os.platform() === 'win32';
|
||||
const echoServer: PlaywrightTestConfig['webServer'] = {
|
||||
name: 'Echo server',
|
||||
command: 'npm run serve',
|
||||
url: 'http://localhost:4010',
|
||||
timeout: 15 * 1000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
wait: {
|
||||
stdout: /Listening at http/,
|
||||
},
|
||||
};
|
||||
const viteServer: PlaywrightTestConfig['webServer'] = {
|
||||
name: 'Vite Server',
|
||||
cwd: '../../',
|
||||
command: 'npm run watch:app',
|
||||
url: 'http://localhost:3334',
|
||||
timeout: 120 * 1000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
stdout: 'pipe',
|
||||
stderr: 'pipe',
|
||||
wait: {
|
||||
stdout: /VITE\s+ready in/,
|
||||
},
|
||||
};
|
||||
const onlyStartWebServerInDev = !process.env.BUNDLE || process.env.BUNDLE === 'dev';
|
||||
const config: PlaywrightTestConfig = {
|
||||
projects: [
|
||||
{
|
||||
@@ -20,12 +49,7 @@ const config: PlaywrightTestConfig = {
|
||||
retries: 0,
|
||||
},
|
||||
],
|
||||
webServer: {
|
||||
command: 'npm run serve',
|
||||
url: 'http://127.0.0.1:4010',
|
||||
timeout: 120 * 1000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
webServer: [echoServer, ...(onlyStartWebServerInDev ? [viteServer] : [])],
|
||||
use: {
|
||||
trace: {
|
||||
mode: 'retain-on-failure',
|
||||
@@ -35,7 +59,7 @@ const config: PlaywrightTestConfig = {
|
||||
},
|
||||
},
|
||||
reporter: process.env.CI ? [['github'], ['line']] : [['list']],
|
||||
timeout: process.env.CI ? 60 * 1000 : 20 * 1000,
|
||||
timeout: process.env.CI || isWindows ? 60 * 1000 : 20 * 1000,
|
||||
forbidOnly: !!process.env.CI,
|
||||
outputDir: 'traces',
|
||||
testDir: 'tests',
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { ElectronApplication, Locator, Page } from '@playwright/test';
|
||||
|
||||
import { BasePage } from '../base-page';
|
||||
|
||||
/**
|
||||
* Component for the **Credentials tab** within Insomnia Preferences.
|
||||
*
|
||||
* Handles credential management functionality:
|
||||
* - Add, edit, and remove credentials
|
||||
*/
|
||||
export class PreferencesCredentialsTab extends BasePage {
|
||||
constructor(
|
||||
readonly page: Page,
|
||||
readonly app: ElectronApplication,
|
||||
) {
|
||||
super(page);
|
||||
}
|
||||
|
||||
get root(): Locator {
|
||||
return this.page.getByTestId('credentials-settings-tab');
|
||||
}
|
||||
|
||||
async addAccessTokenGitCredential() {
|
||||
await this.page.getByRole('button', { name: 'Create Git Credential' }).click();
|
||||
await this.page.getByText('Access Token').click();
|
||||
await this.page.getByRole('textbox', { name: 'Author Email' }).click();
|
||||
await this.page.getByRole('textbox', { name: 'Author Email' }).fill('a@b.com');
|
||||
await this.page.getByRole('textbox', { name: 'Author Name' }).click();
|
||||
await this.page.getByRole('textbox', { name: 'Author Name' }).fill('author');
|
||||
await this.page.getByRole('textbox', { name: 'Username' }).click();
|
||||
await this.page.getByRole('textbox', { name: 'Username' }).fill('username');
|
||||
await this.page.getByRole('textbox', { name: 'Git Access Token' }).click();
|
||||
await this.page.getByRole('textbox', { name: 'Git Access Token' }).fill('accesstoken');
|
||||
await this.page.getByRole('textbox', { name: 'Repository base URL' }).click();
|
||||
await this.page.getByRole('textbox', { name: 'Repository base URL' }).fill('http://localhost:4010/git/');
|
||||
await this.page.getByRole('button', { name: 'Save Credential' }).click();
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,30 @@
|
||||
import type { ElectronApplication, Locator, Page } from '@playwright/test';
|
||||
|
||||
import { PreferencesCredentialsTab } from './credentials-tab';
|
||||
import { PreferencesDataTab } from './data-tab';
|
||||
|
||||
type PreferencesTab = 'Data' | 'General' | 'Themes' | 'Plugins' | 'Other';
|
||||
type PreferencesTab = 'Data' | 'General' | 'Themes' | 'Credentials' | 'Plugins' | 'Other';
|
||||
|
||||
/**
|
||||
* Page Object for **Insomnia Preferences** modal.
|
||||
*
|
||||
* Composes preference tabs:
|
||||
* - Data tab (import/export)
|
||||
* - Credentials tab (Git credentials management)
|
||||
* - Other tabs (themes, plugins, etc.) can be added as needed
|
||||
*/
|
||||
export class PreferencesPage {
|
||||
/** Data tab (import/export functionality). */
|
||||
readonly dataTab: PreferencesDataTab;
|
||||
/** Credentials tab (Git credentials management). */
|
||||
readonly credentialsTab: PreferencesCredentialsTab;
|
||||
|
||||
constructor(
|
||||
readonly page: Page,
|
||||
readonly app: ElectronApplication,
|
||||
) {
|
||||
this.dataTab = new PreferencesDataTab(page, app);
|
||||
this.credentialsTab = new PreferencesCredentialsTab(page, app);
|
||||
}
|
||||
|
||||
/** The root preferences dialog. */
|
||||
@@ -43,7 +48,7 @@ export class PreferencesPage {
|
||||
* Closes the preferences modal.
|
||||
*/
|
||||
async closePreferences(): Promise<void> {
|
||||
await this.page.locator('.app').press('Escape');
|
||||
await this.page.getByRole('button', { name: 'Modal Close Button' }).click();
|
||||
await this.root.waitFor({ state: 'hidden' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,33 @@ export class ProjectPage extends BasePage {
|
||||
await this.page.getByRole('button', { name: 'Create', exact: true }).click();
|
||||
}
|
||||
|
||||
async createGitSyncProject(name = 'My Git Project'): Promise<void> {
|
||||
await this.page.getByRole('button', { name: 'Create new Project' }).click();
|
||||
await this.page.getByRole('textbox', { name: 'Project name' }).click();
|
||||
await this.page.getByRole('textbox', { name: 'Project name' }).press('ControlOrMeta+a');
|
||||
await this.page.getByRole('textbox', { name: 'Project name' }).fill(name);
|
||||
await this.page.getByText('Git Sync').click();
|
||||
await this.page.getByRole('button', { name: 'Access Token author Git' }).click();
|
||||
await this.page.getByRole('option', { name: 'Custom Git Credential' }).click();
|
||||
await this.page.getByRole('textbox', { name: 'Repository URL' }).click();
|
||||
await this.page.getByRole('textbox', { name: 'Repository URL' }).fill('git-server.git');
|
||||
await this.page.getByRole('button', { name: 'Show suggestions Branch' }).click();
|
||||
await this.page.getByRole('option', { name: 'master' }).click();
|
||||
await this.page.getByRole('button', { name: 'Scan for files' }).click();
|
||||
await this.page.getByRole('button', { name: 'Create Blank Project' }).click();
|
||||
const projectModalCloseButton = this.page.locator('[data-test-id="project-modal-close-button"]');
|
||||
await projectModalCloseButton.waitFor({ state: 'visible', timeout: 5000 }).catch(() => {});
|
||||
if (await projectModalCloseButton.isVisible()) {
|
||||
await projectModalCloseButton.click();
|
||||
}
|
||||
await this.page.getByRole('button', { name: 'Personal workspace' }).click();
|
||||
await this.page.getByRole('option', { name: /Magic/ }).locator('span').click();
|
||||
await this.page.getByRole('button', { name: /Magic/ }).click();
|
||||
await this.page.getByRole('option', { name: 'Personal workspace' }).locator('span').click();
|
||||
await this.page.getByText('Git Project').waitFor({ state: 'visible', timeout: 10_000 });
|
||||
await this.page.getByText('Git Project').click();
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Import Operations
|
||||
// ===========================================================================
|
||||
|
||||
@@ -69,7 +69,7 @@ const hasBinaryBeenBuilt = fs.existsSync(path.resolve(cwd, insomniaBinary));
|
||||
// NOTE: guard against missing build artifacts
|
||||
if (bundleType() === 'dev' && !hasMainBeenBuilt) {
|
||||
console.error(`ERROR: ${mainPath} not found at ${path.resolve(cwd, mainPath)}
|
||||
Have you run "npm run watch:app"?`);
|
||||
Ensure that playwright has run npm run watch:app`);
|
||||
exit(1);
|
||||
}
|
||||
if (bundleType() === 'build' && !hasMainBeenBuilt) {
|
||||
|
||||
@@ -77,18 +77,17 @@ export const test = baseTest.extend<{
|
||||
insomnia: InsomniaApp;
|
||||
}>({
|
||||
app: async ({ playwright, trace, dataPath, userConfig }, use, testInfo) => {
|
||||
invariant(testInfo.config.webServer?.url, 'Requires web server config');
|
||||
const webServerUrl = testInfo.config.webServer.url;
|
||||
const echoServer = 'http://localhost:4010';
|
||||
|
||||
const options: EnvOptions = {
|
||||
INSOMNIA_DATA_PATH: dataPath,
|
||||
INSOMNIA_API_URL: webServerUrl,
|
||||
INSOMNIA_APP_WEBSITE_URL: webServerUrl + '/website',
|
||||
INSOMNIA_AI_URL: webServerUrl + '/ai',
|
||||
INSOMNIA_GITHUB_REST_API_URL: webServerUrl + '/github-api/rest',
|
||||
INSOMNIA_GITHUB_API_URL: webServerUrl + '/github-api/graphql',
|
||||
INSOMNIA_GITLAB_API_URL: webServerUrl + '/gitlab-api',
|
||||
INSOMNIA_UPDATES_URL: webServerUrl || 'https://updates.insomnia.rest',
|
||||
INSOMNIA_API_URL: echoServer,
|
||||
INSOMNIA_APP_WEBSITE_URL: echoServer + '/website',
|
||||
INSOMNIA_AI_URL: echoServer + '/ai',
|
||||
INSOMNIA_GITHUB_REST_API_URL: echoServer + '/github-api/rest',
|
||||
INSOMNIA_GITHUB_API_URL: echoServer + '/github-api/graphql',
|
||||
INSOMNIA_GITLAB_API_URL: echoServer + '/gitlab-api',
|
||||
INSOMNIA_UPDATES_URL: echoServer || 'https://updates.insomnia.rest',
|
||||
INSOMNIA_MOCK_API_URL: 'https://mock-stage.insomnia.run',
|
||||
INSOMNIA_SKIP_ONBOARDING: String(userConfig.skipOnboarding),
|
||||
INSOMNIA_PUBLIC_KEY: userConfig.publicKey,
|
||||
@@ -98,13 +97,14 @@ export const test = baseTest.extend<{
|
||||
INSOMNIA_VAULT_SRP_SECRET: userConfig.vaultSrpSecret || '',
|
||||
...(userConfig.session ? { INSOMNIA_SESSION: JSON.stringify(userConfig.session) } : {}),
|
||||
};
|
||||
const { ELECTRON_RUN_AS_NODE: _ignored, ...launchEnv } = process.env;
|
||||
|
||||
const electronApp = await playwright._electron.launch({
|
||||
cwd,
|
||||
executablePath,
|
||||
args: bundleType() === 'package' ? ['--no-sandbox'] : ['--no-sandbox', mainPath],
|
||||
env: {
|
||||
...process.env,
|
||||
...launchEnv,
|
||||
...options,
|
||||
PLAYWRIGHT: 'true',
|
||||
},
|
||||
|
||||
@@ -291,6 +291,17 @@ let deletedProjectIds: string[] = [];
|
||||
let cloudSyncApiEnabled = false;
|
||||
let remoteHasNewCommit = false;
|
||||
|
||||
const resetCloudSyncTestState = () => {
|
||||
Object.keys(newSnapshots).forEach(projectId => {
|
||||
delete newSnapshots[projectId];
|
||||
});
|
||||
Object.keys(newBlobs).forEach(blobId => {
|
||||
delete newBlobs[blobId];
|
||||
});
|
||||
deletedProjectIds = [];
|
||||
remoteHasNewCommit = false;
|
||||
};
|
||||
|
||||
const getSnapshotsForProject = (projectId: string) => {
|
||||
const originalSnapshots = projectSnapshots[projectId] || [];
|
||||
const addedSnapshots = newSnapshots[projectId] || [];
|
||||
@@ -308,13 +319,16 @@ export default function setup(app: Application) {
|
||||
}
|
||||
cloudSyncApiEnabled = enabled;
|
||||
if (!enabled) {
|
||||
// clear the test data when cloud sync is disabled to avoid affecting other tests
|
||||
deletedProjectIds = [];
|
||||
remoteHasNewCommit = false;
|
||||
resetCloudSyncTestState();
|
||||
}
|
||||
return res.status(200).send();
|
||||
});
|
||||
|
||||
app.post('/__test-config/cloud-sync/reset', json(), (_req, res) => {
|
||||
resetCloudSyncTestState();
|
||||
return res.status(200).send();
|
||||
});
|
||||
|
||||
app.post('/__test-config/cloud-sync/new-commit', json(), (req, res) => {
|
||||
const { enabled = false } = req.body ?? {};
|
||||
remoteHasNewCommit = !!enabled;
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
import { spawn } from 'node:child_process';
|
||||
import crypto from 'node:crypto';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { cpSync, existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
||||
import { createServer } from 'node:https';
|
||||
import { tmpdir } from 'node:os';
|
||||
import nodePath from 'node:path';
|
||||
import type { Duplex } from 'node:stream';
|
||||
|
||||
import * as bodyParser from 'body-parser';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import express from 'express';
|
||||
import { createHandler } from 'graphql-http/lib/use/http';
|
||||
|
||||
interface GitBackendService {
|
||||
type: string;
|
||||
cmd: string;
|
||||
args: string[];
|
||||
createStream(): Duplex;
|
||||
}
|
||||
// git-http-backend has no @types package; require+cast is the standard workaround
|
||||
const backend = require('git-http-backend') as (
|
||||
url: string,
|
||||
cb: (err: Error | null, service: GitBackendService) => void,
|
||||
) => NodeJS.ReadWriteStream;
|
||||
|
||||
import { basicAuthRouter } from './basic-auth';
|
||||
import cloudSyncApi from './cloud-sync-api';
|
||||
import githubApi from './github-api';
|
||||
@@ -152,9 +167,56 @@ app.get('/v1/oauth/azure/config', (_req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
const GIT_FIXTURE_ROOT = nodePath.join(__dirname, '../fixtures/git-repo');
|
||||
let currentGitTmpDir: string | null = null;
|
||||
|
||||
// Create a fresh per-test copy of the git fixture repo
|
||||
app.post('/v1/test-utils/git/setup', (_req, res) => {
|
||||
if (currentGitTmpDir && existsSync(currentGitTmpDir)) {
|
||||
rmSync(currentGitTmpDir, { recursive: true, force: true });
|
||||
}
|
||||
currentGitTmpDir = mkdtempSync(nodePath.join(tmpdir(), 'insomnia-git-'));
|
||||
cpSync(GIT_FIXTURE_ROOT, currentGitTmpDir, { recursive: true });
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// Remove the per-test copy
|
||||
app.delete('/v1/test-utils/git/setup', (_req, res) => {
|
||||
if (currentGitTmpDir && existsSync(currentGitTmpDir)) {
|
||||
rmSync(currentGitTmpDir, { recursive: true, force: true });
|
||||
}
|
||||
currentGitTmpDir = null;
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// Git smart HTTP server backed by git-http-backend — accepts real pushes.
|
||||
// Falls back to the fixture root if no per-test dir is set up.
|
||||
app.use('/git', (req, res) => {
|
||||
const root = currentGitTmpDir ?? GIT_FIXTURE_ROOT;
|
||||
// req.url has the '/git' prefix stripped by Express, e.g. '/git-server.git/info/refs?...'
|
||||
const repoPath = nodePath.join(root, req.url.split('?')[0].split('/')[1]);
|
||||
|
||||
req.pipe(
|
||||
backend(req.url, (err, service) => {
|
||||
if (err) {
|
||||
res.status(500).end(err.message);
|
||||
return;
|
||||
}
|
||||
res.setHeader('content-type', service.type);
|
||||
const ps = spawn(service.cmd, service.args.concat(repoPath), {
|
||||
env: { ...process.env, GIT_HTTP_EXPORT_ALL: '1' },
|
||||
});
|
||||
ps.stderr.on('data', d => console.error('[git]', String(d)));
|
||||
ps.stdout.pipe(service.createStream()).pipe(ps.stdin);
|
||||
}),
|
||||
).pipe(res);
|
||||
});
|
||||
|
||||
startWebSocketServer(
|
||||
app.listen(port, () => {
|
||||
app.listen(port, '::', () => {
|
||||
console.log(`Listening at http://localhost:${port}`);
|
||||
console.log(`Listening at http://127.0.0.1:${port}`);
|
||||
console.log(`Listening at http://[::1]:${port}`);
|
||||
console.log(`Listening at ws://localhost:${port}`);
|
||||
}),
|
||||
);
|
||||
@@ -169,8 +231,10 @@ startWebSocketServer(
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
app,
|
||||
).listen(httpsPort, () => {
|
||||
).listen(httpsPort, '::', () => {
|
||||
console.log(`Listening at https://localhost:${httpsPort}`);
|
||||
console.log(`Listening at https://127.0.0.1:${httpsPort}`);
|
||||
console.log(`Listening at https://[::1]:${httpsPort}`);
|
||||
console.log(`Listening at wss://localhost:${httpsPort}`);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -81,47 +81,40 @@ let organizationFeatures = {
|
||||
},
|
||||
};
|
||||
|
||||
const user = {
|
||||
id: 'email|64f0dd619ab0786da330d83a',
|
||||
const v3User = {
|
||||
id: 'acct_64a477e6b59d43a5a607f84b4f73e3ce',
|
||||
email: 'insomnia-user@konghq.com',
|
||||
name: 'Rick Morty',
|
||||
first_name: 'Rick',
|
||||
last_name: 'Morty',
|
||||
picture: '',
|
||||
bio: 'My BIO',
|
||||
github: '',
|
||||
linkedin: '',
|
||||
twitter: '',
|
||||
identities: null,
|
||||
given_name: '',
|
||||
family_name: '',
|
||||
emails: [],
|
||||
encryption_enabled: true,
|
||||
is_externally_provisioned: false,
|
||||
};
|
||||
|
||||
const whoami = {
|
||||
sessionExpiry: 4_838_400,
|
||||
publicKey: {
|
||||
const v3EncryptionKeys = {
|
||||
public_key: JSON.stringify({
|
||||
alg: 'RSA-OAEP-256',
|
||||
e: 'AQAB',
|
||||
ext: true,
|
||||
key_ops: ['encrypt'],
|
||||
kty: 'RSA',
|
||||
n: 'pTQVaUaiqggIldSKm6ib6eFRLLoGj9W-2O4gTbiorR-2b8-ZmKUwQ0F-jgYX71AjYaFn5VjOHOHSP6byNAjN7WzJ6A_Z3tytNraLoZfwK8KdfflOCZiZzQeD3nO8BNgh_zEgCHStU61b6N6bSpCKjbyPkmZcOkJfsz0LJMAxrXvFB-I42WYA2vJKReTJKXeYx4d6L_XGNIoYtmGZit8FldT4AucfQUXgdlKvr4_OZmt6hgjwt_Pjcu-_jO7m589mMWMebfUhjte3Lp1jps0MqTOvgRb0FQf5eoBHnL01OZjvFPDKeqlvoz7II9wFNHIKzSvgAKnyemh6DiyPuIukyQ',
|
||||
},
|
||||
encPrivateKey: {
|
||||
}),
|
||||
enc_private_key: JSON.stringify({
|
||||
iv: '3a1f2bdb8acbf15f469d57a2',
|
||||
t: '904d6b1bc0ece8e5df6fefb9efefda7c',
|
||||
d: '2a7b0c4beb773fa3e3c2158f0bfa654a88c4041184c3b1e01b4ddd2da2c647244a0d66d258b6abb6a9385251bf5d79e6b03ef35bdfafcb400547f8f88adb8bceb7020f2d873d5a74fb5fc561e7bd67cea0a37c49107bf5c96631374dc44ddb1e4a8b5688dc6560fc6143294ed92c3ad8e1696395dfdf15975aa67b9212366dbfcb31191e4f4fe3559c89a92fb1f0f1cc6cbf90d8a062307fce6e7701f6f5169d9247c56dae79b55fba1e10fde562b971ca708c9a4d87e6e9d9e890b88fa0480360420e610c4e41459570e52ae72f349eadf84fc0a68153722de3280becf8a1762e7faebe964f0ad706991c521feda3440d3e1b22f2c221a80490359879bd47c0d059ace81213c74a1e192dbebd8a80cf58c9eb1fe461a971b88d3899baf4c4ef7141623c93fb4a54758f5e1cf9ee35cd00777fa89b24e4ded57219e770de2670619c6e971935c61ae72e3276cf8db49dfa0e91c68222f02d7e0c69b399af505de7e5a90852d83e0a30934b0362db986f3aaefaaf1a96fef3e8165287a3a7f0ee1e072d9dee3aefb86194e1d877d6b34529d45a70ec4573c35a7fe27833c77c3154b0ad02187e4fcecd408bcf4b29a85a5dc358cb479140f4983fcd936141f581764669651530af97d2b7d9416aea7de67e787f3e29ae3eba6672bcd934dc1e308783aa63a4ab46d48d213cf53ad6bd8828011f5bfa3aa5ee24551c694e829b54c93b1dda6c3ddda04756d68a28bec8d044c8af4147680dc5b972d0ca74299b0ab6306b9e7b99bf0557558df120455a272145b7aa792654730f3d670b76d72408f5ce1cf5fbd453d2903fa72cf26397437854ba8abbb731a8107f6a86a01fa98edc81bb42a4c1330f779e7a0fbd1820eaed78e03e40a996e03884b707556be06fd14ee8f4035469210d1d2bb8f58285fc2ab6de3d3cc0e4e1f40c6d9d24b50dc8e2e2374a0aff52031b3736c2982133bb19dd551ce1f953f4ba02b0cf53382c15752e202c138cb42b2322df103ff17fd886dfd5f992b711673cdf16048c4bff19038138b161c2e1783b85fc7b965a91ac4795fcbfebf827940cacdeae57946863aee027df43b36612f3cb8f34dc44396e87c564bf10f5b1a9dfbd6da3d7f4f65024b0b4f8ce51d01c230840941fc4523b17eb1c2522032f410e8328239a11a15ab755c32945ce52966d5bfb4666909ed2ca04d536e4bf92091563dd44d46cbb35e53c2481400058ab3b52a0280d262551073f61db125ee280e2cc1ec0bdf9c4817824261465011e34c2296411384f7f5e16742157c5520f137631edf498aa39c7c32b107e3634cbeb70feea19a233c8bd939d665135c9f7c1bb33cb47edc58bdbbcde9b0b9eb73a46642e4639289a62638fb7813e1eeaadd105c803de8357236f33c4bcf31a876b5867591af8f165eba0b35cf0b0886af17dab35a6a39f8f576387d6ffb9e677ee46fc0f11ff069a2a068fce441ff8f4125095fad228c2bf45c788d641941ed13c0a16fffcafd7c7eff11bb7550c0b7d54eebdbd2066e3bbdb47aaee2b5f1e499726324a40015458c7de1db0abe872594d8e6802deff7ea9518bdb3a3e46f07139267fd67dc570ba8ab04c2b37ce6a34ec73b802c7052a2eef0cae1b0979322ef86395535db80cf2a9a88aa7c2e5cc28a93612a8dafe1982f741d7cec28a866f6c09dba5b99ead24c3df0ca03c6c5afae41f3d39608a8f49b0d6a0b541a159409791c25ede103eb4f79cfbd0cc9c9aa6b591755c1e9fd07b5b9e38ed85b5939e65d127256f6a4c078f8c9d655c4f072f9cbcfb2e1e17eaa83dc62aaab2a6dc3735ee76ce7a215740f795f1fbe7136c7734ae3714438015e8fc383d63775a8abddb23cbc5f906c046bb0b5b31d492a7c151b40ea82c7c966e25820641c55b343b89d6378f90de5983fa76547e9d6c634effdf019a0fd9b6d3e488a5aa94f0710d517ba4f7c1ed82f9f3072612e953e036c0ec7f3c618368362f6da6f3af76056a66aef914805cc8b628f1c11695f760b535ded9ff66727273ae7e12d67a01243d75f22fec8ed1b043122a211c923aa92ecbbe01dd0d7195c3c0e09a2a6ab3eca354963122d5a0ec16e2b2b81b0ddce6ec0a312c492a96a4fd392f1deb6a1f3318541a3f87e5c9e73ee7edd3b855910f412789e25038108e1eaae04dcfb02b4d958c00c630dc8caa87a40798ce7156d2ade882e68832d39fe8f9bce6a995249a7383013a5093c4af55c3b7232de0f2593d82c30b8dabd0784455037f25f6bb66a6d0d8f72bc7be0dee2d0a8af44bb4e143257d873268d331722c3253ea5c004e72daf04c875e2054f2b4b2bca2979fd046a1e835600045edf2f159d851a540a91a1ab8fbcb64594d21942bbaa2160535d32496ba7ce4a76c6bdeb9bb4c5cab7bed1ae26564058d0be125803d7019b83b3953c4b0cc1f8299c4edcf6a5faa4765092412d368b277689900e71fb5d47581057adaa2dd494e0f66dc1aa16f3741973b0d9ffa1728aeafab84b777394a7afae0f8eabaa6b740f1c60ca26469f0c9356ec880ad6f4dc01b99bd14d7a4bb8afc97662a9e68b0155e4cdf3caa3402819ac6ce562c8fe06edb50a31cfd7a',
|
||||
ad: '',
|
||||
},
|
||||
symmetricKey: {
|
||||
alg: 'A256GCM',
|
||||
ext: true,
|
||||
k: 'w62OJNWF4G8iWA8ZrTpModiY8dICyHI7ko1vMLb877g=',
|
||||
key_ops: ['encrypt', 'decrypt'],
|
||||
kty: 'oct',
|
||||
},
|
||||
email: 'insomnia-user@konghq.com',
|
||||
accountId: 'acct_64a477e6b59d43a5a607f84b4f73e3ce',
|
||||
firstName: 'Rick',
|
||||
lastName: 'Morty',
|
||||
}),
|
||||
enc_symmetric_key: JSON.stringify({
|
||||
iv: '3a1f2bdb8acbf15f469d57a2',
|
||||
t: '904d6b1bc0ece8e5df6fefb9efefda7c',
|
||||
d: '2a7b0c4beb773fa3e3c2158f0bfa654a88c4041184c3b1e01b4ddd2da2c647244a0d66d258b6abb6a9385251bf5d79e6b03ef35bdfafcb400547f8f88adb8bceb7020f2d873d5a74fb5fc561e7bd67cea0a37c49107bf5c96631374dc44ddb1e4a8b5688dc6560fc6143294ed92c3ad8e1696395dfdf15975aa67b9212366dbfcb31191e4f4fe3559c89a92fb1f0f1cc6cbf90d8a062307fce6e7701f6f5169d9247c56dae79b55fba1e10fde562b971ca708c9a4d87e6e9d9e890b88fa0480360420e610c4e41459570e52ae72f349eadf84fc0a68153722de3280becf8a1762e7faebe964f0ad706991c521feda3440d3e1b22f2c221a80490359879bd47c0d059ace81213c74a1e192dbebd8a80cf58c9eb1fe461a971b88d3899baf4c4ef7141623c93fb4a54758f5e1cf9ee35cd00777fa89b24e4ded57219e770de2670619c6e971935c61ae72e3276cf8db49dfa0e91c68222f02d7e0c69b399af505de7e5a90852d83e0a30934b0362db986f3aaefaaf1a96fef3e8165287a3a7f0ee1e072d9dee3aefb86194e1d877d6b34529d45a70ec4573c35a7fe27833c77c3154b0ad02187e4fcecd408bcf4b29a85a5dc358cb479140f4983fcd936141f581764669651530af97d2b7d9416aea7de67e787f3e29ae3eba6672bcd934dc1e308783aa63a4ab46d48d213cf53ad6bd8828011f5bfa3aa5ee24551c694e829b54c93b1dda6c3ddda04756d68a28bec8d044c8af4147680dc5b972d0ca74299b0ab6306b9e7b99bf0557558df120455a272145b7aa792654730f3d670b76d72408f5ce1cf5fbd453d2903fa72cf26397437854ba8abbb731a8107f6a86a01fa98edc81bb42a4c1330f779e7a0fbd1820eaed78e03e40a996e03884b707556be06fd14ee8f4035469210d1d2bb8f58285fc2ab6de3d3cc0e4e1f40c6d9d24b50dc8e2e2374a0aff52031b3736c2982133bb19dd551ce1f953f4ba02b0cf53382c15752e202c138cb42b2322df103ff17fd886dfd5f992b711673cdf16048c4bff19038138b161c2e1783b85fc7b965a91ac4795fcbfebf827940cacdeae57946863aee027df43b36612f3cb8f34dc44396e87c564bf10f5b1a9dfbd6da3d7f4f65024b0b4f8ce51d01c230840941fc4523b17eb1c2522032f410e8328239a11a15ab755c32945ce52966d5bfb4666909ed2ca04d536e4bf92091563dd44d46cbb35e53c2481400058ab3b52a0280d262551073f61db125ee280e2cc1ec0bdf9c4817824261465011e34c2296411384f7f5e16742157c5520f137631edf498aa39c7c32b107e3634cbeb70feea19a233c8bd939d665135c9f7c1bb33cb47edc58bdbbcde9b0b9eb73a46642e4639289a62638fb7813e1eeaadd105c803de8357236f33c4bcf31a876b5867591af8f165eba0b35cf0b0886af17dab35a6a39f8f576387d6ffb9e677ee46fc0f11ff069a2a068fce441ff8f4125095fad228c2bf45c788d641941ed13c0a16fffcafd7c7eff11bb7550c0b7d54eebdbd2066e3bbdb47aaee2b5f1e499726324a40015458c7de1db0abe872594d8e6802deff7ea9518bdb3a3e46f07139267fd67dc570ba8ab04c2b37ce6a34ec73b802c7052a2eef0cae1b0979322ef86395535db80cf2a9a88aa7c2e5cc28a93612a8dafe1982f741d7cec28a866f6c09dba5b99ead24c3df0ca03c6c5afae41f3d39608a8f49b0d6a0b541a159409791c25ede103eb4f79cfbd0cc9c9aa6b591755c1e9fd07b5b9e38ed85b5939e65d127256f6a4c078f8c9d655c4f072f9cbcfb2e1e17eaa83dc62aaab2a6dc3735ee76ce7a215740f795f1fbe7136c7734ae3714438015e8fc383d63775a8abddb23cbc5f906c046bb0b5b31d492a7c151b40ea82c7c966e25820641c55b343b89d6378f90de5983fa76547e9d6c634effdf019a0fd9b6d3e488a5aa94f0710d517ba4f7c1ed82f9f3072612e953e036c0ec7f3c618368362f6da6f3af76056a66aef914805cc8b628f1c11695f760b535ded9ff66727273ae7e12d67a01243d75f22fec8ed1b043122a211c923aa92ecbbe01dd0d7195c3c0e09a2a6ab3eca354963122d5a0ec16e2b2b81b0ddce6ec0a312c492a96a4fd392f1deb6a1f3318541a3f87e5c9e73ee7edd3b855910f412789e25038108e1eaae04dcfb02b4d958c00c630dc8caa87a40798ce7156d2ade882e68832d39fe8f9bce6a995249a7383013a5093c4af55c3b7232de0f2593d82c30b8dabd0784455037f25f6bb66a6d0d8f72bc7be0dee2d0a8af44bb4e143257d873268d331722c3253ea5c004e72daf04c875e2054f2b4b2bca2979fd046a1e835600045edf2f159d851a540a91a1ab8fbcb64594d21942bbaa2160535d32496ba7ce4a76c6bdeb9bb4c5cab7bed1ae26564058d0be125803d7019b83b3953c4b0cc1f8299c4edcf6a5faa4765092412d368b277689900e71fb5d47581057adaa2dd494e0f66dc1aa16f3741973b0d9ffa1728aeafab84b777394a7afae0f8eabaa6b740f1c60ca26469f0c9356ec880ad6f4dc01b99bd14d7a4bb8afc97662a9e68b0155e4cdf3caa3402819ac6ce562c8fe06edb50a31cfd7a',
|
||||
ad: '',
|
||||
}),
|
||||
salt_enc: '',
|
||||
enc_driver_key: null,
|
||||
};
|
||||
|
||||
const userVerifyA = {
|
||||
@@ -419,13 +412,12 @@ collaboratorsList.total = collaboratorsList.collaborators.length + emailsAndGrou
|
||||
|
||||
export default function setup(app: Application) {
|
||||
// User
|
||||
app.get('/v1/user/profile', (_req, res) => {
|
||||
console.log('GET *');
|
||||
res.status(200).send(user);
|
||||
app.get('/v3/users/me', (_req, res) => {
|
||||
res.status(200).send(v3User);
|
||||
});
|
||||
|
||||
app.get('/auth/whoami', (_req, res) => {
|
||||
res.status(200).send(whoami);
|
||||
app.get('/v3/users/me/encryption-keys', (_req, res) => {
|
||||
res.status(200).send(v3EncryptionKeys);
|
||||
});
|
||||
|
||||
// Vault related
|
||||
|
||||
@@ -120,5 +120,5 @@ test('can send requests', async ({ page, insomnia }) => {
|
||||
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
|
||||
|
||||
await page.getByRole('button', { name: 'Cancel Request' }).click();
|
||||
await page.click('text=Request was cancelled');
|
||||
await page.getByText('Request was cancelled').click();
|
||||
});
|
||||
|
||||
@@ -15,11 +15,11 @@ test.describe('Cookie editor', () => {
|
||||
|
||||
test('create and send a cookie', async ({ page }) => {
|
||||
// Open cookie editor
|
||||
await page.click('button:has-text("Cookies")');
|
||||
await page.getByRole('button', { name: 'Cookies' }).click();
|
||||
|
||||
// Edit existing cookie
|
||||
await page.getByTestId('cookie-test-iteration-0').getByRole('button', { name: 'Edit' }).click();
|
||||
await page.click('pre[role="presentation"]:has-text("bar")');
|
||||
await page.locator('pre[role="presentation"]').filter({ hasText: 'bar' }).click();
|
||||
await page.locator('[data-testid="CookieValue"] >> textarea').nth(1).fill('123');
|
||||
await page.locator('text=Done').nth(1).click();
|
||||
await page.getByTestId('cookie-test-iteration-0').click();
|
||||
@@ -37,11 +37,11 @@ test.describe('Cookie editor', () => {
|
||||
await page.locator('text=Done').nth(1).click();
|
||||
await page.getByTestId('cookie-test-iteration-0').click();
|
||||
|
||||
await page.click('text=Done');
|
||||
await page.getByText('Done').click();
|
||||
|
||||
// Send http request
|
||||
await page.getByLabel('Request Collection').getByTestId('example http').press('Enter');
|
||||
await page.click('[data-testid="request-pane"] button:has-text("Send")');
|
||||
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
|
||||
|
||||
// Check in the timeline that the cookie was sent
|
||||
|
||||
@@ -50,8 +50,8 @@ test.describe('Cookie editor', () => {
|
||||
|
||||
// Send ws request
|
||||
await page.getByLabel('Request Collection').getByTestId('example websocket').press('Enter');
|
||||
await page.click('text=ws://localhost:4010');
|
||||
await page.click('[data-testid="request-pane"] >> text=Connect');
|
||||
await page.getByText('ws://localhost:4010').click();
|
||||
await page.getByTestId('request-pane').getByText('Connect').click();
|
||||
|
||||
// Check in the timeline that the cookie was sent
|
||||
await page.getByRole('tab', { name: 'Console' }).click();
|
||||
@@ -60,7 +60,7 @@ test.describe('Cookie editor', () => {
|
||||
|
||||
test('support __Host- prefix', async ({ page }) => {
|
||||
// Open cookie editor
|
||||
await page.click('button:has-text("Cookies")');
|
||||
await page.getByRole('button', { name: 'Cookies' }).click();
|
||||
|
||||
// Create a new cookie
|
||||
await page.getByRole('button', { name: 'Add Cookie' }).click();
|
||||
@@ -74,11 +74,11 @@ test.describe('Cookie editor', () => {
|
||||
.locator('text=Raw Cookie String >> input[type="text"]')
|
||||
.fill('__Host-foo=bar; Expires=Tue, 19 Jan 2038 03:14:07 GMT; Secure; Domain=localhost; Path=/');
|
||||
await page.locator('text=Done').nth(1).click();
|
||||
await page.click('text=Done');
|
||||
await page.getByText('Done').click();
|
||||
|
||||
// Send request
|
||||
await page.getByLabel('Request Collection').getByTestId('example http').press('Enter');
|
||||
await page.click('[data-testid="request-pane"] button:has-text("Send")');
|
||||
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
|
||||
|
||||
// Check in the timeline that the cookie was sent
|
||||
await page.getByRole('tab', { name: 'Console' }).click();
|
||||
@@ -87,7 +87,7 @@ test.describe('Cookie editor', () => {
|
||||
|
||||
test('cookie list should update when cookie is updated', async ({ page }) => {
|
||||
// Open cookie editor
|
||||
await page.click('button:has-text("Cookies")');
|
||||
await page.getByRole('button', { name: 'Cookies' }).click();
|
||||
|
||||
// Set domain to empty
|
||||
await page.getByTestId('cookie-test-iteration-0').getByRole('button', { name: 'Edit' }).click();
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { test } from '../../playwright/test';
|
||||
|
||||
const mockCredentials = {
|
||||
email: 'insomnia-test@konghq.com',
|
||||
gitUsername: 'insomnia-test',
|
||||
username: 'insomnia',
|
||||
token: '12345',
|
||||
baseUrl: 'https://fakeurl.com/',
|
||||
};
|
||||
|
||||
test.describe('Git Sync', () => {
|
||||
test.describe('with git sync feature flag disabled', () => {
|
||||
test.beforeEach(async ({ request }) => {
|
||||
// Disable git sync feature flag for organization
|
||||
await request.post('http://127.0.0.1:4010/v1/test-utils/organizations/features', {
|
||||
data: {
|
||||
features: {
|
||||
gitSync: {
|
||||
enabled: false,
|
||||
},
|
||||
konnectSync: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test.afterEach(async ({ request }) => {
|
||||
// Re-enable git sync feature flag for organization
|
||||
await request.post('http://127.0.0.1:4010/v1/test-utils/organizations/features', {
|
||||
data: {
|
||||
features: {
|
||||
gitSync: {
|
||||
enabled: true,
|
||||
},
|
||||
konnectSync: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should disable git sync usage', async ({ page }) => {
|
||||
await page.getByTestId('settings-button').click();
|
||||
await page.getByRole('tab', { name: 'Credentials' }).click();
|
||||
await page.getByRole('button', { name: 'Create Git Credential' }).click();
|
||||
await page.getByText('Access Token').click();
|
||||
await page.getByRole('textbox', { name: 'Author Email' }).fill(mockCredentials.email);
|
||||
await page.getByRole('textbox', { name: 'Author Name' }).fill(mockCredentials.gitUsername);
|
||||
await page.getByRole('textbox', { name: 'Username', exact: true }).fill(mockCredentials.username);
|
||||
await page.getByRole('textbox', { name: 'Git Access Token' }).fill(mockCredentials.token);
|
||||
await page.getByRole('textbox', { name: 'Repository base URL' }).fill(mockCredentials.baseUrl);
|
||||
await page.getByRole('button', { name: 'Save Credential' }).click();
|
||||
await page.getByRole('button', { name: 'Modal Close Button' }).click();
|
||||
await page.getByRole('button', { name: 'Create new Project' }).click();
|
||||
await page.getByLabel('Project Type Item: git').click();
|
||||
await expect.soft(page.getByLabel('Git Sync Feature Disabled Banner')).toBeVisible();
|
||||
|
||||
await expect.soft(page.getByLabel('Git Setup Form')).toBeHidden();
|
||||
await expect.soft(page.getByRole('button', { name: 'Scan for files' })).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('with git storage rule disabled', () => {
|
||||
test.beforeEach(async ({ request }) => {
|
||||
// Set storage rule to disable git sync
|
||||
await request.post('http://127.0.0.1:4010/v1/test-utils/organizations/storage-rule', {
|
||||
data: {
|
||||
enableCloudSync: true,
|
||||
enableGitSync: false,
|
||||
enableLocalVault: true,
|
||||
isOverridden: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test.afterEach(async ({ request }) => {
|
||||
// reset the storage rule after test
|
||||
await request.post('http://127.0.0.1:4010/v1/test-utils/organizations/storage-rule', {
|
||||
data: {
|
||||
enableCloudSync: true,
|
||||
enableGitSync: true,
|
||||
enableLocalVault: true,
|
||||
isOverridden: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('disable git sync selection', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Create new Project' }).click();
|
||||
const banner = page.getByLabel('Project Storage Restriction Banner');
|
||||
await expect.soft(banner).toBeVisible();
|
||||
await expect.soft(banner).not.toHaveText('Git Sync');
|
||||
await expect.soft(page.getByLabel('Project Type: git')).toBeDisabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -33,9 +33,8 @@ test.describe('Export', () => {
|
||||
await insomnia.preferencesPage.switchToPreferenceTab('Data');
|
||||
await insomnia.preferencesPage.dataTab.exportProjectData(tempDir, 'yaml');
|
||||
await waitForExportFiles(tempDir, 2);
|
||||
await insomnia.preferencesPage.closePreferences();
|
||||
const exportedFiles = getExportedFiles(tempDir);
|
||||
expect.soft(exportedFiles.length).toBe(2);
|
||||
expect.soft(exportedFiles).toHaveLength(2);
|
||||
const fixtureMap: Record<string, string> = {
|
||||
'Collection-A': FIXTURE_FILES[0],
|
||||
'Collection-B': FIXTURE_FILES[1],
|
||||
@@ -78,7 +77,7 @@ test.describe('Export', () => {
|
||||
await insomnia.preferencesPage.dataTab.exportAllData(tempDir);
|
||||
await insomnia.preferencesPage.closePreferences();
|
||||
const exportedFiles = getExportedFiles(tempDir).filter((file: string) => !file.includes('scratchpad'));
|
||||
expect.soft(exportedFiles.length).toBe(2);
|
||||
expect.soft(exportedFiles).toHaveLength(2);
|
||||
const fixtureMap: Record<string, string> = {
|
||||
'Collection-A': FIXTURE_FILES[0],
|
||||
'Collection-B': FIXTURE_FILES[1],
|
||||
@@ -123,8 +122,6 @@ test.describe('Export', () => {
|
||||
await insomnia.preferencesPage.dataTab.exportProjectData(exportFilePath, 'har');
|
||||
await waitForExportFiles(tempDir, 1);
|
||||
|
||||
await insomnia.preferencesPage.closePreferences();
|
||||
|
||||
const exportedContent = readExportedFile(exportFilePath);
|
||||
|
||||
const har = JSON.parse(exportedContent);
|
||||
|
||||
@@ -1,101 +1,171 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import type { InsomniaApp } from '../../playwright/pages';
|
||||
import { test } from '../../playwright/test';
|
||||
|
||||
const mockCredentials = {
|
||||
email: 'insomnia-test@konghq.com',
|
||||
gitUsername: 'insomnia-test',
|
||||
username: 'insomnia',
|
||||
token: '12345',
|
||||
baseUrl: 'https://fakeurl.com/',
|
||||
};
|
||||
|
||||
test.describe('Git Sync', () => {
|
||||
test.describe('with git sync feature flag disabled', () => {
|
||||
test.beforeEach(async ({ request }) => {
|
||||
// Disable git sync feature flag for organization
|
||||
await request.post('http://127.0.0.1:4010/v1/test-utils/organizations/features', {
|
||||
data: {
|
||||
features: {
|
||||
gitSync: {
|
||||
enabled: false,
|
||||
},
|
||||
konnectSync: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
test.slow();
|
||||
|
||||
test.afterEach(async ({ request }) => {
|
||||
// Re-enable git sync feature flag for organization
|
||||
await request.post('http://127.0.0.1:4010/v1/test-utils/organizations/features', {
|
||||
data: {
|
||||
features: {
|
||||
gitSync: {
|
||||
enabled: true,
|
||||
},
|
||||
konnectSync: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('should disable git sync usage', async ({ page }) => {
|
||||
await page.getByTestId('settings-button').click();
|
||||
await page.getByRole('tab', { name: 'Credentials' }).click();
|
||||
await page.getByRole('button', { name: 'Create Git Credential' }).click();
|
||||
await page.getByText('Access Token').click();
|
||||
await page.getByRole('textbox', { name: 'Author Email' }).fill(mockCredentials.email);
|
||||
await page.getByRole('textbox', { name: 'Author Name' }).fill(mockCredentials.gitUsername);
|
||||
await page.getByRole('textbox', { name: 'Username', exact: true }).fill(mockCredentials.username);
|
||||
await page.getByRole('textbox', { name: 'Git Access Token' }).fill(mockCredentials.token);
|
||||
await page.getByRole('textbox', { name: 'Repository base URL' }).fill(mockCredentials.baseUrl);
|
||||
await page.getByRole('button', { name: 'Save Credential' }).click();
|
||||
await page.getByRole('button', { name: 'Modal Close Button' }).click();
|
||||
await page.getByRole('button', { name: 'Create new Project' }).click();
|
||||
await page.getByLabel('Project Type Item: git').click();
|
||||
await expect.soft(page.getByLabel('Git Sync Feature Disabled Banner')).toBeVisible();
|
||||
|
||||
await expect.soft(page.getByLabel('Git Setup Form')).toBeHidden();
|
||||
await expect.soft(page.getByRole('button', { name: 'Scan for files' })).toBeDisabled();
|
||||
});
|
||||
test.beforeEach(async ({ insomnia, request }) => {
|
||||
await request.post('http://127.0.0.1:4010/v1/test-utils/git/setup');
|
||||
await addAccessTokenGitCredential(insomnia);
|
||||
await insomnia.projectPage.createGitSyncProject();
|
||||
});
|
||||
|
||||
test.describe('with git storage rule disabled', () => {
|
||||
test.beforeEach(async ({ request }) => {
|
||||
// Set storage rule to disable git sync
|
||||
await request.post('http://127.0.0.1:4010/v1/test-utils/organizations/storage-rule', {
|
||||
data: {
|
||||
enableCloudSync: true,
|
||||
enableGitSync: false,
|
||||
enableLocalVault: true,
|
||||
isOverridden: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
test.afterEach(async ({ request }) => {
|
||||
await request.delete('http://127.0.0.1:4010/v1/test-utils/git/setup');
|
||||
});
|
||||
|
||||
test.afterEach(async ({ request }) => {
|
||||
// reset the storage rule after test
|
||||
await request.post('http://127.0.0.1:4010/v1/test-utils/organizations/storage-rule', {
|
||||
data: {
|
||||
enableCloudSync: true,
|
||||
enableGitSync: true,
|
||||
enableLocalVault: true,
|
||||
isOverridden: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
// Creates a git sync project, opens the Branches modal, creates "branch1",
|
||||
// and verifies the active branch switches to branch1.
|
||||
test('Create new branch and switch to it', async ({ page }) => {
|
||||
await page.getByTestId('git-dropdown').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Branches' }).click();
|
||||
await page.getByRole('textbox', { name: 'New branch name:' }).click();
|
||||
await page.getByRole('textbox', { name: 'New branch name:' }).fill('branch1');
|
||||
await page.getByRole('button', { name: 'Create', exact: true }).click();
|
||||
await expect.soft(page.getByText('branch1 *')).toBeVisible();
|
||||
});
|
||||
|
||||
test('disable git sync selection', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Create new Project' }).click();
|
||||
const banner = page.getByLabel('Project Storage Restriction Banner');
|
||||
await expect.soft(banner).toBeVisible();
|
||||
await expect.soft(banner).not.toHaveText('Git Sync');
|
||||
await expect.soft(page.getByLabel('Project Type: git')).toBeDisabled();
|
||||
});
|
||||
// Creates a collection to produce an unstaged change, stages it, commits with message "1",
|
||||
// then opens History and verifies the commit appears in the log.
|
||||
test('Commit and check history', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'New request collection' }).click();
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).click();
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).press('ControlOrMeta+a');
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).fill('Collection 1');
|
||||
await page.getByRole('textbox', { name: 'File name my_collection' }).click();
|
||||
await page.getByRole('textbox', { name: 'File name my_collection' }).press('ControlOrMeta+a');
|
||||
await page.getByRole('textbox', { name: 'File name my_collection' }).fill('collection_1');
|
||||
await page.getByRole('button', { name: 'Create', exact: true }).click();
|
||||
await page.getByTestId('git-dropdown').click();
|
||||
await expect.soft(page.getByRole('menuitemradio', { name: 'Commit' })).toBeVisible();
|
||||
await page.getByRole('menuitemradio', { name: 'Commit' }).click();
|
||||
await expect.soft(page.getByLabel('Unstaged changes').locator('span')).toContainText('collection_1.yaml');
|
||||
|
||||
await page.locator('button[name="Stage all changes"]').click();
|
||||
await page.getByRole('textbox', { name: 'Message' }).click();
|
||||
await page.getByRole('textbox', { name: 'Message' }).fill('1');
|
||||
await page.getByRole('button', { name: 'Commit', exact: true }).click();
|
||||
await page.getByTestId('git-dropdown').click();
|
||||
await page.getByText('History').click();
|
||||
await expect.soft(page.getByLabel('1', { exact: true }).getByRole('rowheader')).toContainText('1');
|
||||
});
|
||||
|
||||
// Creates branch1, commits a new collection on it, switches back to master,
|
||||
// merges branch1 into master, and verifies the collection is visible on master.
|
||||
test('Merge branch and verify changes on the other branch has been merged into current branch', async ({ page }) => {
|
||||
await page.getByTestId('git-dropdown').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Branches' }).click();
|
||||
await page.getByRole('textbox', { name: 'New branch name:' }).click();
|
||||
await page.getByRole('textbox', { name: 'New branch name:' }).fill('branch1');
|
||||
await page.getByRole('button', { name: 'Create', exact: true }).click();
|
||||
await expect.soft(page.getByText('branch1 *')).toBeVisible();
|
||||
await page.getByTestId('close-git-project-branches-modal').click();
|
||||
await page.getByTestId('git-project-branches-modal-overlay').waitFor({ state: 'hidden' });
|
||||
await page.getByRole('button', { name: 'New request collection' }).click();
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).click();
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).press('ControlOrMeta+a');
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).fill('collection 1');
|
||||
await page.getByRole('textbox', { name: 'File name my_collection' }).click();
|
||||
await page.getByRole('textbox', { name: 'File name my_collection' }).press('ControlOrMeta+a');
|
||||
await page.getByRole('textbox', { name: 'File name my_collection' }).fill('collection_1');
|
||||
await page.getByRole('button', { name: 'Create', exact: true }).click();
|
||||
await page.getByTestId('project').click();
|
||||
await page.getByTestId('git-dropdown').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Commit' }).click();
|
||||
await page.locator('button[name="Stage all changes"]').click();
|
||||
await page.getByRole('textbox', { name: 'Message' }).click();
|
||||
await page.getByRole('textbox', { name: 'Message' }).fill('commit 1');
|
||||
await page.getByRole('button', { name: 'Commit', exact: true }).click();
|
||||
await page.getByTestId('git-dropdown').click();
|
||||
await page.getByRole('menuitemradio', { name: 'master' }).click();
|
||||
await page.locator('html').click();
|
||||
await page.getByTestId('git-dropdown').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Branches' }).click();
|
||||
await page.getByLabel('branch1').getByRole('button', { name: 'Merge' }).click();
|
||||
await page.getByRole('button', { name: ' Confirm' }).click();
|
||||
await page.getByTestId('close-git-project-branches-modal').click();
|
||||
await page.getByTestId('git-project-branches-modal-overlay').waitFor({ state: 'hidden' });
|
||||
await expect.soft(page.getByText('collection 1')).toBeVisible();
|
||||
});
|
||||
|
||||
// Creates a collection, commits it, then pushes to the remote git server.
|
||||
// Verifies the "Push completed" toast appears, confirming a successful push.
|
||||
test('Push committed changes to remote', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'New request collection' }).click();
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).click();
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).press('ControlOrMeta+a');
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).fill('Push Test Collection');
|
||||
await page.getByRole('textbox', { name: 'File name my_collection' }).click();
|
||||
await page.getByRole('textbox', { name: 'File name my_collection' }).press('ControlOrMeta+a');
|
||||
await page.getByRole('textbox', { name: 'File name my_collection' }).fill('push_test_collection');
|
||||
await page.getByRole('button', { name: 'Create', exact: true }).click();
|
||||
|
||||
await page.getByTestId('git-dropdown').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Commit' }).click();
|
||||
await page.locator('button[name="Stage all changes"]').click();
|
||||
await page.getByRole('textbox', { name: 'Message' }).fill('push test commit');
|
||||
await page.getByRole('button', { name: 'Commit', exact: true }).click();
|
||||
|
||||
await page.getByTestId('git-dropdown').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Push' }).click();
|
||||
|
||||
await expect.soft(page.getByText('Push completed')).toBeVisible();
|
||||
});
|
||||
|
||||
// Creates "branch-to-delete", checks out master, then deletes the branch via the
|
||||
// two-step PromptButton (Delete → Confirm). Verifies the branch is removed from the list.
|
||||
test('Delete a branch', async ({ page }) => {
|
||||
await page.getByTestId('git-dropdown').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Branches' }).click();
|
||||
await page.getByRole('textbox', { name: 'New branch name:' }).click();
|
||||
await page.getByRole('textbox', { name: 'New branch name:' }).fill('branch-to-delete');
|
||||
await page.getByRole('button', { name: 'Create', exact: true }).click();
|
||||
await expect.soft(page.getByText('branch-to-delete *')).toBeVisible();
|
||||
|
||||
await page.getByRole('row', { name: 'master' }).getByRole('button', { name: 'Checkout' }).click();
|
||||
await expect.soft(page.getByText('master *')).toBeVisible();
|
||||
|
||||
await page.getByRole('row', { name: 'branch-to-delete' }).getByRole('button', { name: 'Delete' }).click();
|
||||
await page.getByRole('row', { name: 'branch-to-delete' }).getByRole('button', { name: 'Confirm' }).click();
|
||||
|
||||
await expect.soft(page.getByRole('row', { name: 'branch-to-delete' })).toBeHidden();
|
||||
|
||||
await page.getByTestId('close-git-project-branches-modal').click();
|
||||
await page.getByTestId('git-project-branches-modal-overlay').waitFor({ state: 'hidden' });
|
||||
await expect.soft(page.getByTestId('git-dropdown')).toContainText('master');
|
||||
});
|
||||
|
||||
// Creates a collection to produce an unstaged change, opens the staging modal,
|
||||
// clicks "Discard all changes" and confirms. Verifies the modal auto-closes,
|
||||
// indicating all changes were discarded.
|
||||
test('Discard all unstaged changes', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'New request collection' }).click();
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).click();
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).press('ControlOrMeta+a');
|
||||
await page.getByRole('textbox', { name: 'Name', exact: true }).fill('Discard Test Collection');
|
||||
await page.getByRole('textbox', { name: 'File name my_collection' }).click();
|
||||
await page.getByRole('textbox', { name: 'File name my_collection' }).press('ControlOrMeta+a');
|
||||
await page.getByRole('textbox', { name: 'File name my_collection' }).fill('discard_test_collection');
|
||||
await page.getByRole('button', { name: 'Create', exact: true }).click();
|
||||
|
||||
await page.getByTestId('git-dropdown').click();
|
||||
await page.getByRole('menuitemradio', { name: 'Commit' }).click();
|
||||
await expect.soft(page.getByLabel('Unstaged changes').locator('span')).toContainText('discard_test_collection.yaml');
|
||||
|
||||
await page.locator('button[name="Discard all changes"]').click();
|
||||
await page.getByTestId('discard-changes-confirm-button').click();
|
||||
|
||||
// After discarding all changes the staging modal auto-closes
|
||||
await expect.soft(page.getByLabel('Unstaged changes')).toBeHidden();
|
||||
});
|
||||
});
|
||||
|
||||
async function addAccessTokenGitCredential(insomnia: InsomniaApp) {
|
||||
await insomnia.statusbar.openPreferences();
|
||||
await insomnia.preferencesPage.switchToPreferenceTab('Credentials');
|
||||
await insomnia.preferencesPage.credentialsTab.addAccessTokenGitCredential();
|
||||
await expect.soft(insomnia.page.getByRole('row', { name: 'Custom Git Credential' })).toBeVisible();
|
||||
await insomnia.preferencesPage.closePreferences();
|
||||
}
|
||||
|
||||
40
packages/insomnia-smoke-test/tests/smoke/ipv6.test.ts
Normal file
40
packages/insomnia-smoke-test/tests/smoke/ipv6.test.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { expect } from '@playwright/test';
|
||||
|
||||
import { test } from '../../playwright/test';
|
||||
|
||||
test('can send ipv6 requests', async ({ page, insomnia }) => {
|
||||
test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms');
|
||||
|
||||
const statusTag = page.locator('[data-testid="response-status-tag"]:visible');
|
||||
const responseBody = page.getByTestId('response-pane');
|
||||
|
||||
await insomnia.projectPage.importFixture('ipv6-collection.yaml');
|
||||
|
||||
await page.getByLabel('Request Collection')
|
||||
.getByTestId('send JSON request').press('Enter');
|
||||
await expect.soft(page.getByTestId('request-pane')
|
||||
.getByTestId('OneLineEditor')
|
||||
.getByText('http://[::1]:4010/pets/1')
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByTestId('request-pane')
|
||||
.getByRole('button', { name: 'Send' }).click();
|
||||
await expect.soft(statusTag).toContainText('200 OK');
|
||||
await expect.soft(responseBody).toContainText('"id": "1"');
|
||||
|
||||
await page
|
||||
.getByLabel('Request Collection')
|
||||
.getByTestId('sends dummy.csv request and shows rich response')
|
||||
.press('Enter');
|
||||
await expect.soft(page.getByTestId('request-pane')
|
||||
.getByTestId('OneLineEditor')
|
||||
.getByText('http://[::1]:4010/file/dummy.csv')
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByTestId('request-pane')
|
||||
.getByRole('button', { name: 'Send' }).click();
|
||||
await expect.soft(statusTag).toContainText('200 OK');
|
||||
await page.getByRole('button', { name: 'Preview' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Raw Data' }).click();
|
||||
await expect.soft(responseBody).toContainText('a,b,c');
|
||||
});
|
||||
@@ -208,6 +208,7 @@ test.describe('pre-request features tests', () => {
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
test('run test cases', async ({ page }) => {
|
||||
for (const tc of testCases) {
|
||||
console.log(`Running test case: ${tc.name}`);
|
||||
@@ -230,6 +231,7 @@ test.describe('pre-request features tests', () => {
|
||||
tc.customVerify(bodyJson);
|
||||
}
|
||||
});
|
||||
|
||||
test('send request with content type', async ({ page }) => {
|
||||
await page.getByTestId('settings-button').click();
|
||||
await page.getByTestId('dataFolders').click();
|
||||
@@ -660,14 +662,15 @@ test.describe('unhappy paths', () => {
|
||||
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
|
||||
|
||||
// verify
|
||||
await expect.soft(page.getByTestId('response-pane')).toContainText('my custom error');
|
||||
await expect.soft(page.getByTestId('response-pane')).toContainText(`my custom error`);
|
||||
|
||||
await page.getByRole('tab', { name: 'Scripts' }).click();
|
||||
await page.getByTestId('CodeEditor').getByRole('textbox').press('ControlOrMeta+a');
|
||||
await page.keyboard.press('Backspace');
|
||||
await editor.fill(`insomnia.INVALID_FIELD.set('', '')`);
|
||||
|
||||
await page.getByRole('tab', { name: 'Body' }).click();
|
||||
// CodeMirror debounces onChange by DEBOUNCE_MILLIS (100ms).
|
||||
await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 150)));
|
||||
|
||||
// send
|
||||
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
|
||||
@@ -678,3 +681,165 @@ test.describe('unhappy paths', () => {
|
||||
.toContainText(`Cannot read properties of undefined (reading 'set')`);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('sandbox features', () => {
|
||||
test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms');
|
||||
|
||||
test.beforeEach(async ({ app, page }) => {
|
||||
const text = await loadFixture('pre-request-collection.yaml');
|
||||
await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text);
|
||||
|
||||
await page.getByLabel('Import').click();
|
||||
await page.locator('[data-test-id="import-from-clipboard"]').click();
|
||||
await page.getByRole('button', { name: 'Scan' }).click();
|
||||
await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click();
|
||||
});
|
||||
|
||||
// Blocked Roots / Scopes group: 'this' is blocked.
|
||||
test('blocked roots / scopes group', async ({ page }) => {
|
||||
await page.getByLabel('Request Collection').getByTestId('echo pre-request script result').press('Enter');
|
||||
|
||||
await page.getByRole('tab', { name: 'Scripts' }).click();
|
||||
const editor = page.getByTestId('CodeEditor').getByRole('textbox');
|
||||
|
||||
// enter script that accesses a property on 'this'.
|
||||
await editor.fill(`insomnia.environment.set('result', String(this?.process));`);
|
||||
|
||||
// CodeMirror debounces onChange by DEBOUNCE_MILLIS (100ms).
|
||||
await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 150)));
|
||||
|
||||
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
|
||||
|
||||
// verify blocked-root error
|
||||
await expect
|
||||
.soft(page.getByTestId('response-pane'))
|
||||
.toContainText("The script was blocked because it used 'this'.");
|
||||
|
||||
// navigate to Settings → Scripting, disable the "Scopes" blocked roots group
|
||||
await page.getByTestId('settings-button').click();
|
||||
await page.locator('text=Insomnia Preferences').first().click();
|
||||
await page.getByRole('tab', { name: 'Scripting' }).click();
|
||||
const scopesSwitch = page.locator('div:has(> h4:has-text("Scopes")) label[data-react-aria-pressable]');
|
||||
await scopesSwitch.scrollIntoViewIfNeeded();
|
||||
await scopesSwitch.click();
|
||||
|
||||
await page.locator('.app').press('Escape');
|
||||
|
||||
// re-send — no sandbox error; this === undefined.
|
||||
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
|
||||
await expect
|
||||
.soft(page.getByTestId('response-pane'))
|
||||
.not.toContainText("The script was blocked because it used 'this'.");
|
||||
await expect.soft(page.locator('[data-testid="response-status-tag"]:visible')).toContainText('200 OK');
|
||||
});
|
||||
|
||||
// Blocked Properties / Prototype Mutation group: 'prototype' is blocked.
|
||||
test('blocked properties / prototype mutation group', async ({ page }) => {
|
||||
await page.getByLabel('Request Collection').getByTestId('echo pre-request script result').press('Enter');
|
||||
|
||||
// enter script that accesses Object.prototype.
|
||||
await page.getByRole('tab', { name: 'Scripts' }).click();
|
||||
const editor = page.getByTestId('CodeEditor').getByRole('textbox');
|
||||
await editor.fill(`insomnia.environment.set('result', typeof Object.prototype.toString);`);
|
||||
|
||||
// CodeMirror debounces onChange by DEBOUNCE_MILLIS (100ms).
|
||||
await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 150)));
|
||||
|
||||
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
|
||||
|
||||
// verify blocked-property error
|
||||
await expect
|
||||
.soft(page.getByTestId('response-pane'))
|
||||
.toContainText("The script was blocked because it used the property 'prototype'.");
|
||||
|
||||
// navigate to Settings → Scripting, disable the "Prototype Mutation" blocked properties group
|
||||
await page.getByTestId('settings-button').click();
|
||||
await page.locator('text=Insomnia Preferences').first().click();
|
||||
await page.getByRole('tab', { name: 'Scripting' }).click();
|
||||
const protoMutationSwitch = page.locator(
|
||||
'div:has(> h4:has-text("Prototype Mutation")) label[data-react-aria-pressable]',
|
||||
);
|
||||
await protoMutationSwitch.scrollIntoViewIfNeeded();
|
||||
await protoMutationSwitch.click();
|
||||
await expect.soft(protoMutationSwitch).not.toHaveAttribute('data-selected');
|
||||
await page.locator('.app').press('Escape');
|
||||
|
||||
// re-send — prototype access now allowed; Object.prototype.toString is a function
|
||||
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
|
||||
await expect
|
||||
.soft(page.getByTestId('response-pane'))
|
||||
.not.toContainText("The script was blocked because it used the property 'prototype'.");
|
||||
await expect.soft(page.locator('[data-testid="response-status-tag"]:visible')).toContainText('200 OK');
|
||||
});
|
||||
|
||||
// Mask Rules / Runtime APIs group: 'Function' is masked to undefined at runtime.
|
||||
test('Mask Rules / Runtime APIs group.', async ({ page }) => {
|
||||
await page.getByLabel('Request Collection').getByTestId('echo pre-request script result').press('Enter');
|
||||
|
||||
// enter script that uses the Function constructor, only masked at runtime.
|
||||
await page.getByRole('tab', { name: 'Scripts' }).click();
|
||||
const editor = page.getByTestId('CodeEditor').getByRole('textbox');
|
||||
await editor.fill(`const f = new Function('return 42'); insomnia.environment.set('result', f());`);
|
||||
|
||||
// CodeMirror debounces onChange by DEBOUNCE_MILLIS (100ms).
|
||||
await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 150)));
|
||||
|
||||
// send — Function masked to undefined → V8 uses the identifier name: "Function is not a constructor"
|
||||
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
|
||||
|
||||
await expect.soft(page.getByTestId('response-pane')).toContainText('Function is not a constructor');
|
||||
|
||||
// navigate to Settings → Scripting, disable the "Runtime APIs" mask group
|
||||
await page.getByTestId('settings-button').click();
|
||||
await page.locator('text=Insomnia Preferences').first().click();
|
||||
await page.getByRole('tab', { name: 'Scripting' }).click();
|
||||
const runtimeApisSwitch = page.locator('div:has(> h4:has-text("Runtime APIs")) label[data-react-aria-pressable]');
|
||||
await runtimeApisSwitch.scrollIntoViewIfNeeded();
|
||||
await runtimeApisSwitch.click();
|
||||
await expect.soft(runtimeApisSwitch).not.toHaveAttribute('data-selected');
|
||||
await page.locator('.app').press('Escape');
|
||||
|
||||
// re-send — Function is now the real constructor; script returns 42
|
||||
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
|
||||
await expect.soft(page.getByTestId('response-pane')).not.toContainText('Function is not a constructor');
|
||||
await expect.soft(page.locator('[data-testid="response-status-tag"]:visible')).toContainText('200 OK');
|
||||
});
|
||||
|
||||
test('Layered security / unblocked properties resolve undefined', async ({ page }) => {
|
||||
await page.getByLabel('Request Collection').getByTestId('echo pre-request script result').press('Enter');
|
||||
|
||||
// enter script that accesses a property on 'process'.
|
||||
await page.getByRole('tab', { name: 'Scripts' }).click();
|
||||
const editor = page.getByTestId('CodeEditor').getByRole('textbox');
|
||||
await editor.fill(`insomnia.environment.set('result', String(process?.version));`);
|
||||
|
||||
// CodeMirror debounces onChange by DEBOUNCE_MILLIS (100ms).
|
||||
await page.evaluate(() => new Promise(resolve => setTimeout(resolve, 150)));
|
||||
|
||||
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
|
||||
|
||||
// verify blocked-root error
|
||||
await expect
|
||||
.soft(page.getByTestId('response-pane'))
|
||||
.toContainText("The script was blocked because it used 'process'.");
|
||||
|
||||
// navigate to Settings → Scripting, disable only the "Node.js Internals" BLOCKED ROOTS group.
|
||||
await page.getByTestId('settings-button').click();
|
||||
await page.locator('text=Insomnia Preferences').first().click();
|
||||
await page.getByRole('tab', { name: 'Scripting' }).click();
|
||||
const nodeInternalsSwitch = page.locator(
|
||||
'xpath=//h4[normalize-space(text())="Node.js Internals"]/following-sibling::div[1]//label[@data-react-aria-pressable]',
|
||||
);
|
||||
await nodeInternalsSwitch.scrollIntoViewIfNeeded();
|
||||
await nodeInternalsSwitch.click();
|
||||
await expect.soft(nodeInternalsSwitch).not.toHaveAttribute('data-selected');
|
||||
await page.locator('.app').press('Escape');
|
||||
|
||||
// process?.version === undefined.
|
||||
await page.getByTestId('request-pane').getByRole('button', { name: 'Send' }).click();
|
||||
await expect
|
||||
.soft(page.getByTestId('response-pane'))
|
||||
.not.toContainText("The script was blocked because it used 'process'.");
|
||||
await expect.soft(page.locator('[data-testid="response-status-tag"]:visible')).toContainText('200 OK');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
This document breaks the renderer `nodeIntegration: false` migration into deliverable slices that can move in parallel without creating excessive merge conflict risk.
|
||||
|
||||
Update: PR 1 through PR 3 are merged. The remaining work is re-scoped around single feature flows instead of broad subsystem buckets so each PR can land with tighter review, clearer ownership, and faster iteration.
|
||||
|
||||
The plan assumes the current guardrails are already in place:
|
||||
|
||||
- renderer import analyzer in `vite.config.ts`
|
||||
@@ -15,6 +17,9 @@ The plan assumes the current guardrails are already in place:
|
||||
2. If a PR removes offenders, update `config/renderer-node-import-baseline.json` in the same PR.
|
||||
3. Prefer moving privileged behavior behind existing preload or `window.main` APIs before inventing new bridge surface.
|
||||
4. Do not combine route cleanup with subsystem redesign unless the route is blocked on the subsystem boundary.
|
||||
5. Remaining candidates should stay scoped to one user-visible feature flow or one tightly bounded privileged service.
|
||||
6. Each PR should carry an explicit test automation plan before implementation starts.
|
||||
7. If a feature depends on sync or storage behavior, land the sync/storage boundary first instead of mixing the dependency into the same PR.
|
||||
|
||||
## Reviewer Lanes
|
||||
|
||||
@@ -26,23 +31,32 @@ The plan assumes the current guardrails are already in place:
|
||||
|
||||
## Parallelization Summary
|
||||
|
||||
Can start immediately in parallel:
|
||||
Already merged:
|
||||
|
||||
- PR 1: Route path-only cleanup
|
||||
- PR 2: Route fs-backed cleanup
|
||||
- PR 3: Shared browser-safe helper cleanup
|
||||
|
||||
Can start in parallel with the above if staffed separately:
|
||||
Next PR candidates:
|
||||
|
||||
- PR 4: Renderer-to-main boundary extraction
|
||||
- Candidate: Sync storage boundary foundation
|
||||
- Candidate: Import parsing and persistence boundary
|
||||
- Candidate: gRPC proto asset boundary **(quick win candidate)**
|
||||
- Candidate: Plugin discovery boundary
|
||||
- Candidate: Templating bootstrap boundary
|
||||
- Candidate: OAuth token crypto cleanup **(quick win candidate)**
|
||||
- Candidate: Response archival and compression boundary
|
||||
- Candidate: Script executor boundary
|
||||
|
||||
Should usually wait until PR 4 is clear enough to avoid duplicated refactors:
|
||||
Dependencies:
|
||||
|
||||
- PR 5: Network and gRPC boundary pass
|
||||
- PR 6: Sync and storage boundary pass
|
||||
- PR 7: Plugin and templating boundary pass
|
||||
- Import depends on sync storage because import creates or updates persisted workspace state.
|
||||
- Plugin discovery and templating should stay split so one does not become a catch-all runtime PR.
|
||||
- gRPC proto asset work can likely move ahead once the sync-storage bridge pattern is clear.
|
||||
- OAuth cleanup looks like the smallest remaining isolated boundary and is a good candidate for a fast iteration.
|
||||
- Response archival and `script-executor.ts` should stay separate from the feature-scoped candidates unless a later PR proves they are truly part of the same flow.
|
||||
|
||||
## PR Board
|
||||
## Candidate Backlog
|
||||
|
||||
## PR 0: Guardrails and Baseline
|
||||
|
||||
@@ -69,6 +83,8 @@ Suggested reviewers:
|
||||
|
||||
## PR 1: Route Path-Only Cleanup
|
||||
|
||||
Status: merged
|
||||
|
||||
Purpose:
|
||||
|
||||
- Remove route-local `node:path` usage where existing `window.path` is already sufficient.
|
||||
@@ -108,10 +124,12 @@ Concurrent with:
|
||||
|
||||
- PR 2
|
||||
- PR 3
|
||||
- PR 4
|
||||
- later dependency-clearing candidate
|
||||
|
||||
## PR 2: Route FS-Backed Cleanup
|
||||
|
||||
Status: merged
|
||||
|
||||
Purpose:
|
||||
|
||||
- Remove route-level `node:fs` and remaining `node:path` usage that touches downloads or file reads.
|
||||
@@ -153,6 +171,8 @@ Concurrent with:
|
||||
|
||||
## PR 3: Shared Browser-Safe Helper Cleanup
|
||||
|
||||
Status: merged
|
||||
|
||||
Purpose:
|
||||
|
||||
- Remove Node builtin usage from helper modules that should be safe to load in the renderer.
|
||||
@@ -199,35 +219,121 @@ Concurrent with:
|
||||
|
||||
- PR 1
|
||||
- PR 2
|
||||
- PR 4
|
||||
- later dependency-clearing candidate
|
||||
|
||||
## PR 4: Renderer-to-Main Boundary Extraction
|
||||
## Candidate: Sync Storage Boundary Foundation
|
||||
|
||||
Status: candidate
|
||||
|
||||
Purpose:
|
||||
|
||||
- Stop renderer code from pulling `src/main` implementations into the client graph when only pure helpers or types are needed.
|
||||
- Move local project sync storage so renderer code stops owning filesystem, compression, and VCS-path details.
|
||||
|
||||
Single feature scope:
|
||||
|
||||
- Local project persistence for sync-backed workspaces.
|
||||
|
||||
Primary files:
|
||||
|
||||
- `src/sync/store/drivers/file-system-driver.ts`
|
||||
- `src/sync/store/drivers/graceful-rename.ts`
|
||||
- `src/sync/store/hooks/compress.ts`
|
||||
- `src/sync/store/index.ts`
|
||||
- `src/sync/vcs/util.ts`
|
||||
- `src/sync/vcs/vcs.ts`
|
||||
- `src/sync/vcs/create-vcs.ts`
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- Introduce a narrow storage-oriented bridge for read, write, rename, compression, and VCS-adjacent path work.
|
||||
- Avoid mixing import, plugin, or network behavior into this PR.
|
||||
- Move the vcs class entirely to main, simplify it down so its clear what internal state it has, expose its functions over IPC.
|
||||
- create an event listener in the renderer to handle events from the conflictHandler function passed into vcs.
|
||||
|
||||
Expected risk: high
|
||||
|
||||
Quick win: no
|
||||
|
||||
Suggested reviewers:
|
||||
|
||||
- Sync/storage
|
||||
- Electron/runtime
|
||||
|
||||
Baseline entries to remove:
|
||||
|
||||
- `src/sync/store/drivers/file-system-driver.ts -> fs/promises`
|
||||
- `src/sync/store/drivers/file-system-driver.ts -> path`
|
||||
- `src/sync/store/drivers/graceful-rename.ts -> fs/promises`
|
||||
- `src/sync/store/hooks/compress.ts -> zlib`
|
||||
- `src/sync/store/index.ts -> path`
|
||||
- `src/sync/vcs/util.ts -> crypto`
|
||||
- `src/sync/vcs/vcs.ts -> crypto`
|
||||
- `src/sync/vcs/vcs.ts -> path`
|
||||
|
||||
Dependencies:
|
||||
|
||||
- none
|
||||
|
||||
Test automation plan:
|
||||
|
||||
- Extend `src/sync/store/hooks/__tests__/compress.test.ts` and related sync store tests to cover the new privileged boundary behavior.
|
||||
- Add focused unit coverage for any new main-process sync bridge or IPC handlers.
|
||||
- Add a renderer-side contract test that proves sync store calls delegate through the bridge instead of importing Node-backed code directly.
|
||||
- Keep smoke coverage limited to one sync-backed roundtrip so this PR stays quick to iterate.
|
||||
|
||||
Enables:
|
||||
|
||||
- Import parsing and persistence boundary
|
||||
- gRPC proto asset boundary
|
||||
- Plugin discovery boundary
|
||||
- Templating bootstrap boundary
|
||||
|
||||
Out of scope:
|
||||
|
||||
- import parsing or import persistence
|
||||
- gRPC proto temp-file handling
|
||||
- plugin discovery or templating runtime changes
|
||||
|
||||
## Candidate: Import Parsing and Persistence Boundary
|
||||
|
||||
Status: candidate
|
||||
|
||||
Purpose:
|
||||
|
||||
- Make import a self-contained feature flow that relies on the sync boundary instead of importing `src/main` helpers or privileged file access into renderer-reachable modules.
|
||||
|
||||
Single feature scope:
|
||||
|
||||
- Scan, parse, and persist imported resources.
|
||||
|
||||
Primary files:
|
||||
|
||||
- `src/common/import.ts`
|
||||
- `src/routes/import.scan.tsx`
|
||||
- `src/routes/import.resources.tsx`
|
||||
- `src/ui/components/modals/import-modal/import-modal.tsx`
|
||||
- `src/main/importers/convert.ts`
|
||||
- `src/main/importers/importers/curl.ts`
|
||||
- `src/main/importers/importers/openapi-3.ts`
|
||||
- `src/main/importers/importers/swagger-2.ts`
|
||||
- `src/main/network/parse-header-strings.ts`
|
||||
- `src/main/secure-read-file.ts`
|
||||
|
||||
Likely implementation:
|
||||
Implementation notes:
|
||||
|
||||
- Move pure helper logic into shared modules outside `src/main`.
|
||||
- Leave privileged code in `src/main`.
|
||||
- Update renderer imports to target shared modules or types only.
|
||||
- Keep importer execution and privileged file reads behind explicit main-process entrypoints.
|
||||
- Extract pure importer helpers into shared modules only where they are genuinely renderer-safe.
|
||||
- Make the import flow consume the sync/storage candidate instead of mixing storage work into this candidate.
|
||||
|
||||
Expected risk: medium
|
||||
Expected risk: high
|
||||
|
||||
Quick win: no
|
||||
|
||||
Suggested reviewers:
|
||||
|
||||
- Electron/runtime
|
||||
- Router/UI
|
||||
- Network/gRPC
|
||||
- Sync/storage
|
||||
- Electron/runtime
|
||||
|
||||
Baseline entries to remove:
|
||||
|
||||
@@ -242,40 +348,52 @@ Baseline entries to remove:
|
||||
|
||||
Dependencies:
|
||||
|
||||
- none
|
||||
- Sync storage boundary foundation
|
||||
|
||||
Concurrent with:
|
||||
Test automation plan:
|
||||
|
||||
- PR 1
|
||||
- PR 3
|
||||
- Extend `src/common/__tests__/import.test.ts` to cover scan and persist paths after the boundary cleanup.
|
||||
- Add route-level coverage for `src/routes/import.scan.tsx` and `src/routes/import.resources.tsx` where the bridge contract changes.
|
||||
- Keep one UI-level import smoke path for a representative source such as curl or file import.
|
||||
- Update the renderer-node-import baseline in the same PR once the import offenders are removed.
|
||||
|
||||
Blocks or informs:
|
||||
Enables:
|
||||
|
||||
- PR 5
|
||||
- import follow-up polish, if needed
|
||||
|
||||
## PR 5: Network and gRPC Privileged Boundary Pass
|
||||
Out of scope:
|
||||
|
||||
- generic OAuth cleanup
|
||||
- gRPC proto temp files
|
||||
- plugin loading and templating
|
||||
|
||||
## Candidate: gRPC Proto Asset Boundary
|
||||
|
||||
Status: candidate
|
||||
|
||||
Purpose:
|
||||
|
||||
- Move filesystem and privileged request execution concerns behind explicit APIs instead of direct renderer imports.
|
||||
- Isolate the gRPC proto file preparation flow behind a privileged boundary without expanding the PR into broader network or response persistence cleanup.
|
||||
|
||||
Single feature scope:
|
||||
|
||||
- Preparing proto directories and temp proto files for gRPC request execution.
|
||||
|
||||
Primary files:
|
||||
|
||||
- `src/network/network.ts`
|
||||
- `src/network/grpc/proto-directory-loader.tsx`
|
||||
- `src/network/grpc/write-proto-file.ts`
|
||||
- `src/network/o-auth-1/get-token.ts`
|
||||
- `src/network/o-auth-2/get-token.ts`
|
||||
- `src/network/o-auth-2/utils.ts`
|
||||
- `src/network/url-matches-cert-host.ts`
|
||||
- `src/models/helpers/response-operations.ts`
|
||||
- any minimal preload or IPC additions needed to support the proto write path
|
||||
|
||||
Likely implementation:
|
||||
Implementation notes:
|
||||
|
||||
- Separate pure parsing and formatting code from file/crypto operations.
|
||||
- Push file reads, writes, temp file creation, and request execution details behind `window.main` or a dedicated network bridge.
|
||||
- Move proto temp-file creation and path work to main.
|
||||
- Keep request assembly in the renderer, but remove direct `fs`, `os`, and `path` ownership from the gRPC preparation path.
|
||||
- Do not mix in generic request execution, OAuth, or response archival work.
|
||||
|
||||
Expected risk: high
|
||||
Expected risk: medium
|
||||
|
||||
Quick win: yes
|
||||
|
||||
Suggested reviewers:
|
||||
|
||||
@@ -289,79 +407,34 @@ Baseline entries to remove:
|
||||
- `src/network/grpc/write-proto-file.ts -> fs`
|
||||
- `src/network/grpc/write-proto-file.ts -> os`
|
||||
- `src/network/grpc/write-proto-file.ts -> path`
|
||||
- `src/network/network.ts -> fs`
|
||||
- `src/network/network.ts -> path`
|
||||
- `src/network/o-auth-1/get-token.ts -> crypto`
|
||||
- `src/network/o-auth-2/get-token.ts -> crypto`
|
||||
- `src/network/o-auth-2/get-token.ts -> querystring`
|
||||
- `src/network/o-auth-2/utils.ts -> crypto`
|
||||
- `src/network/url-matches-cert-host.ts -> url`
|
||||
- `src/models/helpers/response-operations.ts -> fs`
|
||||
- `src/models/helpers/response-operations.ts -> zlib`
|
||||
|
||||
Dependencies:
|
||||
|
||||
- recommended after PR 4
|
||||
- Sync storage boundary foundation patterns should be available first
|
||||
|
||||
Concurrent with:
|
||||
Test automation plan:
|
||||
|
||||
- PR 6
|
||||
- PR 7
|
||||
- Extend or relocate unit coverage for `write-proto-file` so the privileged file-write path stays directly tested.
|
||||
- Add a contract test proving the renderer gRPC flow uses the new bridge instead of direct Node imports.
|
||||
- Keep one gRPC-focused smoke or integration path that confirms proto-backed requests still resolve correctly.
|
||||
|
||||
## PR 6: Sync and Storage Boundary Pass
|
||||
Out of scope:
|
||||
|
||||
- OAuth token helpers
|
||||
- generic network file IO
|
||||
- response body archival helpers
|
||||
|
||||
## Candidate: Plugin Discovery Boundary
|
||||
|
||||
Status: candidate
|
||||
|
||||
Purpose:
|
||||
|
||||
- Isolate local project storage, sync, compression, and file rename flows behind explicit privileged services.
|
||||
- Move plugin discovery and plugin file access onto a privileged boundary without coupling the work to templating bootstrap or unrelated runtime refactors.
|
||||
|
||||
Primary files:
|
||||
Single feature scope:
|
||||
|
||||
- `src/sync/store/drivers/file-system-driver.ts`
|
||||
- `src/sync/store/drivers/graceful-rename.ts`
|
||||
- `src/sync/store/hooks/compress.ts`
|
||||
- `src/sync/store/index.ts`
|
||||
- `src/sync/vcs/util.ts`
|
||||
- `src/sync/vcs/vcs.ts`
|
||||
- `src/script-executor.ts`
|
||||
|
||||
Likely implementation:
|
||||
|
||||
- Move filesystem operations and compression into a storage backend boundary.
|
||||
- Keep renderer-facing sync orchestration on the safe side of that boundary.
|
||||
|
||||
Expected risk: high
|
||||
|
||||
Suggested reviewers:
|
||||
|
||||
- Sync/storage
|
||||
- Electron/runtime
|
||||
|
||||
Baseline entries to remove:
|
||||
|
||||
- `src/script-executor.ts -> fs/promises`
|
||||
- `src/sync/store/drivers/file-system-driver.ts -> fs/promises`
|
||||
- `src/sync/store/drivers/file-system-driver.ts -> path`
|
||||
- `src/sync/store/drivers/graceful-rename.ts -> fs/promises`
|
||||
- `src/sync/store/hooks/compress.ts -> zlib`
|
||||
- `src/sync/store/index.ts -> path`
|
||||
- `src/sync/vcs/util.ts -> crypto`
|
||||
- `src/sync/vcs/vcs.ts -> crypto`
|
||||
- `src/sync/vcs/vcs.ts -> path`
|
||||
|
||||
Dependencies:
|
||||
|
||||
- none, though shared boundary patterns from PR 4 help
|
||||
|
||||
Concurrent with:
|
||||
|
||||
- PR 5
|
||||
- PR 7
|
||||
|
||||
## PR 7: Plugin and Templating Boundary Pass
|
||||
|
||||
Purpose:
|
||||
|
||||
- Redesign plugin and templating runtime boundaries so privileged module loading and filesystem traversal do not live in renderer-reachable code.
|
||||
- Plugin discovery, metadata loading, and plugin file access.
|
||||
|
||||
Primary files:
|
||||
|
||||
@@ -369,16 +442,18 @@ Primary files:
|
||||
- `src/plugins/create.ts`
|
||||
- `src/plugins/index.ts`
|
||||
- `src/utils/plugin.ts`
|
||||
- `src/templating/base-extension.ts`
|
||||
- any preload or IPC additions needed for plugin discovery and metadata handoff
|
||||
|
||||
Likely implementation:
|
||||
Implementation notes:
|
||||
|
||||
- Decide what plugin discovery and loading must remain privileged.
|
||||
- Extract pure metadata and UI-facing types from runtime loading logic.
|
||||
- Push filesystem-backed plugin operations behind explicit APIs.
|
||||
- Keep plugin metadata and UI-facing types renderer-safe.
|
||||
- Move plugin discovery and plugin file traversal to main.
|
||||
- Avoid adding templating bootstrap, plugin install, package management, or unrelated runtime redesign to this candidate.
|
||||
|
||||
Expected risk: high
|
||||
|
||||
Quick win: no
|
||||
|
||||
Suggested reviewers:
|
||||
|
||||
- Plugins/templating
|
||||
@@ -393,19 +468,237 @@ Baseline entries to remove:
|
||||
- `src/plugins/index.ts -> path`
|
||||
- `src/utils/plugin.ts -> fs`
|
||||
- `src/utils/plugin.ts -> path`
|
||||
|
||||
Dependencies:
|
||||
|
||||
- Sync storage boundary foundation should land first
|
||||
- Prefer after import and gRPC candidate patterns are established so plugin work can reuse the same bridge shape
|
||||
|
||||
Test automation plan:
|
||||
|
||||
- Extend plugin discovery or plugin creation unit tests around the new privileged entrypoints.
|
||||
- Add a renderer contract test for plugin metadata loading so the UI path remains fast and explicit.
|
||||
- Keep one plugin-focused smoke path to prove discovery still works end to end.
|
||||
|
||||
Out of scope:
|
||||
|
||||
- templating bootstrap
|
||||
- package installation and lifecycle management
|
||||
- generic network cleanup
|
||||
- import persistence changes
|
||||
|
||||
## Candidate: Templating Bootstrap Boundary
|
||||
|
||||
Status: candidate
|
||||
|
||||
Purpose:
|
||||
|
||||
- Move templating bootstrap helpers off the renderer path without coupling the work to plugin discovery or broader templating redesign.
|
||||
|
||||
Single feature scope:
|
||||
|
||||
- Templating startup and privileged helper access.
|
||||
|
||||
Primary files:
|
||||
|
||||
- `src/templating/base-extension.ts`
|
||||
- any preload or IPC additions needed for templating bootstrap helpers
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- Keep template evaluation behavior unchanged while moving privileged helper access behind explicit main-process entrypoints.
|
||||
- Avoid folding plugin discovery or plugin package management into this candidate.
|
||||
- Prefer to reuse any plugin-side metadata bridge rather than inventing parallel surfaces if the contracts line up cleanly.
|
||||
|
||||
Expected risk: medium
|
||||
|
||||
Quick win: maybe
|
||||
|
||||
Suggested reviewers:
|
||||
|
||||
- Plugins/templating
|
||||
- Electron/runtime
|
||||
|
||||
Baseline entries to remove:
|
||||
|
||||
- `src/templating/base-extension.ts -> crypto`
|
||||
- `src/templating/base-extension.ts -> os`
|
||||
|
||||
Dependencies:
|
||||
|
||||
- none, but coordinate if PR 5 touches shared templating execution
|
||||
- Prefer after plugin discovery boundary if templating still relies on shared plugin bootstrap behavior
|
||||
|
||||
Concurrent with:
|
||||
Test automation plan:
|
||||
|
||||
- PR 5
|
||||
- PR 6
|
||||
- Extend templating-focused unit tests around the new privileged entrypoints.
|
||||
- Add a renderer contract test for templating bootstrap helpers.
|
||||
- Keep one templating smoke path to prove the startup flow still works end to end.
|
||||
|
||||
## PR 8: Baseline Ratchet Follow-Ups
|
||||
Out of scope:
|
||||
|
||||
- plugin discovery
|
||||
- package installation and lifecycle management
|
||||
- generic network cleanup
|
||||
|
||||
## Candidate: OAuth Token Crypto Cleanup
|
||||
|
||||
Status: candidate
|
||||
|
||||
Purpose:
|
||||
|
||||
- Remove isolated OAuth crypto usage from renderer-reachable code without pulling in unrelated network or storage work.
|
||||
|
||||
Single feature scope:
|
||||
|
||||
- OAuth token helper crypto operations.
|
||||
|
||||
Primary files:
|
||||
|
||||
- `src/network/o-auth-1/get-token.ts`
|
||||
- `src/network/o-auth-2/get-token.ts`
|
||||
- `src/network/o-auth-2/utils.ts`
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- Treat this as a small privileged-helper cleanup, not a broad request-execution refactor.
|
||||
- Prefer a narrow bridge for hashing, PKCE, and token helper crypto rather than a generic network bridge.
|
||||
- Keep request sending and response handling out of scope.
|
||||
|
||||
Expected risk: low to medium
|
||||
|
||||
Quick win: yes
|
||||
|
||||
Suggested reviewers:
|
||||
|
||||
- Network/gRPC
|
||||
- Electron/runtime
|
||||
|
||||
Baseline entries to remove:
|
||||
|
||||
- `src/network/o-auth-1/get-token.ts -> crypto`
|
||||
- `src/network/o-auth-2/get-token.ts -> crypto`
|
||||
- `src/network/o-auth-2/utils.ts -> crypto`
|
||||
|
||||
Dependencies:
|
||||
|
||||
- none
|
||||
|
||||
Test automation plan:
|
||||
|
||||
- Extend OAuth unit tests around PKCE and token helper behavior.
|
||||
- Add a renderer contract test for any new crypto bridge surface.
|
||||
- Keep this candidate free of smoke-test expansion unless an existing auth smoke path already covers the flow.
|
||||
|
||||
Out of scope:
|
||||
|
||||
- generic request execution
|
||||
- response archival
|
||||
- import or sync persistence
|
||||
|
||||
## Candidate: Response Archival and Compression Boundary
|
||||
|
||||
Status: candidate
|
||||
|
||||
Purpose:
|
||||
|
||||
- Isolate response body archival and compression so renderer-safe helpers stop owning file and compression concerns.
|
||||
|
||||
Single feature scope:
|
||||
|
||||
- Response archival, file writes, and compression helpers.
|
||||
|
||||
Primary files:
|
||||
|
||||
- `src/models/helpers/response-operations.ts`
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- Split pure response metadata shaping from privileged file-write and compression behavior.
|
||||
- Avoid bundling this candidate with sync compression just because both touch compression.
|
||||
- Reuse existing preload APIs if they are already close to the needed shape.
|
||||
|
||||
Expected risk: medium
|
||||
|
||||
Quick win: maybe
|
||||
|
||||
Suggested reviewers:
|
||||
|
||||
- Network/gRPC
|
||||
- Electron/runtime
|
||||
|
||||
Baseline entries to remove:
|
||||
|
||||
- `src/models/helpers/response-operations.ts -> fs`
|
||||
- `src/models/helpers/response-operations.ts -> zlib`
|
||||
|
||||
Dependencies:
|
||||
|
||||
- none, though sync-storage patterns may provide a good template
|
||||
|
||||
Test automation plan:
|
||||
|
||||
- Extend unit tests for response archival helpers around the boundary split.
|
||||
- Add a renderer contract test for any file-write bridge used by response exports.
|
||||
- Keep one focused integration path for exporting or persisting a response body.
|
||||
|
||||
Out of scope:
|
||||
|
||||
- sync store compression
|
||||
- generic request execution
|
||||
- plugin or templating work
|
||||
|
||||
## Candidate: Script Executor Boundary
|
||||
|
||||
Status: candidate
|
||||
|
||||
Purpose:
|
||||
|
||||
- Move `script-executor.ts` file access behind a privileged boundary without broadening the work into general scripting or plugin changes.
|
||||
|
||||
Single feature scope:
|
||||
|
||||
- Script execution file access.
|
||||
|
||||
Primary files:
|
||||
|
||||
- `src/script-executor.ts`
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- Keep the candidate narrowly focused on the file-access boundary.
|
||||
- Do not combine this with plugin loading or broader execution-runtime redesign.
|
||||
- Reuse existing bridge patterns from sync or response file operations where possible.
|
||||
|
||||
Expected risk: medium
|
||||
|
||||
Quick win: maybe
|
||||
|
||||
Suggested reviewers:
|
||||
|
||||
- Electron/runtime
|
||||
- Sync/storage
|
||||
|
||||
Baseline entries to remove:
|
||||
|
||||
- `src/script-executor.ts -> fs/promises`
|
||||
|
||||
Dependencies:
|
||||
|
||||
- none
|
||||
|
||||
Test automation plan:
|
||||
|
||||
- Extend targeted script-executor tests around the file-access boundary.
|
||||
- Add a renderer contract test if a new bridge surface is introduced.
|
||||
- Avoid growing this into a smoke-heavy candidate unless an existing script-execution smoke path already exists.
|
||||
|
||||
Out of scope:
|
||||
|
||||
- plugin runtime redesign
|
||||
- sync store persistence
|
||||
- response archival
|
||||
|
||||
## Candidate: Baseline Ratchet Follow-Ups
|
||||
|
||||
Purpose:
|
||||
|
||||
@@ -416,7 +709,7 @@ Primary files:
|
||||
- `config/renderer-node-import-baseline.json`
|
||||
- `.reports/renderer-node-imports.json`
|
||||
|
||||
Likely implementation:
|
||||
Implementation notes:
|
||||
|
||||
- Re-run `npm run update:renderer-node-import-baseline` after each offender-removing PR.
|
||||
- Confirm the baseline only drops entries already removed from the analyzer output.
|
||||
@@ -435,29 +728,35 @@ Concurrent with:
|
||||
|
||||
- usually folded into the offender-removing PR rather than done separately
|
||||
|
||||
## Suggested Merge Order
|
||||
## Suggested Candidate Sequencing
|
||||
|
||||
1. PR 1
|
||||
2. PR 2
|
||||
3. PR 3
|
||||
4. PR 4
|
||||
5. PR 5
|
||||
6. PR 6
|
||||
7. PR 7
|
||||
1. Sync storage boundary foundation
|
||||
2. Import parsing and persistence boundary
|
||||
3. gRPC proto asset boundary
|
||||
4. Plugin discovery boundary
|
||||
5. Templating bootstrap boundary
|
||||
6. OAuth token crypto cleanup
|
||||
7. Response archival and compression boundary
|
||||
8. Script executor boundary
|
||||
|
||||
Notes:
|
||||
|
||||
- PR 1, PR 2, and PR 3 should produce the fastest visible reduction in route and helper debt.
|
||||
- PR 4 is the best candidate for early architectural work because it reduces follow-on churn in PR 5 through PR 7.
|
||||
- PR 5 through PR 7 are intentionally split by subsystem so they can be assigned to different owners.
|
||||
- Sync storage remains the dependency-clearing candidate because import should not move until sync storage is on the privileged side of the boundary.
|
||||
- gRPC proto asset work and OAuth token crypto cleanup look like the clearest quick-win candidates.
|
||||
- Plugin discovery and templating are intentionally separate candidates. If they naturally converge later, that should be a conscious decision rather than the default plan.
|
||||
- If a candidate starts collecting unrelated offenders, split it again instead of broadening it.
|
||||
|
||||
## Ownership Template
|
||||
|
||||
For each PR, capture the following in the PR description:
|
||||
|
||||
- Purpose
|
||||
- Single feature scope
|
||||
- Implementation notes
|
||||
- Files in scope
|
||||
- Baseline entries expected to be removed
|
||||
- Test automation plan
|
||||
- Any new preload or IPC surface added
|
||||
- Any deliberate deferrals to later PRs
|
||||
|
||||
@@ -469,3 +768,4 @@ This migration is complete when:
|
||||
2. The baseline file is empty or reduced to intentionally permitted entries.
|
||||
3. Lint restrictions can be tightened by removing temporary offender exclusions.
|
||||
4. The main BrowserWindow runs with `nodeIntegration: false` without renderer regressions.
|
||||
5. Security audit of changes is complete, including the writeResponseBodyToFile preload function.
|
||||
|
||||
@@ -16,74 +16,6 @@
|
||||
"importer": "../insomnia-testing/src/run/run.ts",
|
||||
"builtin": "path"
|
||||
},
|
||||
{
|
||||
"importer": "src/common/misc.ts",
|
||||
"builtin": "path"
|
||||
},
|
||||
{
|
||||
"importer": "src/common/misc.ts",
|
||||
"builtin": "zlib"
|
||||
},
|
||||
{
|
||||
"importer": "src/common/significant-diff-detection.ts",
|
||||
"builtin": "path"
|
||||
},
|
||||
{
|
||||
"importer": "src/main/importers/importers/curl.ts",
|
||||
"builtin": "url"
|
||||
},
|
||||
{
|
||||
"importer": "src/main/importers/importers/openapi-3.ts",
|
||||
"builtin": "crypto"
|
||||
},
|
||||
{
|
||||
"importer": "src/main/importers/importers/openapi-3.ts",
|
||||
"builtin": "url"
|
||||
},
|
||||
{
|
||||
"importer": "src/main/importers/importers/swagger-2.ts",
|
||||
"builtin": "crypto"
|
||||
},
|
||||
{
|
||||
"importer": "src/main/network/libcurl-promise.ts",
|
||||
"builtin": "fs"
|
||||
},
|
||||
{
|
||||
"importer": "src/main/network/libcurl-promise.ts",
|
||||
"builtin": "path"
|
||||
},
|
||||
{
|
||||
"importer": "src/main/network/libcurl-promise.ts",
|
||||
"builtin": "url"
|
||||
},
|
||||
{
|
||||
"importer": "src/main/network/multipart.ts",
|
||||
"builtin": "fs"
|
||||
},
|
||||
{
|
||||
"importer": "src/main/network/multipart.ts",
|
||||
"builtin": "os"
|
||||
},
|
||||
{
|
||||
"importer": "src/main/network/multipart.ts",
|
||||
"builtin": "path"
|
||||
},
|
||||
{
|
||||
"importer": "src/main/network/parse-header-strings.ts",
|
||||
"builtin": "url"
|
||||
},
|
||||
{
|
||||
"importer": "src/main/secure-read-file.ts",
|
||||
"builtin": "fs"
|
||||
},
|
||||
{
|
||||
"importer": "src/main/secure-read-file.ts",
|
||||
"builtin": "os"
|
||||
},
|
||||
{
|
||||
"importer": "src/main/secure-read-file.ts",
|
||||
"builtin": "path"
|
||||
},
|
||||
{
|
||||
"importer": "src/models/helpers/response-operations.ts",
|
||||
"builtin": "fs"
|
||||
@@ -92,26 +24,6 @@
|
||||
"importer": "src/models/helpers/response-operations.ts",
|
||||
"builtin": "zlib"
|
||||
},
|
||||
{
|
||||
"importer": "src/network/grpc/proto-directory-loader.tsx",
|
||||
"builtin": "fs"
|
||||
},
|
||||
{
|
||||
"importer": "src/network/grpc/proto-directory-loader.tsx",
|
||||
"builtin": "path"
|
||||
},
|
||||
{
|
||||
"importer": "src/network/grpc/write-proto-file.ts",
|
||||
"builtin": "fs"
|
||||
},
|
||||
{
|
||||
"importer": "src/network/grpc/write-proto-file.ts",
|
||||
"builtin": "os"
|
||||
},
|
||||
{
|
||||
"importer": "src/network/grpc/write-proto-file.ts",
|
||||
"builtin": "path"
|
||||
},
|
||||
{
|
||||
"importer": "src/network/network.ts",
|
||||
"builtin": "fs"
|
||||
@@ -120,22 +32,6 @@
|
||||
"importer": "src/network/network.ts",
|
||||
"builtin": "path"
|
||||
},
|
||||
{
|
||||
"importer": "src/network/o-auth-1/get-token.ts",
|
||||
"builtin": "crypto"
|
||||
},
|
||||
{
|
||||
"importer": "src/network/o-auth-2/get-token.ts",
|
||||
"builtin": "crypto"
|
||||
},
|
||||
{
|
||||
"importer": "src/network/o-auth-2/get-token.ts",
|
||||
"builtin": "querystring"
|
||||
},
|
||||
{
|
||||
"importer": "src/network/o-auth-2/utils.ts",
|
||||
"builtin": "crypto"
|
||||
},
|
||||
{
|
||||
"importer": "src/network/url-matches-cert-host.ts",
|
||||
"builtin": "url"
|
||||
@@ -144,6 +40,10 @@
|
||||
"importer": "src/plugins/context/response.ts",
|
||||
"builtin": "fs"
|
||||
},
|
||||
{
|
||||
"importer": "src/plugins/context/response.ts",
|
||||
"builtin": "zlib"
|
||||
},
|
||||
{
|
||||
"importer": "src/plugins/create.ts",
|
||||
"builtin": "fs"
|
||||
@@ -160,57 +60,21 @@
|
||||
"importer": "src/plugins/index.ts",
|
||||
"builtin": "path"
|
||||
},
|
||||
{
|
||||
"importer": "src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.send.tsx",
|
||||
"builtin": "fs"
|
||||
},
|
||||
{
|
||||
"importer": "src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.debug.request.$requestId.send.tsx",
|
||||
"builtin": "path"
|
||||
},
|
||||
{
|
||||
"importer": "src/routes/organization.$organizationId.project.$projectId.workspace.new.tsx",
|
||||
"builtin": "fs"
|
||||
},
|
||||
{
|
||||
"importer": "src/routes/organization.$organizationId.project.$projectId.workspace.new.tsx",
|
||||
"builtin": "path"
|
||||
},
|
||||
{
|
||||
"importer": "src/script-executor.ts",
|
||||
"builtin": "fs/promises"
|
||||
},
|
||||
{
|
||||
"importer": "src/sync/store/drivers/file-system-driver.ts",
|
||||
"builtin": "fs/promises"
|
||||
"importer": "src/scripting/require-interceptor.ts",
|
||||
"builtin": "buffer"
|
||||
},
|
||||
{
|
||||
"importer": "src/sync/store/drivers/file-system-driver.ts",
|
||||
"builtin": "path"
|
||||
"importer": "src/scripting/require-interceptor.ts",
|
||||
"builtin": "timers"
|
||||
},
|
||||
{
|
||||
"importer": "src/sync/store/drivers/graceful-rename.ts",
|
||||
"builtin": "fs/promises"
|
||||
},
|
||||
{
|
||||
"importer": "src/sync/store/hooks/compress.ts",
|
||||
"builtin": "zlib"
|
||||
},
|
||||
{
|
||||
"importer": "src/sync/store/index.ts",
|
||||
"builtin": "path"
|
||||
},
|
||||
{
|
||||
"importer": "src/sync/vcs/util.ts",
|
||||
"builtin": "crypto"
|
||||
},
|
||||
{
|
||||
"importer": "src/sync/vcs/vcs.ts",
|
||||
"builtin": "crypto"
|
||||
},
|
||||
{
|
||||
"importer": "src/sync/vcs/vcs.ts",
|
||||
"builtin": "path"
|
||||
"importer": "src/scripting/require-interceptor.ts",
|
||||
"builtin": "util"
|
||||
},
|
||||
{
|
||||
"importer": "src/templating/base-extension.ts",
|
||||
@@ -227,10 +91,6 @@
|
||||
{
|
||||
"importer": "src/utils/plugin.ts",
|
||||
"builtin": "path"
|
||||
},
|
||||
{
|
||||
"importer": "src/utils/url/querystring.ts",
|
||||
"builtin": "url"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
"dependencies": {
|
||||
"@apideck/better-ajv-errors": "^0.3.6",
|
||||
"@apidevtools/swagger-parser": "10.1.1",
|
||||
"ajv": "^8.17.1",
|
||||
"@bufbuild/protobuf": "^1.10.0",
|
||||
"@connectrpc/connect": "^1.6.1",
|
||||
"@connectrpc/connect-node": "^1.6.1",
|
||||
@@ -54,7 +53,7 @@
|
||||
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||
"@fortawesome/react-fontawesome": "^3.0.2",
|
||||
"@getinsomnia/node-libcurl": "3.2.1",
|
||||
"@getinsomnia/node-libcurl": "3.2.2",
|
||||
"@getinsomnia/srp-js": "1.0.0-alpha.1",
|
||||
"@grpc/grpc-js": "^1.13.3",
|
||||
"@grpc/proto-loader": "^0.7.13",
|
||||
@@ -67,13 +66,16 @@
|
||||
"@seald-io/nedb": "^4.1.1",
|
||||
"@segment/analytics-node": "2.2.1",
|
||||
"@sentry/electron": "^6.5.0",
|
||||
"@stoplight/spectral-core": "^1.20.0",
|
||||
"@stoplight/spectral-core": "^1.22.0",
|
||||
"@stoplight/spectral-formats": "^1.8.2",
|
||||
"@stoplight/spectral-ruleset-bundler": "1.6.3",
|
||||
"@stoplight/spectral-rulesets": "^1.22.0",
|
||||
"@stoplight/spectral-ruleset-bundler": "1.7.0",
|
||||
"@stoplight/spectral-rulesets": "^1.22.1",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tanstack/react-virtual": "3.13.12",
|
||||
"@xmldom/xmldom": "^0.9.8",
|
||||
"acorn": "^8.16.0",
|
||||
"acorn-walk": "^8.3.5",
|
||||
"ajv": "^8.17.1",
|
||||
"apiconnect-wsdl": "2.0.36",
|
||||
"aws4": "^1.13.2",
|
||||
"blakejs": "^1.2.1",
|
||||
@@ -92,10 +94,11 @@
|
||||
"decompress": "^4.2.1",
|
||||
"deep-equal": "2.2.3",
|
||||
"diff-match-patch-ts": "^0.6.0",
|
||||
"dompurify": "^3.2.5",
|
||||
"dompurify": "^3.4.1",
|
||||
"electron-context-menu": "^3.6.1",
|
||||
"electron-updater": "^6.6.2",
|
||||
"fastq": "^1.19.1",
|
||||
"fflate": "^0.8.2",
|
||||
"fuzzysort": "^1.9.0",
|
||||
"graphql": "^16.10.0",
|
||||
"graphql-ws": "^5.16.2",
|
||||
@@ -144,7 +147,7 @@
|
||||
"tinykeys": "^3.0.0",
|
||||
"tough-cookie": "^4.1.4",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"undici": "^7.16.0",
|
||||
"undici": "^7.25.0",
|
||||
"uuid": "^9.0.1",
|
||||
"vkbeautify": "^0.99.3",
|
||||
"ws": "^8.18.1",
|
||||
@@ -207,7 +210,7 @@
|
||||
"vite": "^7.1.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@kong/insomnia-plugin-ai": "^1.0.9",
|
||||
"@kong/insomnia-plugin-ai": "^1.0.11",
|
||||
"@kong/insomnia-plugin-external-vault": "0.1.4-dev.20251224090833"
|
||||
},
|
||||
"dev": {
|
||||
|
||||
@@ -5,8 +5,8 @@ import { servicesNodeImpl } from '~/insomnia-data/node';
|
||||
|
||||
import { nodeLibcurlMock } from './src/__mocks__/@getinsomnia/node-libcurl';
|
||||
import { electronMock } from './src/__mocks__/electron';
|
||||
import { v4Mock } from './src/__mocks__/uuid';
|
||||
import { mainDatabase } from './src/main/database.main';
|
||||
import { v4Mock } from './src/models/__mocks__/uuid';
|
||||
|
||||
await initDatabase(mainDatabase, { inMemoryOnly: true }, true);
|
||||
await initServices(servicesNodeImpl);
|
||||
|
||||
@@ -48,12 +48,6 @@ vi.mock('electron', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../models', () => ({
|
||||
settings: {
|
||||
get: vi.fn(() => Promise.resolve({})),
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock the entire install-plugin module
|
||||
vi.mock('../main/install-plugin', async () => {
|
||||
const actual = await vi.importActual('../main/install-plugin');
|
||||
|
||||
240
packages/insomnia/src/account/__tests__/session.test.ts
Normal file
240
packages/insomnia/src/account/__tests__/session.test.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
import * as insomniaApi from 'insomnia-api';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import * as crypt from '../crypt';
|
||||
import {
|
||||
absorbKey,
|
||||
getCurrentSessionId,
|
||||
getPrivateKey,
|
||||
getUserSession,
|
||||
isLoggedIn,
|
||||
logout,
|
||||
setSessionData,
|
||||
} from '../session';
|
||||
|
||||
vi.mock('insomnia-api', () => ({
|
||||
getUserProfile: vi.fn(),
|
||||
getEncryptionKeys: vi.fn(),
|
||||
logout: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('../crypt', () => ({
|
||||
decryptAES: vi.fn(),
|
||||
}));
|
||||
|
||||
interface MockWindowMain {
|
||||
loginStateChange: ReturnType<typeof vi.fn>;
|
||||
}
|
||||
|
||||
const getWindowMain = () => (window as unknown as { main: MockWindowMain }).main;
|
||||
|
||||
// Fixtures
|
||||
const SESSION_ID = 'test-session-id';
|
||||
const RAW_KEY = 'raw-key-material';
|
||||
|
||||
const MOCK_PUBLIC_KEY = { kty: 'RSA', n: 'abc', e: 'AQAB' };
|
||||
const MOCK_ENC_PRIVATE_KEY = { iv: 'iv1', t: 't1', d: 'd1', ad: 'ad1' };
|
||||
const MOCK_ENC_SYMMETRIC_KEY = { iv: 'iv2', t: 't2', d: 'd2', ad: 'ad2' };
|
||||
const MOCK_SYMMETRIC_KEY = { kty: 'oct', k: 'sym-key' };
|
||||
|
||||
const mockEncryptionKeys = {
|
||||
public_key: JSON.stringify(MOCK_PUBLIC_KEY),
|
||||
enc_private_key: JSON.stringify(MOCK_ENC_PRIVATE_KEY),
|
||||
enc_symmetric_key: JSON.stringify(MOCK_ENC_SYMMETRIC_KEY),
|
||||
salt_enc: 'salt',
|
||||
enc_driver_key: '',
|
||||
};
|
||||
|
||||
const mockUserProfile = {
|
||||
id: 'account-123',
|
||||
created_at: new Date('2026-01-01T00:00:00Z'),
|
||||
email: 'test@example.com',
|
||||
first_name: 'Jane',
|
||||
last_name: 'Doe',
|
||||
picture: '',
|
||||
emails: [],
|
||||
encryption_enabled: false,
|
||||
is_externally_provisioned: false,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.mocked(insomniaApi.getUserProfile).mockResolvedValue(mockUserProfile);
|
||||
vi.mocked(insomniaApi.getEncryptionKeys).mockResolvedValue(mockEncryptionKeys);
|
||||
vi.mocked(crypt.decryptAES).mockReturnValue(JSON.stringify(MOCK_SYMMETRIC_KEY));
|
||||
|
||||
vi.stubGlobal('window', { main: { loginStateChange: vi.fn() } });
|
||||
});
|
||||
|
||||
describe('absorbKey', () => {
|
||||
it('fetches profile and encryption keys with the provided sessionId', async () => {
|
||||
await absorbKey(SESSION_ID, RAW_KEY);
|
||||
|
||||
expect(insomniaApi.getUserProfile).toHaveBeenCalledWith({ sessionId: SESSION_ID });
|
||||
expect(insomniaApi.getEncryptionKeys).toHaveBeenCalledWith({ sessionId: SESSION_ID });
|
||||
});
|
||||
|
||||
it('decrypts the symmetric key using the provided raw key and encSymmetricKey', async () => {
|
||||
await absorbKey(SESSION_ID, RAW_KEY);
|
||||
|
||||
expect(crypt.decryptAES).toHaveBeenCalledWith(RAW_KEY, MOCK_ENC_SYMMETRIC_KEY);
|
||||
});
|
||||
|
||||
it('stores session data with mapped fields from profile and encryption keys', async () => {
|
||||
await absorbKey(SESSION_ID, RAW_KEY);
|
||||
|
||||
const session = await getUserSession();
|
||||
expect(session.id).toBe(SESSION_ID);
|
||||
expect(session.accountId).toBe(mockUserProfile.id);
|
||||
expect(session.email).toBe(mockUserProfile.email);
|
||||
expect(session.firstName).toBe(mockUserProfile.first_name);
|
||||
expect(session.lastName).toBe(mockUserProfile.last_name);
|
||||
expect(session.symmetricKey).toEqual(MOCK_SYMMETRIC_KEY);
|
||||
expect(session.publicKey).toEqual(MOCK_PUBLIC_KEY);
|
||||
expect(session.encPrivateKey).toEqual(MOCK_ENC_PRIVATE_KEY);
|
||||
});
|
||||
|
||||
it('triggers loginStateChange after storing session', async () => {
|
||||
await absorbKey(SESSION_ID, RAW_KEY);
|
||||
|
||||
expect(getWindowMain().loginStateChange).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('falls back to current session id when none is provided', async () => {
|
||||
// First establish a session so getCurrentSessionId returns something
|
||||
await setSessionData(
|
||||
SESSION_ID,
|
||||
'acct',
|
||||
'A',
|
||||
'B',
|
||||
'a@b.com',
|
||||
{} as JsonWebKey,
|
||||
{} as JsonWebKey,
|
||||
{} as crypt.AESMessage,
|
||||
);
|
||||
|
||||
await absorbKey('', RAW_KEY);
|
||||
|
||||
expect(insomniaApi.getUserProfile).toHaveBeenCalledWith({ sessionId: SESSION_ID });
|
||||
expect(insomniaApi.getEncryptionKeys).toHaveBeenCalledWith({ sessionId: SESSION_ID });
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPrivateKey', () => {
|
||||
it('decrypts and returns the private key from session, and throws when keys are missing', async () => {
|
||||
const mockPrivateKey = { kty: 'RSA', d: 'private' };
|
||||
vi.mocked(crypt.decryptAES).mockReturnValue(JSON.stringify(mockPrivateKey));
|
||||
|
||||
await setSessionData(
|
||||
SESSION_ID,
|
||||
'acct',
|
||||
'A',
|
||||
'B',
|
||||
'a@b.com',
|
||||
MOCK_SYMMETRIC_KEY as JsonWebKey,
|
||||
MOCK_PUBLIC_KEY as JsonWebKey,
|
||||
MOCK_ENC_PRIVATE_KEY as crypt.AESMessage,
|
||||
);
|
||||
|
||||
const privateKey = await getPrivateKey();
|
||||
|
||||
expect(crypt.decryptAES).toHaveBeenCalledWith(MOCK_SYMMETRIC_KEY, MOCK_ENC_PRIVATE_KEY);
|
||||
expect(privateKey).toEqual(mockPrivateKey);
|
||||
|
||||
await setSessionData(
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
null as unknown as JsonWebKey,
|
||||
{} as JsonWebKey,
|
||||
null as unknown as crypt.AESMessage,
|
||||
);
|
||||
|
||||
await expect(getPrivateKey()).rejects.toThrow("Can't get private key: session is missing keys.");
|
||||
});
|
||||
});
|
||||
|
||||
describe('isLoggedIn', () => {
|
||||
it('returns true when a session id exists', async () => {
|
||||
await setSessionData(
|
||||
SESSION_ID,
|
||||
'acct',
|
||||
'A',
|
||||
'B',
|
||||
'a@b.com',
|
||||
{} as JsonWebKey,
|
||||
{} as JsonWebKey,
|
||||
{} as crypt.AESMessage,
|
||||
);
|
||||
expect(await isLoggedIn()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logout', () => {
|
||||
it('calls the logout API with the current session id', async () => {
|
||||
await setSessionData(
|
||||
SESSION_ID,
|
||||
'acct',
|
||||
'A',
|
||||
'B',
|
||||
'a@b.com',
|
||||
{} as JsonWebKey,
|
||||
{} as JsonWebKey,
|
||||
{} as crypt.AESMessage,
|
||||
);
|
||||
|
||||
await logout();
|
||||
|
||||
expect(insomniaApi.logout).toHaveBeenCalledWith({ sessionId: SESSION_ID });
|
||||
});
|
||||
|
||||
it('triggers loginStateChange', async () => {
|
||||
await setSessionData(
|
||||
SESSION_ID,
|
||||
'acct',
|
||||
'A',
|
||||
'B',
|
||||
'a@b.com',
|
||||
{} as JsonWebKey,
|
||||
{} as JsonWebKey,
|
||||
{} as crypt.AESMessage,
|
||||
);
|
||||
|
||||
await logout();
|
||||
|
||||
expect(getWindowMain().loginStateChange).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it('does not throw if the API call fails', async () => {
|
||||
vi.mocked(insomniaApi.logout).mockRejectedValue(new Error('network error'));
|
||||
await setSessionData(
|
||||
SESSION_ID,
|
||||
'acct',
|
||||
'A',
|
||||
'B',
|
||||
'a@b.com',
|
||||
{} as JsonWebKey,
|
||||
{} as JsonWebKey,
|
||||
{} as crypt.AESMessage,
|
||||
);
|
||||
|
||||
await expect(logout()).resolves.not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCurrentSessionId', () => {
|
||||
it('returns the current session id', async () => {
|
||||
await setSessionData(
|
||||
SESSION_ID,
|
||||
'acct',
|
||||
'A',
|
||||
'B',
|
||||
'a@b.com',
|
||||
{} as JsonWebKey,
|
||||
{} as JsonWebKey,
|
||||
{} as crypt.AESMessage,
|
||||
);
|
||||
expect(await getCurrentSessionId()).toBe(SESSION_ID);
|
||||
});
|
||||
});
|
||||
@@ -232,7 +232,7 @@ export async function generateAES256Key() {
|
||||
|
||||
function _hexToB64Url(h: string) {
|
||||
const bytes = forge.util.hexToBytes(h);
|
||||
return window.btoa(bytes).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
|
||||
return btoa(bytes).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
|
||||
}
|
||||
|
||||
function _b64UrlToBigInt(s: string) {
|
||||
@@ -243,5 +243,5 @@ function _b64UrlToBigInt(s: string) {
|
||||
|
||||
function _b64UrlToHex(s: string) {
|
||||
const b64 = s.replace(/-/g, '+').replace(/_/g, '/');
|
||||
return forge.util.bytesToHex(window.atob(b64));
|
||||
return forge.util.bytesToHex(atob(b64));
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { logout as logoutAPI, whoami } from 'insomnia-api';
|
||||
import { getEncryptionKeys, getUserProfile, logout as logoutAPI } from 'insomnia-api';
|
||||
|
||||
import type { GitRepository, Project, WorkspaceMeta } from '~/insomnia-data';
|
||||
import { models, services } from '~/insomnia-data';
|
||||
@@ -21,9 +21,17 @@ export interface SessionData {
|
||||
/** Creates a session from a sessionId and derived symmetric key. */
|
||||
export async function absorbKey(sessionId: string, key: string) {
|
||||
// Get and store some extra info (salts and keys)
|
||||
const { publicKey, encPrivateKey, encSymmetricKey, email, accountId, firstName, lastName } = await whoami({
|
||||
sessionId: sessionId || (await getCurrentSessionId()),
|
||||
});
|
||||
const sessionIdResolved = sessionId || (await getCurrentSessionId());
|
||||
const [profile, keys] = await Promise.all([
|
||||
getUserProfile({ sessionId: sessionIdResolved }),
|
||||
getEncryptionKeys({ sessionId: sessionIdResolved }),
|
||||
]);
|
||||
const {
|
||||
public_key: publicKey,
|
||||
enc_private_key: encPrivateKey,
|
||||
enc_symmetric_key: encSymmetricKey,
|
||||
} = keys;
|
||||
const { email, id: accountId, first_name: firstName, last_name: lastName } = profile;
|
||||
const symmetricKeyStr = crypt.decryptAES(key, JSON.parse(encSymmetricKey));
|
||||
|
||||
// Store the information for later
|
||||
@@ -217,7 +225,8 @@ async function _removeAllCredentials() {
|
||||
*
|
||||
*/
|
||||
async function _removeGitRepository(repo: GitRepository) {
|
||||
const projects = await database.find<Project>(models.project.type, { gitRepositoryId: repo._id });
|
||||
const queryIds = models.project.getQueryableGitRepositoryIds(repo._id);
|
||||
const projects = await database.find<Project>(models.project.type, { gitRepositoryId: { $in: queryIds } });
|
||||
for (const p of projects) {
|
||||
await services.project.update(p, { gitRepositoryId: models.project.EMPTY_GIT_PROJECT_ID });
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ interface Props {
|
||||
onClose?: () => void;
|
||||
title?: React.ReactNode;
|
||||
closable?: boolean;
|
||||
isDismissable?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
@@ -18,6 +19,7 @@ export const Modal: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
className,
|
||||
title,
|
||||
closable,
|
||||
isDismissable,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
@@ -26,7 +28,7 @@ export const Modal: React.FC<React.PropsWithChildren<Props>> = ({
|
||||
onOpenChange={isOpen => {
|
||||
!isOpen && onClose?.();
|
||||
}}
|
||||
isDismissable
|
||||
isDismissable={isDismissable}
|
||||
className="fixed top-0 left-0 z-10 flex h-(--visual-viewport-height) w-full items-center justify-center bg-black/30"
|
||||
>
|
||||
<RAModal
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { type BaseModel, request, requestGroup, workspace } from '../../models';
|
||||
import type { BaseModel } from '~/insomnia-data';
|
||||
import { models } from '~/insomnia-data';
|
||||
|
||||
const { workspace, requestGroup, request } = models;
|
||||
|
||||
export const data: Record<string, Partial<BaseModel>[]> = {
|
||||
[workspace.type]: [
|
||||
|
||||
@@ -108,4 +108,13 @@ describe('Test electron storage()', () => {
|
||||
expect(fs.readFileSync(path.join(basePath, 'foo'), 'utf8')).toEqual('"bar3"');
|
||||
expect(fs.readFileSync(path.join(basePath, 'another'), 'utf8')).toEqual('10');
|
||||
});
|
||||
|
||||
it.each(['', '.', '..', 'foo/bar', 'foo\\bar', 'foo\0bar'])('rejects invalid key %j', key => {
|
||||
const basePath = `/tmp/insomnia-electronstorage-${Math.random()}`;
|
||||
const electronStorage = new ElectronStorage(basePath);
|
||||
|
||||
expect(() => electronStorage.getItem(key)).toThrowError('Invalid electron storage key');
|
||||
expect(() => electronStorage.setItem(key, 'value')).toThrowError('Invalid electron storage key');
|
||||
expect(() => electronStorage.deleteItem(key)).toThrowError('Invalid electron storage key');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,10 +3,9 @@ import path from 'node:path';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import type { Cookie, Request, Response } from '~/insomnia-data';
|
||||
import { services } from '~/insomnia-data';
|
||||
import { models, services } from '~/insomnia-data';
|
||||
|
||||
import { database as db } from '../../common/database';
|
||||
import * as models from '../../models';
|
||||
import { exportHar, exportHarResponse, exportHarWithRequest } from '../har';
|
||||
import { getRenderedRequestAndContext } from '../render';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { parse } from 'yaml';
|
||||
|
||||
import { EnvironmentKvPairDataType, EnvironmentType, services } from '~/insomnia-data';
|
||||
@@ -172,6 +172,36 @@ describe('importRaw()', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should call syncNewWorkspaceIfNeeded after importing to a new workspace', async () => {
|
||||
const fixturePath = path.join(__dirname, '..', '__fixtures__', 'curl', 'complex-input.sh');
|
||||
const content = fs.readFileSync(fixturePath, 'utf8').toString();
|
||||
|
||||
const projectToImportTo = await services.project.create();
|
||||
const syncNewWorkspaceIfNeeded = vi.fn();
|
||||
|
||||
const scanResult = await importUtil.scanResources([
|
||||
{
|
||||
contentStr: content,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(scanResult[0].type?.id).toBe('curl');
|
||||
expect(scanResult[0].errors.length).toBe(0);
|
||||
|
||||
await importUtil.importResourcesToProject({
|
||||
projectId: projectToImportTo._id,
|
||||
syncNewWorkspaceIfNeeded,
|
||||
});
|
||||
|
||||
expect(syncNewWorkspaceIfNeeded).toHaveBeenCalledTimes(1);
|
||||
expect(syncNewWorkspaceIfNeeded).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
parentId: projectToImportTo._id,
|
||||
scope: 'collection',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should import a curl request to an existing workspace', async () => {
|
||||
const fixturePath = path.join(__dirname, '..', '__fixtures__', 'curl', 'complex-input.sh');
|
||||
const content = fs.readFileSync(fixturePath, 'utf8').toString();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { chunkArray } from '../../sync/vcs/vcs';
|
||||
import {
|
||||
debounce,
|
||||
filterHeaders,
|
||||
@@ -204,33 +203,6 @@ describe('fuzzyMatchAll()', () => {
|
||||
expect(fuzzyMatchAll('wrong this ou', ['testing', 'this', 'out'])).toEqual(null);
|
||||
});
|
||||
});
|
||||
describe('chunkArray()', () => {
|
||||
it('works with exact divisor', () => {
|
||||
const chunks = chunkArray([1, 2, 3, 4, 5, 6], 3);
|
||||
expect(chunks).toEqual([
|
||||
[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
]);
|
||||
});
|
||||
|
||||
it('works with weird divisor', () => {
|
||||
const chunks = chunkArray([1, 2, 3, 4, 5, 6], 4);
|
||||
expect(chunks).toEqual([
|
||||
[1, 2, 3, 4],
|
||||
[5, 6],
|
||||
]);
|
||||
});
|
||||
|
||||
it('works with empty', () => {
|
||||
const chunks = chunkArray([], 4);
|
||||
expect(chunks).toEqual([]);
|
||||
});
|
||||
|
||||
it('works with less than one chunk', () => {
|
||||
const chunks = chunkArray([1, 2], 4);
|
||||
expect(chunks).toEqual([[1, 2]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isNotNullOrUndefined', () => {
|
||||
it('should return correctly', () => {
|
||||
|
||||
@@ -2,9 +2,9 @@ import { createBuilder } from '@develohpanda/fluent-builder';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import type { Environment, Workspace } from '~/insomnia-data';
|
||||
import { models, services } from '~/insomnia-data';
|
||||
import { services } from '~/insomnia-data';
|
||||
|
||||
import { environmentModelSchema, requestGroupModelSchema } from '../../models/__schemas__/model-schemas';
|
||||
import { environmentModelSchema, requestGroupModelSchema } from '../../sync/__schemas__/model-schemas';
|
||||
import * as renderUtils from '../render';
|
||||
|
||||
const envBuilder = createBuilder(environmentModelSchema);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { grpcRequest, request, requestGroup } from '../../models';
|
||||
import { models } from '~/insomnia-data';
|
||||
|
||||
import {
|
||||
METHOD_DELETE,
|
||||
METHOD_GET,
|
||||
@@ -18,6 +19,8 @@ import {
|
||||
sortMethodMap,
|
||||
} from '../sorting';
|
||||
|
||||
const { request, requestGroup, grpcRequest } = models;
|
||||
|
||||
describe('Sorting methods', () => {
|
||||
it('defaults to ascending metaSortKey aka descending but flipped (* -1)', () => {
|
||||
const unsorted = [
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import * as models from '../../models';
|
||||
import { models } from '~/insomnia-data';
|
||||
|
||||
import { getWorkspaceLabel } from '../get-workspace-label';
|
||||
import { strings } from '../strings';
|
||||
|
||||
|
||||
40
packages/insomnia/src/common/compression.ts
Normal file
40
packages/insomnia/src/common/compression.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { gunzipSync, gzipSync, strFromU8, strToU8 } from 'fflate';
|
||||
|
||||
const bytesToBase64 = (bytes: Uint8Array) => {
|
||||
if (typeof Buffer !== 'undefined') {
|
||||
return Buffer.from(bytes).toString('base64');
|
||||
}
|
||||
|
||||
let binary = '';
|
||||
for (const byte of bytes) {
|
||||
binary += String.fromCodePoint(byte);
|
||||
}
|
||||
|
||||
return btoa(binary);
|
||||
};
|
||||
|
||||
const base64ToBytes = (input: string) => {
|
||||
if (typeof Buffer !== 'undefined') {
|
||||
return Uint8Array.from(Buffer.from(input, 'base64'));
|
||||
}
|
||||
|
||||
const binary = atob(input);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let index = 0; index < binary.length; index++) {
|
||||
bytes[index] = binary.codePointAt(index)!;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
};
|
||||
|
||||
export function compressObject(obj: any) {
|
||||
return bytesToBase64(gzipSync(strToU8(JSON.stringify(obj))));
|
||||
}
|
||||
|
||||
export function decompressObject<ObjectType>(input: string | null): ObjectType | null {
|
||||
if (typeof input !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSON.parse(strFromU8(gunzipSync(base64ToBytes(input)))) as ObjectType;
|
||||
}
|
||||
@@ -12,7 +12,8 @@ const env = process[ENV];
|
||||
export const INSOMNIA_GITLAB_REDIRECT_URI = env.INSOMNIA_GITLAB_REDIRECT_URI;
|
||||
export const INSOMNIA_GITLAB_CLIENT_ID = env.INSOMNIA_GITLAB_CLIENT_ID;
|
||||
export const INSOMNIA_GITLAB_API_URL = env.INSOMNIA_GITLAB_API_URL;
|
||||
export const PLAYWRIGHT = env.PLAYWRIGHT;
|
||||
export const PLAYWRIGHT_TEST = env.PLAYWRIGHT_TEST;
|
||||
export const OAUTH_WINDOW_SESSION_ID_KEY = 'current-oauth-session-id';
|
||||
|
||||
// App Stuff
|
||||
export const getSkipOnboarding = () => env.INSOMNIA_SKIP_ONBOARDING;
|
||||
@@ -33,12 +34,12 @@ export const getAppBundlePlugins = () => appConfig.bundlePlugins;
|
||||
export const getAppEnvironment = () => process.env.INSOMNIA_ENV || 'production';
|
||||
export const isDevelopment = () => getAppEnvironment() === 'development';
|
||||
export const getSegmentWriteKey = () =>
|
||||
appConfig.segmentWriteKeys[isDevelopment() || env.PLAYWRIGHT ? 'development' : 'production'];
|
||||
appConfig.segmentWriteKeys[isDevelopment() || env.PLAYWRIGHT_TEST ? 'development' : 'production'];
|
||||
export const getSentryDsn = () => appConfig.sentryDsn;
|
||||
export const getCioWriteKey = () =>
|
||||
appConfig.cio[isDevelopment() || env.PLAYWRIGHT ? 'development' : 'production'].writeKey;
|
||||
appConfig.cio[isDevelopment() || env.PLAYWRIGHT_TEST ? 'development' : 'production'].writeKey;
|
||||
export const getCioSiteId = () =>
|
||||
appConfig.cio[isDevelopment() || env.PLAYWRIGHT ? 'development' : 'production'].siteId;
|
||||
appConfig.cio[isDevelopment() || env.PLAYWRIGHT_TEST ? 'development' : 'production'].siteId;
|
||||
export const getAppBuildDate = () => new Date(process.env.BUILD_DATE ?? '').toLocaleDateString();
|
||||
|
||||
export const getBrowserUserAgent = () =>
|
||||
@@ -112,6 +113,7 @@ export const getMockServiceBinURL = (mockServer: MockServer, path: string) => {
|
||||
|
||||
export const getAIServiceURL = () => env.INSOMNIA_AI_URL || 'https://ai-helper.insomnia.rest';
|
||||
export const getKonnectApiBaseURL = () => env.KONNECT_API_URL || 'https://global.api.konghq.com';
|
||||
export const isKonnectSyncEnabled = () => !!env.KONNECT_SYNC_ENABLED;
|
||||
|
||||
// App website
|
||||
export const getAppWebsiteBaseURL = () => env.INSOMNIA_APP_WEBSITE_URL || 'https://app.insomnia.rest';
|
||||
@@ -256,6 +258,55 @@ export type AuthTypes =
|
||||
export const HAWK_ALGORITHM_SHA256 = 'sha256';
|
||||
export const HAWK_ALGORITHM_SHA1 = 'sha1';
|
||||
|
||||
//oauth 1
|
||||
export type OAuth1SignatureMethod = 'HMAC-SHA1' | 'RSA-SHA1' | 'HMAC-SHA256' | 'PLAINTEXT';
|
||||
|
||||
export const SIGNATURE_METHOD_HMAC_SHA1: OAuth1SignatureMethod = 'HMAC-SHA1';
|
||||
export const SIGNATURE_METHOD_HMAC_SHA256: OAuth1SignatureMethod = 'HMAC-SHA256';
|
||||
export const SIGNATURE_METHOD_RSA_SHA1: OAuth1SignatureMethod = 'RSA-SHA1';
|
||||
export const SIGNATURE_METHOD_PLAINTEXT: OAuth1SignatureMethod = 'PLAINTEXT';
|
||||
|
||||
//oauth 2
|
||||
export const GRANT_TYPE_AUTHORIZATION_CODE = 'authorization_code';
|
||||
export const GRANT_TYPE_IMPLICIT = 'implicit';
|
||||
export const GRANT_TYPE_PASSWORD = 'password';
|
||||
export const GRANT_TYPE_CLIENT_CREDENTIALS = 'client_credentials';
|
||||
export const GRANT_TYPE_REFRESH = 'refresh_token';
|
||||
export const GRANT_TYPE_MCP_AUTH_FLOW = 'mcp_auth_flow';
|
||||
|
||||
export type AuthKeys =
|
||||
| 'access_token'
|
||||
| 'id_token'
|
||||
| 'client_id'
|
||||
| 'client_secret'
|
||||
| 'audience'
|
||||
| 'resource'
|
||||
| 'code_challenge'
|
||||
| 'code_challenge_method'
|
||||
| 'code_verifier'
|
||||
| 'code'
|
||||
| 'nonce'
|
||||
| 'error'
|
||||
| 'error_description'
|
||||
| 'error_uri'
|
||||
| 'expires_in'
|
||||
| 'grant_type'
|
||||
| 'password'
|
||||
| 'redirect_uri'
|
||||
| 'refresh_token'
|
||||
| 'response_type'
|
||||
| 'scope'
|
||||
| 'state'
|
||||
| 'token_type'
|
||||
| 'username'
|
||||
| 'xError'
|
||||
| 'xResponseId';
|
||||
|
||||
export const PKCE_CHALLENGE_S256 = 'S256';
|
||||
export const PKCE_CHALLENGE_PLAIN = 'plain';
|
||||
|
||||
export type OAuth2AuthorizationStatusType = 'none' | 'getting_code' | 'getting_token';
|
||||
|
||||
// json-order constants
|
||||
export const JSON_ORDER_PREFIX = '&';
|
||||
export const JSON_ORDER_SEPARATOR = '~|';
|
||||
|
||||
@@ -2,13 +2,9 @@ import clone from 'clone';
|
||||
import type * as Har from 'har-format';
|
||||
import { Cookie as ToughCookie } from 'tough-cookie';
|
||||
|
||||
import type { Environment, Request, RequestGroup, Response, Workspace } from '~/insomnia-data';
|
||||
import { services } from '~/insomnia-data';
|
||||
import { getBodyBuffer } from '~/models/helpers/response-operations';
|
||||
import type { BaseModel, Environment, Request, RequestGroup, Response, Workspace } from '~/insomnia-data';
|
||||
import { models, services } from '~/insomnia-data';
|
||||
|
||||
import type { BaseModel } from '../models';
|
||||
import * as models from '../models';
|
||||
import { getAuthHeader } from '../network/authentication';
|
||||
import * as plugins from '../plugins';
|
||||
import * as pluginApp from '../plugins/context/app';
|
||||
import * as pluginRequest from '../plugins/context/request';
|
||||
@@ -307,6 +303,10 @@ export async function exportHarWithRenderedRequest(renderedRequest: RenderedRequ
|
||||
|
||||
// Set auth header if we have it
|
||||
if (!hasAuthHeader(renderedRequest.headers)) {
|
||||
const getAuthHeader =
|
||||
process.type === 'renderer'
|
||||
? window.main.getAuthHeader
|
||||
: (await import('../main/network/get-auth-header')).getAuthHeader;
|
||||
const header = await getAuthHeader(renderedRequest, url);
|
||||
|
||||
if (header) {
|
||||
@@ -403,7 +403,7 @@ function mapCookie(cookie: ToughCookie) {
|
||||
}
|
||||
|
||||
async function getResponseContent(response: Response) {
|
||||
let body = await getBodyBuffer(response);
|
||||
let body = await services.helpers.getResponseBodyBuffer(response);
|
||||
|
||||
if (body === null) {
|
||||
body = Buffer.alloc(0);
|
||||
|
||||
@@ -2,7 +2,9 @@ import orderedJSON from 'json-order';
|
||||
import { z, type ZodError } from 'zod/v4';
|
||||
|
||||
import type {
|
||||
AllTypes,
|
||||
ApiSpec,
|
||||
BaseModel,
|
||||
CookieJar,
|
||||
Environment,
|
||||
EnvironmentKvPairData,
|
||||
@@ -16,21 +18,18 @@ import type {
|
||||
WebSocketRequest,
|
||||
Workspace,
|
||||
} from '~/insomnia-data';
|
||||
import { services } from '~/insomnia-data';
|
||||
import { insecureReadFile } from '~/main/secure-read-file';
|
||||
import { models, services } from '~/insomnia-data';
|
||||
|
||||
import type { InsomniaImporter } from '../main/importers/convert';
|
||||
import type { ImportEntry } from '../main/importers/entities';
|
||||
import { pathWithParamsAsPathParameters } from '../main/importers/importers/openapi-3';
|
||||
import { id as postmanEnvImporterId } from '../main/importers/importers/postman-env';
|
||||
import * as models from '../models/index';
|
||||
import { type AllTypes, type BaseModel, getModel } from '../models/index';
|
||||
import { invariant } from '../utils/invariant';
|
||||
import { parseApiSpec, type ParsedApiSpec } from './api-specs';
|
||||
import { JSON_ORDER_PREFIX, JSON_ORDER_SEPARATOR } from './constants';
|
||||
import { database as db } from './database';
|
||||
import { tryImportV5Data } from './insomnia-v5';
|
||||
import { generateId } from './misc';
|
||||
import { pathWithParamsAsPathParameters } from './path-with-params';
|
||||
|
||||
const { isRequest } = models.request;
|
||||
const { isApiSpec } = models.apiSpec;
|
||||
@@ -93,8 +92,12 @@ export async function fetchImportContentFromURI({ uri }: { uri: string }) {
|
||||
return content;
|
||||
} else if (uri.match(/^(file):\/\//)) {
|
||||
const path = uri.replace(/^(file):\/\//, '');
|
||||
// allow reading the file as it is chosen by user
|
||||
return insecureReadFile(path);
|
||||
const readFileProcessFork = async (path: string) =>
|
||||
process.type === 'renderer'
|
||||
? window.main.insecureReadFile({ path })
|
||||
: (await import('../main/secure-read-file')).insecureReadFile(path);
|
||||
|
||||
return readFileProcessFork(path);
|
||||
}
|
||||
// Treat everything else as raw text
|
||||
const content = decodeURIComponent(uri);
|
||||
@@ -204,9 +207,9 @@ export async function scanResources(importEntries: ImportEntry[]): Promise<ScanR
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const processFork =
|
||||
const convertProcessFork =
|
||||
process.type === 'renderer' ? window.main.parseImport : (await import('../main/importers/convert')).convert;
|
||||
result = (await processFork(importEntry)) as unknown as ConvertResult;
|
||||
result = (await convertProcessFork(importEntry)) as unknown as ConvertResult;
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
if (v5Error) {
|
||||
@@ -531,7 +534,7 @@ export const importResourcesToWorkspace = async ({
|
||||
const subEnvironments = resources.filter(models.environment.isEnvironment).filter(isSubEnvironmentResource) || [];
|
||||
|
||||
for (const environment of subEnvironments) {
|
||||
const model = getModel(environment.type);
|
||||
const model = models.getModel(environment.type);
|
||||
model && ResourceIdMap.set(environment._id, generateId(model.prefix));
|
||||
await services.environment.create({
|
||||
...environment,
|
||||
@@ -542,13 +545,13 @@ export const importResourcesToWorkspace = async ({
|
||||
|
||||
// Create new ids for each resource below optionalResources
|
||||
for (const resource of optionalResources) {
|
||||
const model = getModel(resource.type);
|
||||
const model = models.getModel(resource.type);
|
||||
model && ResourceIdMap.set(resource._id, generateId(model.prefix));
|
||||
}
|
||||
|
||||
// Preserve optionalResource relationships
|
||||
for (const resource of optionalResources) {
|
||||
const model = getModel(resource.type);
|
||||
const model = models.getModel(resource.type);
|
||||
if (model) {
|
||||
const rewritten = models.rewriteReferences(resource, ResourceIdMap);
|
||||
const objectToWrite = {
|
||||
@@ -626,12 +629,12 @@ export const importResourcesToNewWorkspace = async ({
|
||||
);
|
||||
|
||||
for (const resource of resourcesWithoutWorkspaceAndApiSpec) {
|
||||
const model = getModel(resource.type);
|
||||
const model = models.getModel(resource.type);
|
||||
model && ResourceIdMap.set(resource._id, generateId(model.prefix));
|
||||
}
|
||||
|
||||
for (const resource of resourcesWithoutWorkspaceAndApiSpec) {
|
||||
const model = getModel(resource.type);
|
||||
const model = models.getModel(resource.type);
|
||||
|
||||
if (model) {
|
||||
const newParentId = ResourceIdMap.get(resource.parentId);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { type FetchConfig, ResponseFailError } from 'insomnia-api';
|
||||
|
||||
import { getApiBaseURL, getClientString, INSOMNIA_FETCH_TIME_OUT, PLAYWRIGHT } from './constants';
|
||||
import { getApiBaseURL, getClientString, INSOMNIA_FETCH_TIME_OUT, PLAYWRIGHT_TEST } from './constants';
|
||||
import { generateId } from './misc';
|
||||
|
||||
// Adds headers, retries and opens deep links returned from the api
|
||||
@@ -27,7 +27,7 @@ export async function insomniaFetch<T = void>({
|
||||
...(sessionId ? { 'X-Session-Id': sessionId } : {}),
|
||||
...(data ? { 'Content-Type': 'application/json' } : {}),
|
||||
...(organizationId ? { 'X-Insomnia-Org-Id': organizationId } : {}),
|
||||
...(PLAYWRIGHT ? { 'X-Mockbin-Test': 'true' } : {}),
|
||||
...(PLAYWRIGHT_TEST ? { 'X-Mockbin-Test': 'true' } : {}),
|
||||
},
|
||||
...(data ? { body: JSON.stringify(data) } : {}),
|
||||
signal: AbortSignal.timeout(timeout),
|
||||
|
||||
@@ -19,6 +19,7 @@ import { migrateToLatestYaml } from '~/common/insomnia-schema-migrations';
|
||||
import { INSOMNIA_SCHEMA_VERSION } from '~/common/insomnia-schema-migrations/schema-version';
|
||||
import type {
|
||||
ApiSpec,
|
||||
BaseModel,
|
||||
CookieJar,
|
||||
Environment,
|
||||
EnvironmentKvPairData,
|
||||
@@ -38,11 +39,10 @@ import type {
|
||||
Workspace,
|
||||
WorkspaceScope,
|
||||
} from '~/insomnia-data';
|
||||
import { services } from '~/insomnia-data';
|
||||
import { models, services } from '~/insomnia-data';
|
||||
import { maskVaultEnvironmentData } from '~/utils/environment-utils';
|
||||
import { invariant } from '~/utils/invariant';
|
||||
|
||||
import * as models from '../models';
|
||||
import { database } from './database';
|
||||
import {
|
||||
type Insomnia_GRPCRequest,
|
||||
@@ -62,7 +62,7 @@ import {
|
||||
* Type helper that adds the export type field to any BaseModel
|
||||
* This is used to ensure all exported models have the correct _type field for v5 format
|
||||
*/
|
||||
type WithExportType<T extends models.BaseModel> = T & { _type: AllExportTypes };
|
||||
type WithExportType<T extends BaseModel> = T & { _type: AllExportTypes };
|
||||
|
||||
/**
|
||||
* Maps request headers from internal format to v5 export format
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import path from 'node:path';
|
||||
import zlib from 'node:zlib';
|
||||
|
||||
import fuzzysort from 'fuzzysort';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { DEBOUNCE_MILLIS } from './constants';
|
||||
export { compressObject, decompressObject } from './compression';
|
||||
|
||||
const ESCAPE_REGEX_MATCH = /[-[\]/{}()*+?.\\^$|]/g;
|
||||
|
||||
@@ -147,20 +145,6 @@ export function fnOrString(v: string | ((...args: any[]) => any), ...args: any[]
|
||||
return v(...args);
|
||||
}
|
||||
|
||||
export function compressObject(obj: any) {
|
||||
const compressed = zlib.gzipSync(JSON.stringify(obj));
|
||||
return compressed.toString('base64');
|
||||
}
|
||||
|
||||
export function decompressObject<ObjectType>(input: string | null): ObjectType | null {
|
||||
if (typeof input !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const jsonBuffer = zlib.gunzipSync(Buffer.from(input, 'base64'));
|
||||
return JSON.parse(jsonBuffer.toString('utf8')) as ObjectType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape a dynamic string for use inside of a regular expression
|
||||
* @param str - string to escape
|
||||
@@ -266,34 +250,6 @@ export function unescapeForwardSlash(str: string): string {
|
||||
});
|
||||
}
|
||||
|
||||
export const normalizeFolderPath = (p: string) => {
|
||||
const normalized = path.normalize(p);
|
||||
// Preserve filesystem roots as-is (e.g. "/" on POSIX, "C:\" on Windows)
|
||||
if (normalized === path.parse(normalized).root) {
|
||||
return normalized;
|
||||
}
|
||||
return normalized.replace(/[/\\]+$/, '');
|
||||
};
|
||||
|
||||
export type FolderValidationResult =
|
||||
| { ok: true; normalizedValue: string }
|
||||
| { ok: false; error: string };
|
||||
|
||||
export function validateFolderInput(input: string, existing: string[]): FolderValidationResult {
|
||||
const trimmed = input.trim();
|
||||
if (trimmed === '') {
|
||||
return { ok: false, error: 'Enter a folder path to add.' };
|
||||
}
|
||||
const normalized = normalizeFolderPath(trimmed);
|
||||
if (trimmed !== normalized) {
|
||||
return { ok: false, error: `Invalid folder path format. Did you mean "${normalized}"?` };
|
||||
}
|
||||
if (existing.some(v => normalizeFolderPath(v) === normalized)) {
|
||||
return { ok: false, error: 'Duplicate folders are not allowed.' };
|
||||
}
|
||||
return { ok: true, normalizedValue: normalized };
|
||||
}
|
||||
|
||||
export const SECURITY_SETTINGS_PATH_LABEL = "Insomnia Preferences → General → Security";
|
||||
|
||||
export function cannotAccessPathError(accessingPath: string): string {
|
||||
|
||||
54
packages/insomnia/src/common/organization-storage-rules.ts
Normal file
54
packages/insomnia/src/common/organization-storage-rules.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { getOrganizationStorageRule, type StorageRules } from 'insomnia-api';
|
||||
|
||||
import { models, services } from '~/insomnia-data';
|
||||
import { invariant } from '~/utils/invariant';
|
||||
|
||||
const inMemoryStorageRuleCache: Map<string, StorageRules> = new Map<string, StorageRules>();
|
||||
|
||||
export const DEFAULT_STORAGE_RULES = {
|
||||
enableCloudSync: true,
|
||||
enableLocalVault: true,
|
||||
enableGitSync: true,
|
||||
isOverridden: false,
|
||||
};
|
||||
|
||||
export async function fetchAndCacheOrganizationStorageRule(
|
||||
organizationId: string | undefined,
|
||||
forceFetch = false,
|
||||
): Promise<StorageRules> {
|
||||
invariant(organizationId, 'Organization ID is required');
|
||||
|
||||
if (models.organization.isScratchpadOrganizationId(organizationId)) {
|
||||
return {
|
||||
enableCloudSync: false,
|
||||
enableLocalVault: true,
|
||||
enableGitSync: false,
|
||||
isOverridden: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (!forceFetch) {
|
||||
const storageRules = inMemoryStorageRuleCache.get(organizationId);
|
||||
if (storageRules) {
|
||||
return storageRules;
|
||||
}
|
||||
}
|
||||
|
||||
const { id: sessionId } = await services.userSession.getOrCreate();
|
||||
|
||||
return await getOrganizationStorageRule({
|
||||
organizationId,
|
||||
sessionId,
|
||||
}).then(
|
||||
res => {
|
||||
if (res) {
|
||||
inMemoryStorageRuleCache.set(organizationId, res);
|
||||
}
|
||||
return res || DEFAULT_STORAGE_RULES;
|
||||
},
|
||||
err => {
|
||||
console.log('[storageRule] Failed to load storage rules', err.message);
|
||||
return DEFAULT_STORAGE_RULES;
|
||||
},
|
||||
);
|
||||
}
|
||||
3
packages/insomnia/src/common/path-with-params.ts
Normal file
3
packages/insomnia/src/common/path-with-params.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
const VARIABLE_SEARCH_VALUE = /{([^}]+)}/g;
|
||||
|
||||
export const pathWithParamsAsPathParameters = (path?: string) => path?.replace(VARIABLE_SEARCH_VALUE, ':$1') ?? '';
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
type WorkspaceMeta,
|
||||
type WorkspaceScope,
|
||||
} from '~/insomnia-data';
|
||||
import { VCSInstance } from '~/sync/vcs/insomnia-sync';
|
||||
|
||||
export interface InsomniaFile {
|
||||
id: string;
|
||||
@@ -34,6 +33,10 @@ export interface InsomniaFile {
|
||||
hasUncommittedChanges?: boolean;
|
||||
hasUnpushedChanges?: boolean;
|
||||
gitFilePath?: string | null;
|
||||
fileIssue?: {
|
||||
kind: 'conflict' | 'parse-error';
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
|
||||
const lockGenerator = () => {
|
||||
@@ -208,8 +211,7 @@ export const getAllRemoteBackendProjectsByProjectId = async ({
|
||||
teamProjectId: string;
|
||||
organizationId: string;
|
||||
}) => {
|
||||
const vcs = VCSInstance();
|
||||
return vcs.remoteBackendProjects({ teamId: organizationId, teamProjectId });
|
||||
return window.main.sync.remoteBackendProjects({ teamId: organizationId, teamProjectId });
|
||||
};
|
||||
|
||||
export const getUnsyncedRemoteWorkspaces = (remoteFiles: InsomniaFile[], workspaces: Workspace[]) =>
|
||||
@@ -230,12 +232,10 @@ export async function getAllRemoteFiles({ projectId, organizationId }: { project
|
||||
`remoteId: ${remoteId}`,
|
||||
);
|
||||
|
||||
const vcs = VCSInstance();
|
||||
|
||||
const [allPulledBackendProjectsForRemoteId, allFetchedRemoteBackendProjectsForRemoteId] = await Promise.all([
|
||||
vcs.localBackendProjects().then(projects => projects.filter(p => p.id === remoteId)),
|
||||
window.main.sync.localBackendProjects().then(projects => projects.filter(p => p.id === remoteId)),
|
||||
// Remote backend projects are fetched from the backend since they are not stored locally
|
||||
vcs.remoteBackendProjects({ teamId: organizationId, teamProjectId: remoteId }),
|
||||
window.main.sync.remoteBackendProjects({ teamId: organizationId, teamProjectId: remoteId }),
|
||||
]);
|
||||
console.log(
|
||||
`[getAllRemoteFiles] found allPulledBackendProjectsForRemoteId: ${allPulledBackendProjectsForRemoteId.length} and allFetchedRemoteBackendProjectsForRemoteId: ${allFetchedRemoteBackendProjectsForRemoteId.length} for remoteId: ${remoteId}`,
|
||||
|
||||
@@ -13,9 +13,8 @@ import type {
|
||||
WebSocketRequest,
|
||||
Workspace,
|
||||
} from '~/insomnia-data';
|
||||
import { services } from '~/insomnia-data';
|
||||
import { models, services } from '~/insomnia-data';
|
||||
|
||||
import * as models from '../models';
|
||||
import { getOrInheritAuthentication, getOrInheritHeaders } from '../network/network';
|
||||
import * as templating from '../templating';
|
||||
import { RenderError } from '../templating/render-error';
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
import type { Environment, Settings, UserUploadEnvironment } from '~/insomnia-data';
|
||||
import type { BaseModel, Environment, Settings, UserUploadEnvironment } from '~/insomnia-data';
|
||||
import { database, initDatabase, services } from '~/insomnia-data';
|
||||
import { createNedbDatabase } from '~/insomnia-data/node';
|
||||
import { getBodyBuffer } from '~/models/helpers/response-operations';
|
||||
|
||||
import type { BaseModel } from '../models';
|
||||
import {
|
||||
defaultSendActionRuntime,
|
||||
fetchRequestData,
|
||||
@@ -140,7 +138,7 @@ export async function getSendRequestCallbackMemDb(
|
||||
(acc, { name, value }) => ({ ...acc, [name.toLowerCase() || '']: value || '' }),
|
||||
{},
|
||||
);
|
||||
const bodyBuffer = (await getBodyBuffer(res)) as Buffer;
|
||||
const bodyBuffer = (await services.helpers.getResponseBodyBuffer(res)) as Buffer;
|
||||
const data = bodyBuffer ? bodyBuffer.toString('utf8') : undefined;
|
||||
|
||||
const testResults = [
|
||||
|
||||
@@ -163,4 +163,16 @@ export interface Settings {
|
||||
saveVaultKeyToOSSecretManager: boolean;
|
||||
vaultSecretCacheDuration: number;
|
||||
dataFolders: string[];
|
||||
// AST and shadowing check.
|
||||
scriptSandboxEnabled: boolean;
|
||||
// Wraps the user script in 'use strict', preventing accidental globals and making `this` undefined.
|
||||
scriptStrictModeEnabled: boolean;
|
||||
// Names of security rules that have been individually disabled.
|
||||
disabledSecurityRules: string[];
|
||||
// AST blocked-property names that have been individually disabled.
|
||||
disabledBlockedProperties: string[];
|
||||
// AST blocked-root names that have been individually disabled.
|
||||
disabledBlockedRoots: string[];
|
||||
/** Custom npm registry URL for plugin installation (e.g., corporate mirror). Empty string uses the default https://registry.npmjs.org/. */
|
||||
npmRegistryUrl: string;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import path from 'node:path';
|
||||
|
||||
import { isMap, isScalar, isSeq, LineCounter, parse, type ParsedNode, parseDocument } from 'yaml';
|
||||
|
||||
import { normalizeScripts } from '~/common/insomnia-schema-migrations/v5.1';
|
||||
@@ -158,7 +156,7 @@ export function hasSignificantChanges(
|
||||
config: Partial<IntelligentDiffConfig> = {},
|
||||
): boolean {
|
||||
// Non-YAML files → raw string comparison
|
||||
if (path.extname(filePath) !== '.yaml') {
|
||||
if (!filePath.toLowerCase().endsWith('.yaml')) {
|
||||
return originalContent !== modifiedContent;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,13 @@ import { HydratedRouter } from 'react-router/dom';
|
||||
import { insomniaFetch } from '~/common/insomnia-fetch';
|
||||
import { initDatabase, initServices, services } from '~/insomnia-data';
|
||||
import { database as clientDatabase } from '~/ui/database.client';
|
||||
import { clearOAuthWindowSessionId } from '~/ui/spawn-oauth-window';
|
||||
|
||||
import { migrateFromLocalStorage, type SessionData, setSessionData, setVaultSessionData } from './account/session';
|
||||
import { getInsomniaSession, getInsomniaVaultKey, getInsomniaVaultSalt, getSkipOnboarding } from './common/constants';
|
||||
import { initNewOAuthSession } from './network/o-auth-2/get-token';
|
||||
import { init as initPlugins } from './plugins';
|
||||
import { applyColorScheme } from './plugins/misc';
|
||||
import { registerSyncMergeConflictListener } from './sync/vcs/insomnia-sync';
|
||||
import { HtmlElementWrapper } from './ui/components/html-element-wrapper';
|
||||
import { showModal } from './ui/components/modals';
|
||||
import { AlertModal } from './ui/components/modals/alert-modal';
|
||||
@@ -42,6 +43,7 @@ configureFetch(options => insomniaFetch({ ...options }));
|
||||
await initPlugins();
|
||||
|
||||
await migrateFromLocalStorage();
|
||||
registerSyncMergeConflictListener();
|
||||
|
||||
try {
|
||||
window.showAlert = options => showModal(AlertModal, options);
|
||||
@@ -129,7 +131,7 @@ if (insomniaSession) {
|
||||
const appSettings = await services.settings.getOrCreate();
|
||||
|
||||
if (appSettings.clearOAuth2SessionOnRestart) {
|
||||
initNewOAuthSession();
|
||||
await clearOAuthWindowSessionId();
|
||||
}
|
||||
|
||||
applyColorScheme(appSettings);
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as fs from 'node:fs';
|
||||
import { contextBridge, ipcRenderer, type IpcRendererEvent } from 'electron';
|
||||
|
||||
import type { Compression } from '~/insomnia-data';
|
||||
import { servicesProxy } from '~/ui/renderer-services-proxy';
|
||||
|
||||
import {
|
||||
asyncTasksAllSettled,
|
||||
@@ -13,7 +14,7 @@ import {
|
||||
stopMonitorAsyncTasks,
|
||||
} from '../../insomnia-scripting-environment/src/objects';
|
||||
// this will also import lots of node_modules into the preload script, consider moving this file insomnia-scripting-environment
|
||||
import { requireInterceptor } from './require-interceptor';
|
||||
import { requireInterceptor } from './scripting/require-interceptor';
|
||||
|
||||
export interface HiddenBrowserWindowToMainBridgeAPI {
|
||||
requireInterceptor: (module: string) => any;
|
||||
@@ -63,7 +64,9 @@ const bridge: HiddenBrowserWindowToMainBridgeAPI = {
|
||||
if (process.contextIsolated) {
|
||||
contextBridge.exposeInMainWorld('bridge', bridge);
|
||||
contextBridge.exposeInMainWorld('Promise', ProxiedPromise);
|
||||
contextBridge.exposeInMainWorld('_dataServices', servicesProxy);
|
||||
} else {
|
||||
window.bridge = bridge;
|
||||
window.Promise = ProxiedPromise;
|
||||
window._dataServices = servicesProxy;
|
||||
}
|
||||
|
||||
@@ -1,29 +1,34 @@
|
||||
import * as Sentry from '@sentry/electron/renderer';
|
||||
import * as _ from 'es-toolkit/compat';
|
||||
import { SENTRY_OPTIONS } from 'insomnia/src/common/sentry';
|
||||
|
||||
import {
|
||||
initInsomniaObject,
|
||||
InsomniaObject,
|
||||
waitForAllTestsDone,
|
||||
} from '../../insomnia-scripting-environment/src/objects';
|
||||
import {
|
||||
getNewConsole,
|
||||
mergeClientCertificates,
|
||||
mergeCookieJar,
|
||||
mergeRequests,
|
||||
mergeSettings,
|
||||
type RequestContext,
|
||||
} from '../../insomnia-scripting-environment/src/objects';
|
||||
import { initServices } from '~/insomnia-data';
|
||||
|
||||
import type { RequestContext } from '../../insomnia-scripting-environment/src/objects';
|
||||
import { runScript } from './scripting/run-script';
|
||||
import { type ScriptSecurityPolicy } from './scripting/sandbox';
|
||||
|
||||
export interface HiddenBrowserWindowBridgeAPI {
|
||||
runScript: (options: { script: string; context: RequestContext }) => Promise<RequestContext>;
|
||||
runScript: (options: {
|
||||
script: string;
|
||||
context: RequestContext;
|
||||
securityPolicy?: ScriptSecurityPolicy;
|
||||
}) => Promise<RequestContext>;
|
||||
}
|
||||
|
||||
Sentry.init({
|
||||
...SENTRY_OPTIONS,
|
||||
});
|
||||
|
||||
// Initialize services for hidden renderer process
|
||||
if (!window._dataServices) {
|
||||
throw new Error(
|
||||
'window._dataServices is not available. This entrypoint must run in an environment with the preload bridge.',
|
||||
);
|
||||
}
|
||||
initServices(window._dataServices);
|
||||
// Remove the global services reference after initialization to improve security by preventing unintended access from the global scope.
|
||||
delete window._dataServices;
|
||||
|
||||
window.bridge.onmessage(
|
||||
async (data: { script: string; context: RequestContext }, callback: ({ error }: { error: string }) => void) => {
|
||||
window.bridge.setBusy(true);
|
||||
@@ -38,14 +43,17 @@ window.bridge.onmessage(
|
||||
const result = await window.bridge.Promise.race([timeoutPromise, runScript(data)]);
|
||||
callback(result);
|
||||
} catch (err) {
|
||||
const errMessage = err.message
|
||||
? `Error from Pre-request or after-response script:
|
||||
|
||||
${err.message}`
|
||||
: err;
|
||||
const fullErrMessage = `${errMessage}
|
||||
|
||||
${err.stack ? `Stack: ${err.stack}` : ''}`;
|
||||
const error = err instanceof Error ? err : new Error(String(err));
|
||||
if ((error as NodeJS.ErrnoException).code === 'SECURITY_POLICY_VIOLATION') {
|
||||
console.log('[hidden-window] security policy violation:', error.message);
|
||||
callback({ error: error.message });
|
||||
return;
|
||||
}
|
||||
const errMessage = error.message
|
||||
? `Error from Pre-request or after-response script:\n${error.message}`
|
||||
: String(error);
|
||||
const fullErrMessage = `${errMessage}\n\n${error.stack ? `Stack: ${error.stack}` : ''}`;
|
||||
console.log('[hidden-window] script error:', errMessage);
|
||||
Sentry.captureException(errMessage, {
|
||||
tags: {
|
||||
source: 'hidden-window',
|
||||
@@ -57,112 +65,3 @@ ${err.stack ? `Stack: ${err.stack}` : ''}`;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// This function is duplicated in scriptExecutor.ts to run in nodejs
|
||||
// TODO: consider removing this implementation and using only nodejs scripting
|
||||
const runScript = async ({ script, context }: { script: string; context: RequestContext }): Promise<RequestContext> => {
|
||||
const scriptConsole = getNewConsole();
|
||||
|
||||
const executionContext = await initInsomniaObject(context, scriptConsole.log);
|
||||
|
||||
const AsyncFunction = (async () => {}).constructor;
|
||||
const executeScript = AsyncFunction(
|
||||
'insomnia',
|
||||
'require',
|
||||
'console',
|
||||
'_',
|
||||
'setTimeout',
|
||||
// disable these as they are not supported in web or existing implementation
|
||||
'setImmediate',
|
||||
'queueMicrotask',
|
||||
'process',
|
||||
'waitForAllTestsDone',
|
||||
`
|
||||
const $ = insomnia;
|
||||
window.bridge.resetAsyncTasks(); // exclude unnecessary ones
|
||||
${script};
|
||||
await waitForAllTestsDone();
|
||||
window.bridge.stopMonitorAsyncTasks(); // the next one should not be monitored
|
||||
await window.bridge.asyncTasksAllSettled();
|
||||
return insomnia;`,
|
||||
);
|
||||
|
||||
const mutatedInsomniaObject = await executeScript(
|
||||
executionContext,
|
||||
window.bridge.requireInterceptor,
|
||||
scriptConsole,
|
||||
_,
|
||||
proxiedSetTimeout,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
waitForAllTestsDone,
|
||||
);
|
||||
if (mutatedInsomniaObject == null || !(mutatedInsomniaObject instanceof InsomniaObject)) {
|
||||
throw new Error('insomnia object is invalid or script returns earlier than expected.');
|
||||
}
|
||||
const mutatedContextObject = mutatedInsomniaObject.toObject();
|
||||
const updatedRequest = mergeRequests(context.request, mutatedContextObject.request);
|
||||
const updatedSettings = mergeSettings(context.settings, mutatedContextObject.request);
|
||||
const updatedCertificates = mergeClientCertificates(
|
||||
mutatedContextObject.clientCertificates,
|
||||
mutatedContextObject.request,
|
||||
);
|
||||
const updatedCookieJar = mergeCookieJar(context.cookieJar, mutatedContextObject.cookieJar);
|
||||
|
||||
return {
|
||||
...context,
|
||||
environment: {
|
||||
id: context.environment.id,
|
||||
name: context.environment.name,
|
||||
data: mutatedContextObject.environment,
|
||||
},
|
||||
baseEnvironment: {
|
||||
id: context.baseEnvironment.id,
|
||||
name: context.baseEnvironment.name,
|
||||
data: mutatedContextObject.baseEnvironment,
|
||||
},
|
||||
iterationData: context.iterationData
|
||||
? {
|
||||
name: context.iterationData.name,
|
||||
data: mutatedContextObject.iterationData,
|
||||
}
|
||||
: undefined,
|
||||
transientVariables: {
|
||||
name: context.transientVariables?.name || 'transientVariables',
|
||||
data: mutatedContextObject.variables,
|
||||
},
|
||||
request: updatedRequest,
|
||||
execution: mutatedContextObject.execution,
|
||||
settings: updatedSettings,
|
||||
clientCertificates: updatedCertificates,
|
||||
cookieJar: updatedCookieJar,
|
||||
globals: context.globals && {
|
||||
id: context.globals.id,
|
||||
name: context.globals.name,
|
||||
data: mutatedContextObject.globals,
|
||||
},
|
||||
baseGlobals: context.baseGlobals && {
|
||||
id: context.baseGlobals.id,
|
||||
name: context.baseGlobals.name,
|
||||
data: mutatedContextObject.baseGlobals,
|
||||
},
|
||||
requestTestResults: mutatedContextObject.requestTestResults,
|
||||
logs: scriptConsole.dumpLogsAsArray(),
|
||||
parentFolders: mutatedContextObject.parentFolders,
|
||||
};
|
||||
};
|
||||
|
||||
// proxiedSetTimeout has to be here as callback could be an async task
|
||||
function proxiedSetTimeout(callback: () => void, ms?: number | undefined) {
|
||||
let resolveHdl: (value: unknown) => void;
|
||||
|
||||
new Promise(resolve => {
|
||||
resolveHdl = resolve;
|
||||
});
|
||||
|
||||
return setTimeout(() => {
|
||||
callback();
|
||||
resolveHdl(null);
|
||||
}, ms);
|
||||
}
|
||||
|
||||
@@ -8,15 +8,15 @@ import contextMenu from 'electron-context-menu';
|
||||
import installExtension, { REACT_DEVELOPER_TOOLS } from 'electron-devtools-installer';
|
||||
import { configureFetch } from 'insomnia-api';
|
||||
|
||||
import { getCurrentSessionId } from '~/account/session';
|
||||
import { insomniaFetch } from '~/common/insomnia-fetch';
|
||||
import type { Project, RemoteProject, Stats } from '~/insomnia-data';
|
||||
import { database, initDatabase, initServices, services } from '~/insomnia-data';
|
||||
import { database, initDatabase, initServices, models, services } from '~/insomnia-data';
|
||||
import { servicesNodeImpl } from '~/insomnia-data/node';
|
||||
import { mainDatabase } from '~/main/database.main';
|
||||
import { initElectronStorage } from '~/main/electron-storage';
|
||||
import { runGitCredentialsMigration } from '~/main/git/migrations';
|
||||
import { registerPathHandlers } from '~/main/ipc/path';
|
||||
import { registerLLMConfigServiceAPI } from '~/main/llm-config-service';
|
||||
import { runGitCredentialsMigration } from '~/sync/git/migrations';
|
||||
|
||||
import { userDataFolder } from '../config/config.json';
|
||||
import { getAppVersion, getProductName, isDevelopment } from './common/constants';
|
||||
@@ -24,8 +24,10 @@ import { isMac } from './common/platform';
|
||||
import { SegmentEvent, trackSegmentEvent } from './main/analytics';
|
||||
import { registerInsomniaProtocols } from './main/api.protocol';
|
||||
import { backupIfNewerVersionAvailable } from './main/backup';
|
||||
import { registerSyncHandlers } from './main/cloud-sync/ipc';
|
||||
import { registerGitServiceAPI } from './main/git-service';
|
||||
import { ipcMainOn, ipcMainOnce, registerElectronHandlers } from './main/ipc/electron';
|
||||
import { registerElectronStorageHandlers } from './main/ipc/electron-storage';
|
||||
import { registergRPCHandlers } from './main/ipc/grpc';
|
||||
import { registerMainHandlers } from './main/ipc/main';
|
||||
import { registerSecretStorageHandlers } from './main/ipc/secret-storage';
|
||||
@@ -39,14 +41,16 @@ import { initializeSentry, sentryWatchAnalyticsEnabled } from './main/sentry';
|
||||
import { checkIfRestartNeeded } from './main/squirrel-startup';
|
||||
import * as updates from './main/updates';
|
||||
import * as windowUtils from './main/window-utils';
|
||||
import * as models from './models/index';
|
||||
|
||||
// Override the Electron userData path
|
||||
// This makes Chromium use this folder for eg localStorage
|
||||
// ensure userData dir change is made before configure sentry SDK (https://docs.sentry.io/platforms/javascript/guides/electron/#app-userdata-directory)
|
||||
const dataPath =
|
||||
process.env.INSOMNIA_DATA_PATH ||
|
||||
path.join(app.getPath('userData'), '../', isDevelopment() ? 'insomnia-app' : userDataFolder);
|
||||
|
||||
app.setPath('userData', dataPath);
|
||||
initElectronStorage(dataPath);
|
||||
|
||||
initializeLogging();
|
||||
|
||||
@@ -89,6 +93,8 @@ app.on('ready', async () => {
|
||||
registerCurlHandlers();
|
||||
registerMcpHandlers();
|
||||
registerSecretStorageHandlers();
|
||||
registerElectronStorageHandlers();
|
||||
registerSyncHandlers();
|
||||
|
||||
/**
|
||||
* There's no option that prevents Electron from fetching spellcheck dictionaries from Chromium's CDN and passing a non-resolving URL is the only known way to prevent it from fetching.
|
||||
@@ -121,7 +127,6 @@ app.on('ready', async () => {
|
||||
await backupIfNewerVersionAvailable();
|
||||
sentryWatchAnalyticsEnabled();
|
||||
watchProxySettings();
|
||||
windowUtils.init();
|
||||
|
||||
await runGitCredentialsMigration();
|
||||
|
||||
@@ -217,7 +222,7 @@ const _launchApp = async () => {
|
||||
}
|
||||
});
|
||||
// Disable deep linking in playwright e2e tests in order to run multiple tests in parallel
|
||||
if (!process.env.PLAYWRIGHT) {
|
||||
if (!process.env.PLAYWRIGHT_TEST) {
|
||||
// Deep linking logic - https://www.electronjs.org/docs/latest/tutorial/launch-app-from-url-in-another-app
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
if (!gotTheLock) {
|
||||
@@ -251,15 +256,6 @@ const _launchApp = async () => {
|
||||
} else {
|
||||
window = windowUtils.createWindowsAndReturnMain();
|
||||
}
|
||||
// Block imports when not logged in
|
||||
const isImportDeeplink = url.includes('://app/import');
|
||||
const isLoggedIn = (await getCurrentSessionId()) ? true : false;
|
||||
const shouldShowLoginPrompt = isImportDeeplink && !isLoggedIn;
|
||||
if (shouldShowLoginPrompt) {
|
||||
const title = encodeURIComponent('You must be logged in to open this link');
|
||||
const message = encodeURIComponent('Please log in and try again.');
|
||||
return window.webContents.send('shell:open', `insomnia://app/alert?title=${title}&message=${message}`);
|
||||
}
|
||||
return window.webContents.send('shell:open', url);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { contextBridge, ipcRenderer, webUtils as webUtilities } from 'electron';
|
||||
|
||||
import type { Services } from '~/insomnia-data';
|
||||
import type { AuthTypeOAuth2, OAuth2Token, RequestHeader } from '~/insomnia-data';
|
||||
import { invokeWithNormalizedError } from '~/main/ipc/invoke';
|
||||
import type { LLMBackend, LLMConfig, LLMConfigServiceAPI } from '~/main/llm-config-service';
|
||||
import type { GenerateMcpSamplingResponseFunction } from '~/plugins/types';
|
||||
import { isUserAbortResolveMergeConflictError, UserAbortResolveMergeConflictError } from '~/sync/vcs/errors';
|
||||
import { servicesProxy } from '~/ui/renderer-services-proxy';
|
||||
|
||||
import type { SyncBridgeAPI } from './main/cloud-sync/ipc';
|
||||
import type { GitServiceAPI } from './main/git-service';
|
||||
import type { electronStorageBridgeAPI } from './main/ipc/electron-storage';
|
||||
import type { gRPCBridgeAPI } from './main/ipc/grpc';
|
||||
import type { secretStorageBridgeAPI } from './main/ipc/secret-storage';
|
||||
import type { AIFeatureNames } from './main/llm-config-service';
|
||||
@@ -12,80 +17,81 @@ import type { CurlBridgeAPI } from './main/network/curl';
|
||||
import type { McpBridgeAPI } from './main/network/mcp';
|
||||
import type { SocketIOBridgeAPI } from './main/network/socket-io';
|
||||
import type { WebSocketBridgeAPI } from './main/network/websocket';
|
||||
import type { RenderedRequest } from './templating/types';
|
||||
import { invariant } from './utils/invariant';
|
||||
const ports = new Map<'hiddenWindowPort', MessagePort>();
|
||||
|
||||
const webSocket: WebSocketBridgeAPI = {
|
||||
open: options => ipcRenderer.invoke('webSocket.open', options),
|
||||
open: options => invokeWithNormalizedError('webSocket.open', options),
|
||||
close: options => ipcRenderer.send('webSocket.close', options),
|
||||
closeAll: () => ipcRenderer.send('webSocket.closeAll'),
|
||||
readyState: {
|
||||
getCurrent: options => ipcRenderer.invoke('webSocket.readyState', options),
|
||||
getCurrent: options => invokeWithNormalizedError('webSocket.readyState', options),
|
||||
},
|
||||
event: {
|
||||
findMany: options => ipcRenderer.invoke('webSocket.event.findMany', options),
|
||||
send: options => ipcRenderer.invoke('webSocket.event.send', options),
|
||||
findMany: options => invokeWithNormalizedError('webSocket.event.findMany', options),
|
||||
send: options => invokeWithNormalizedError('webSocket.event.send', options),
|
||||
},
|
||||
};
|
||||
const curl: CurlBridgeAPI = {
|
||||
open: options => ipcRenderer.invoke('curl.open', options),
|
||||
open: options => invokeWithNormalizedError('curl.open', options),
|
||||
close: options => ipcRenderer.send('curl.close', options),
|
||||
closeAll: () => ipcRenderer.send('curl.closeAll'),
|
||||
readyState: {
|
||||
getCurrent: options => ipcRenderer.invoke('curl.readyState', options),
|
||||
getCurrent: options => invokeWithNormalizedError('curl.readyState', options),
|
||||
},
|
||||
event: {
|
||||
findMany: options => ipcRenderer.invoke('curl.event.findMany', options),
|
||||
findMany: options => invokeWithNormalizedError('curl.event.findMany', options),
|
||||
},
|
||||
};
|
||||
|
||||
const socketIO: SocketIOBridgeAPI = {
|
||||
open: options => ipcRenderer.invoke('socketIO.open', options),
|
||||
open: options => invokeWithNormalizedError('socketIO.open', options),
|
||||
readyState: {
|
||||
getCurrent: options => ipcRenderer.invoke('socketIO.readyState', options),
|
||||
getCurrent: options => invokeWithNormalizedError('socketIO.readyState', options),
|
||||
},
|
||||
close: options => ipcRenderer.send('socketIO.close', options),
|
||||
closeAll: () => ipcRenderer.send('socketIO.closeAll'),
|
||||
event: {
|
||||
findMany: options => ipcRenderer.invoke('socketIO.event.findMany', options),
|
||||
send: options => ipcRenderer.invoke('socketIO.event.send', options),
|
||||
findMany: options => invokeWithNormalizedError('socketIO.event.findMany', options),
|
||||
send: options => invokeWithNormalizedError('socketIO.event.send', options),
|
||||
on: options => ipcRenderer.send('socketIO.event.on', options),
|
||||
off: options => ipcRenderer.send('socketIO.event.off', options),
|
||||
},
|
||||
};
|
||||
|
||||
const mcp: McpBridgeAPI = {
|
||||
connect: options => ipcRenderer.invoke('mcp.connect', options),
|
||||
close: options => ipcRenderer.invoke('mcp.close', options),
|
||||
connect: options => invokeWithNormalizedError('mcp.connect', options),
|
||||
close: options => invokeWithNormalizedError('mcp.close', options),
|
||||
closeAll: () => ipcRenderer.send('mcp.closeAll'),
|
||||
authConfirmation: confirmed => ipcRenderer.send('mcp.authConfirmed', confirmed),
|
||||
primitive: {
|
||||
listTools: options => ipcRenderer.invoke('mcp.primitive.listTools', options),
|
||||
callTool: options => ipcRenderer.invoke('mcp.primitive.callTool', options),
|
||||
listResources: options => ipcRenderer.invoke('mcp.primitive.listResources', options),
|
||||
listResourceTemplates: options => ipcRenderer.invoke('mcp.primitive.listResourceTemplates', options),
|
||||
readResource: options => ipcRenderer.invoke('mcp.primitive.readResource', options),
|
||||
subscribeResource: options => ipcRenderer.invoke('mcp.primitive.subscribeResource', options),
|
||||
unsubscribeResource: options => ipcRenderer.invoke('mcp.primitive.unsubscribeResource', options),
|
||||
listPrompts: options => ipcRenderer.invoke('mcp.primitive.listPrompts', options),
|
||||
getPrompt: options => ipcRenderer.invoke('mcp.primitive.getPrompt', options),
|
||||
listTools: options => invokeWithNormalizedError('mcp.primitive.listTools', options),
|
||||
callTool: options => invokeWithNormalizedError('mcp.primitive.callTool', options),
|
||||
listResources: options => invokeWithNormalizedError('mcp.primitive.listResources', options),
|
||||
listResourceTemplates: options => invokeWithNormalizedError('mcp.primitive.listResourceTemplates', options),
|
||||
readResource: options => invokeWithNormalizedError('mcp.primitive.readResource', options),
|
||||
subscribeResource: options => invokeWithNormalizedError('mcp.primitive.subscribeResource', options),
|
||||
unsubscribeResource: options => invokeWithNormalizedError('mcp.primitive.unsubscribeResource', options),
|
||||
listPrompts: options => invokeWithNormalizedError('mcp.primitive.listPrompts', options),
|
||||
getPrompt: options => invokeWithNormalizedError('mcp.primitive.getPrompt', options),
|
||||
},
|
||||
notification: {
|
||||
rootListChange: options => ipcRenderer.invoke('mcp.notification.rootListChange', options),
|
||||
rootListChange: options => invokeWithNormalizedError('mcp.notification.rootListChange', options),
|
||||
},
|
||||
readyState: {
|
||||
getCurrent: options => ipcRenderer.invoke('mcp.readyState', options),
|
||||
getCurrent: options => invokeWithNormalizedError('mcp.readyState', options),
|
||||
},
|
||||
client: {
|
||||
responseElicitationRequest: options => ipcRenderer.send('mcp.client.responseElicitationRequest', options),
|
||||
responseSamplingRequest: options => ipcRenderer.send('mcp.client.responseSamplingRequest', options),
|
||||
hasRequestResponded: options => ipcRenderer.invoke('mcp.client.hasRequestResponded', options),
|
||||
cancelRequest: options => ipcRenderer.invoke('mcp.client.cancelRequest', options),
|
||||
hasRequestResponded: options => invokeWithNormalizedError('mcp.client.hasRequestResponded', options),
|
||||
cancelRequest: options => invokeWithNormalizedError('mcp.client.cancelRequest', options),
|
||||
},
|
||||
event: {
|
||||
findMany: options => ipcRenderer.invoke('mcp.event.findMany', options),
|
||||
findNotifications: options => ipcRenderer.invoke('mcp.event.findNotifications', options),
|
||||
findPendingEvents: options => ipcRenderer.invoke('mcp.event.findPendingEvents', options),
|
||||
findMany: options => invokeWithNormalizedError('mcp.event.findMany', options),
|
||||
findNotifications: options => invokeWithNormalizedError('mcp.event.findNotifications', options),
|
||||
findPendingEvents: options => invokeWithNormalizedError('mcp.event.findPendingEvents', options),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -95,72 +101,135 @@ const grpc: gRPCBridgeAPI = {
|
||||
commit: options => ipcRenderer.send('grpc.commit', options),
|
||||
cancel: options => ipcRenderer.send('grpc.cancel', options),
|
||||
closeAll: () => ipcRenderer.send('grpc.closeAll'),
|
||||
loadMethods: options => ipcRenderer.invoke('grpc.loadMethods', options),
|
||||
loadMethodsFromReflection: options => ipcRenderer.invoke('grpc.loadMethodsFromReflection', options),
|
||||
loadMethods: options => invokeWithNormalizedError('grpc.loadMethods', options),
|
||||
loadMethodsFromReflection: options => invokeWithNormalizedError('grpc.loadMethodsFromReflection', options),
|
||||
writeProtoFile: protoFileId => invokeWithNormalizedError('grpc.writeProtoFile', protoFileId),
|
||||
};
|
||||
|
||||
const secretStorage: secretStorageBridgeAPI = {
|
||||
setSecret: (key, secret) => ipcRenderer.invoke('secretStorage.setSecret', key, secret),
|
||||
getSecret: key => ipcRenderer.invoke('secretStorage.getSecret', key),
|
||||
deleteSecret: key => ipcRenderer.invoke('secretStorage.deleteSecret', key),
|
||||
encryptString: raw => ipcRenderer.invoke('secretStorage.encryptString', raw),
|
||||
decryptString: cipherText => ipcRenderer.invoke('secretStorage.decryptString', cipherText),
|
||||
setSecret: (key, secret) => invokeWithNormalizedError('secretStorage.setSecret', key, secret),
|
||||
getSecret: key => invokeWithNormalizedError('secretStorage.getSecret', key),
|
||||
deleteSecret: key => invokeWithNormalizedError('secretStorage.deleteSecret', key),
|
||||
encryptString: raw => invokeWithNormalizedError('secretStorage.encryptString', raw),
|
||||
decryptString: cipherText => invokeWithNormalizedError('secretStorage.decryptString', cipherText),
|
||||
};
|
||||
|
||||
const electronStorage: electronStorageBridgeAPI = {
|
||||
getItem: key => invokeWithNormalizedError('electronStorage.getItem', key),
|
||||
setItem: (key, value) => invokeWithNormalizedError('electronStorage.setItem', key, value),
|
||||
};
|
||||
|
||||
const invokeSyncMethod = async <T>(methodName: string, ...args: unknown[]) => {
|
||||
try {
|
||||
return (await invokeWithNormalizedError('sync.invoke', methodName, ...args)) as T;
|
||||
} catch (error) {
|
||||
if (isUserAbortResolveMergeConflictError(error)) {
|
||||
throw new UserAbortResolveMergeConflictError(
|
||||
'message' in error && typeof error.message === 'string' ? error.message : undefined,
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const sync: SyncBridgeAPI = {
|
||||
archiveProject: () => invokeSyncMethod('archiveProject'),
|
||||
checkout: (...args) => invokeSyncMethod('checkout', ...args),
|
||||
compareRemoteBranch: () => invokeSyncMethod('compareRemoteBranch'),
|
||||
fork: (...args) => invokeSyncMethod('fork', ...args),
|
||||
getActiveBackendProject: () => invokeSyncMethod('getActiveBackendProject'),
|
||||
getBranchNames: () => invokeSyncMethod('getBranchNames'),
|
||||
getCurrentBranchName: () => invokeSyncMethod('getCurrentBranchName'),
|
||||
getHistory: (...args) => invokeSyncMethod('getHistory', ...args),
|
||||
getHistoryCount: () => invokeSyncMethod('getHistoryCount'),
|
||||
getRemoteBranchNames: () => invokeSyncMethod('getRemoteBranchNames'),
|
||||
getVersion: () => invokeSyncMethod('getVersion'),
|
||||
hasBackendProject: () => invokeSyncMethod('hasBackendProject'),
|
||||
localBackendProjects: () => invokeSyncMethod('localBackendProjects'),
|
||||
merge: (...args) => invokeSyncMethod('merge', ...args),
|
||||
pull: (...args) => invokeSyncMethod('pull', ...args),
|
||||
pullRemoteBackendProject: options => invokeWithNormalizedError('sync.pullRemoteBackendProject', options),
|
||||
push: (...args) => invokeSyncMethod('push', ...args),
|
||||
remoteBackendProjects: (...args) => invokeSyncMethod('remoteBackendProjects', ...args),
|
||||
removeBackendProjectsForRoot: (...args) => invokeSyncMethod('removeBackendProjectsForRoot', ...args),
|
||||
removeBranch: (...args) => invokeSyncMethod('removeBranch', ...args),
|
||||
removeRemoteBranch: (...args) => invokeSyncMethod('removeRemoteBranch', ...args),
|
||||
rollback: (...args) => invokeSyncMethod('rollback', ...args),
|
||||
rollbackToLatest: (...args) => invokeSyncMethod('rollbackToLatest', ...args),
|
||||
resolveConflict: options => ipcRenderer.send('sync.resolveConflict', options),
|
||||
cancelConflict: options => ipcRenderer.send('sync.cancelConflict', options),
|
||||
stage: (...args) => invokeSyncMethod('stage', ...args),
|
||||
status: (...args) => invokeSyncMethod('status', ...args),
|
||||
switchAndCreateBackendProjectIfNotExist: (...args) =>
|
||||
invokeSyncMethod('switchAndCreateBackendProjectIfNotExist', ...args),
|
||||
takeSnapshot: (...args) => invokeSyncMethod('takeSnapshot', ...args),
|
||||
unstage: (...args) => invokeSyncMethod('unstage', ...args),
|
||||
on: (channel, listener) => {
|
||||
ipcRenderer.on(channel, listener);
|
||||
return () => ipcRenderer.removeListener(channel, listener);
|
||||
},
|
||||
};
|
||||
|
||||
const git: GitServiceAPI = {
|
||||
loadGitRepository: options => ipcRenderer.invoke('git.loadGitRepository', options),
|
||||
getGitBranches: options => ipcRenderer.invoke('git.getGitBranches', options),
|
||||
fetchGitRemoteBranches: options => ipcRenderer.invoke('git.fetchGitRemoteBranches', options),
|
||||
validateGitRepositoryCredentials: options => ipcRenderer.invoke('git.validateGitRepositoryCredentials', options),
|
||||
validateGitCredentialById: options => ipcRenderer.invoke('git.validateGitCredentialById', options),
|
||||
gitFetchAction: options => ipcRenderer.invoke('git.gitFetchAction', options),
|
||||
gitLogLoader: options => ipcRenderer.invoke('git.gitLogLoader', options),
|
||||
gitChangesLoader: options => ipcRenderer.invoke('git.gitChangesLoader', options),
|
||||
canPushLoader: options => ipcRenderer.invoke('git.canPushLoader', options),
|
||||
cloneGitRepo: options => ipcRenderer.invoke('git.cloneGitRepo', options),
|
||||
initGitRepoClone: options => ipcRenderer.invoke('git.initGitRepoClone', options),
|
||||
updateGitRepo: options => ipcRenderer.invoke('git.updateGitRepo', options),
|
||||
resetGitRepo: options => ipcRenderer.invoke('git.resetGitRepo', options),
|
||||
commitToGitRepo: options => ipcRenderer.invoke('git.commitToGitRepo', options),
|
||||
commitAndPushToGitRepo: options => ipcRenderer.invoke('git.commitAndPushToGitRepo', options),
|
||||
createNewGitBranch: options => ipcRenderer.invoke('git.createNewGitBranch', options),
|
||||
checkoutGitBranch: options => ipcRenderer.invoke('git.checkoutGitBranch', options),
|
||||
mergeGitBranch: options => ipcRenderer.invoke('git.mergeGitBranch', options),
|
||||
deleteGitBranch: options => ipcRenderer.invoke('git.deleteGitBranch', options),
|
||||
pushToGitRemote: options => ipcRenderer.invoke('git.pushToGitRemote', options),
|
||||
pullFromGitRemote: options => ipcRenderer.invoke('git.pullFromGitRemote', options),
|
||||
continueMerge: options => ipcRenderer.invoke('git.continueMerge', options),
|
||||
discardChanges: options => ipcRenderer.invoke('git.discardChanges', options),
|
||||
abortMerge: () => ipcRenderer.invoke('git.abortMerge'),
|
||||
gitStatus: options => ipcRenderer.invoke('git.gitStatus', options),
|
||||
diff: () => ipcRenderer.invoke('git.diff'),
|
||||
multipleCommitToGitRepo: options => ipcRenderer.invoke('git.multipleCommitToGitRepo', options),
|
||||
stageChanges: options => ipcRenderer.invoke('git.stageChanges', options),
|
||||
unstageChanges: options => ipcRenderer.invoke('git.unstageChanges', options),
|
||||
diffFileLoader: options => ipcRenderer.invoke('git.diffFileLoader', options),
|
||||
getRepositoryDirectoryTree: options => ipcRenderer.invoke('git.getRepositoryDirectoryTree', options),
|
||||
migrateLegacyInsomniaFolderToFile: options => ipcRenderer.invoke('git.migrateLegacyInsomniaFolderToFile', options),
|
||||
loadGitRepository: options => invokeWithNormalizedError('git.loadGitRepository', options),
|
||||
getGitBranches: options => invokeWithNormalizedError('git.getGitBranches', options),
|
||||
fetchGitRemoteBranches: options => invokeWithNormalizedError('git.fetchGitRemoteBranches', options),
|
||||
getProjectGitFileIssues: options => invokeWithNormalizedError('git.getProjectGitFileIssues', options),
|
||||
validateGitRepositoryCredentials: options =>
|
||||
invokeWithNormalizedError('git.validateGitRepositoryCredentials', options),
|
||||
validateGitCredentialById: options => invokeWithNormalizedError('git.validateGitCredentialById', options),
|
||||
gitFetchAction: options => invokeWithNormalizedError('git.gitFetchAction', options),
|
||||
gitLogLoader: options => invokeWithNormalizedError('git.gitLogLoader', options),
|
||||
gitChangesLoader: options => invokeWithNormalizedError('git.gitChangesLoader', options),
|
||||
canPushLoader: options => invokeWithNormalizedError('git.canPushLoader', options),
|
||||
cloneGitRepo: options => invokeWithNormalizedError('git.cloneGitRepo', options),
|
||||
initGitRepoClone: options => invokeWithNormalizedError('git.initGitRepoClone', options),
|
||||
updateGitRepo: options => invokeWithNormalizedError('git.updateGitRepo', options),
|
||||
resetGitRepo: options => invokeWithNormalizedError('git.resetGitRepo', options),
|
||||
commitToGitRepo: options => invokeWithNormalizedError('git.commitToGitRepo', options),
|
||||
commitAndPushToGitRepo: options => invokeWithNormalizedError('git.commitAndPushToGitRepo', options),
|
||||
createNewGitBranch: options => invokeWithNormalizedError('git.createNewGitBranch', options),
|
||||
checkoutGitBranch: options => invokeWithNormalizedError('git.checkoutGitBranch', options),
|
||||
mergeGitBranch: options => invokeWithNormalizedError('git.mergeGitBranch', options),
|
||||
deleteGitBranch: options => invokeWithNormalizedError('git.deleteGitBranch', options),
|
||||
pushToGitRemote: options => invokeWithNormalizedError('git.pushToGitRemote', options),
|
||||
pullFromGitRemote: options => invokeWithNormalizedError('git.pullFromGitRemote', options),
|
||||
continueMerge: options => invokeWithNormalizedError('git.continueMerge', options),
|
||||
discardChanges: options => invokeWithNormalizedError('git.discardChanges', options),
|
||||
abortMerge: options => invokeWithNormalizedError('git.abortMerge', options),
|
||||
gitStatus: options => invokeWithNormalizedError('git.gitStatus', options),
|
||||
diff: () => invokeWithNormalizedError('git.diff'),
|
||||
multipleCommitToGitRepo: options => invokeWithNormalizedError('git.multipleCommitToGitRepo', options),
|
||||
stageChanges: options => invokeWithNormalizedError('git.stageChanges', options),
|
||||
unstageChanges: options => invokeWithNormalizedError('git.unstageChanges', options),
|
||||
diffFileLoader: options => invokeWithNormalizedError('git.diffFileLoader', options),
|
||||
getRepositoryDirectoryTree: options => invokeWithNormalizedError('git.getRepositoryDirectoryTree', options),
|
||||
migrateLegacyInsomniaFolderToFile: options =>
|
||||
invokeWithNormalizedError('git.migrateLegacyInsomniaFolderToFile', options),
|
||||
|
||||
listGitProviders: () => ipcRenderer.invoke('git.listGitProviders'),
|
||||
initSignInToGitProvider: options => ipcRenderer.invoke('git.initSignInToGitProvider', options),
|
||||
completeSignInToGitProvider: options => ipcRenderer.invoke('git.completeSignInToGitProvider', options),
|
||||
getGitProviderRepositories: options => ipcRenderer.invoke('git.getGitProviderRepositories', options),
|
||||
getGitProviderEmails: options => ipcRenderer.invoke('git.getGitProviderEmails', options),
|
||||
getCurrentBranchByRepositoryId: options => ipcRenderer.invoke('git.getCurrentBranchByRepositoryId', options),
|
||||
listGitProviders: () => invokeWithNormalizedError('git.listGitProviders'),
|
||||
initSignInToGitProvider: options => invokeWithNormalizedError('git.initSignInToGitProvider', options),
|
||||
completeSignInToGitProvider: options => invokeWithNormalizedError('git.completeSignInToGitProvider', options),
|
||||
getGitProviderRepositories: options => invokeWithNormalizedError('git.getGitProviderRepositories', options),
|
||||
getGitProviderEmails: options => invokeWithNormalizedError('git.getGitProviderEmails', options),
|
||||
getCurrentBranchByRepositoryId: options => invokeWithNormalizedError('git.getCurrentBranchByRepositoryId', options),
|
||||
getBranchRemoteInfo: options => invokeWithNormalizedError('git.getBranchRemoteInfo', options),
|
||||
runAllGitRepoMigrations: () => invokeWithNormalizedError('git.runAllGitRepoMigrations'),
|
||||
};
|
||||
|
||||
const llm: LLMConfigServiceAPI = {
|
||||
getActiveBackend: () => ipcRenderer.invoke('llm.getActiveBackend'),
|
||||
setActiveBackend: (backend: LLMBackend) => ipcRenderer.invoke('llm.setActiveBackend', backend),
|
||||
clearActiveBackend: () => ipcRenderer.invoke('llm.clearActiveBackend'),
|
||||
getBackendConfig: (backend: LLMBackend) => ipcRenderer.invoke('llm.getBackendConfig', backend),
|
||||
getActiveBackend: () => invokeWithNormalizedError('llm.getActiveBackend'),
|
||||
setActiveBackend: (backend: LLMBackend) => invokeWithNormalizedError('llm.setActiveBackend', backend),
|
||||
clearActiveBackend: () => invokeWithNormalizedError('llm.clearActiveBackend'),
|
||||
getBackendConfig: (backend: LLMBackend) => invokeWithNormalizedError('llm.getBackendConfig', backend),
|
||||
updateBackendConfig: (backend: LLMBackend, config: Partial<LLMConfig>) =>
|
||||
ipcRenderer.invoke('llm.updateBackendConfig', backend, config),
|
||||
getAllConfigurations: () => ipcRenderer.invoke('llm.getAllConfigurations'),
|
||||
getCurrentConfig: () => ipcRenderer.invoke('llm.getCurrentConfig'),
|
||||
getAIFeatureEnabled: (feature: AIFeatureNames) => ipcRenderer.invoke('llm.getAIFeatureEnabled', feature),
|
||||
invokeWithNormalizedError('llm.updateBackendConfig', backend, config),
|
||||
getAllConfigurations: () => invokeWithNormalizedError('llm.getAllConfigurations'),
|
||||
getCurrentConfig: () => invokeWithNormalizedError('llm.getCurrentConfig'),
|
||||
getAIFeatureEnabled: (feature: AIFeatureNames) => invokeWithNormalizedError('llm.getAIFeatureEnabled', feature),
|
||||
setAIFeatureEnabled: (feature: AIFeatureNames, enabled: boolean) =>
|
||||
ipcRenderer.invoke('llm.setAIFeatureEnabled', feature, enabled),
|
||||
invokeWithNormalizedError('llm.setAIFeatureEnabled', feature, enabled),
|
||||
};
|
||||
|
||||
const main: Window['main'] = {
|
||||
@@ -168,33 +237,44 @@ const main: Window['main'] = {
|
||||
addExecutionStep: options => ipcRenderer.send('addExecutionStep', options),
|
||||
completeExecutionStep: options => ipcRenderer.send('completeExecutionStep', options),
|
||||
updateLatestStepName: options => ipcRenderer.send('updateLatestStepName', options),
|
||||
getExecution: options => ipcRenderer.invoke('getExecution', options),
|
||||
getExecution: options => invokeWithNormalizedError('getExecution', options),
|
||||
loginStateChange: () => ipcRenderer.send('loginStateChange'),
|
||||
restart: () => ipcRenderer.send('restart'),
|
||||
openInBrowser: options => ipcRenderer.send('openInBrowser', options),
|
||||
openDeepLink: options => ipcRenderer.send('openDeepLink', options),
|
||||
halfSecondAfterAppStart: () => ipcRenderer.send('halfSecondAfterAppStart'),
|
||||
manualUpdateCheck: () => ipcRenderer.send('manualUpdateCheck'),
|
||||
backup: () => ipcRenderer.invoke('backup'),
|
||||
restoreBackup: options => ipcRenderer.invoke('restoreBackup', options),
|
||||
authorizeUserInWindow: options => ipcRenderer.invoke('authorizeUserInWindow', options),
|
||||
authorizeUserInDefaultBrowser: options => ipcRenderer.invoke('authorizeUserInDefaultBrowser', options),
|
||||
onDefaultBrowserOAuthRedirect: options => ipcRenderer.invoke('onDefaultBrowserOAuthRedirect', options),
|
||||
cancelAuthorizationInDefaultBrowser: options => ipcRenderer.invoke('cancelAuthorizationInDefaultBrowser', options),
|
||||
backup: () => invokeWithNormalizedError('backup'),
|
||||
restoreBackup: options => invokeWithNormalizedError('restoreBackup', options),
|
||||
authorizeUserInWindow: options => invokeWithNormalizedError('authorizeUserInWindow', options),
|
||||
authorizeUserInDefaultBrowser: options => invokeWithNormalizedError('authorizeUserInDefaultBrowser', options),
|
||||
onDefaultBrowserOAuthRedirect: options => invokeWithNormalizedError('onDefaultBrowserOAuthRedirect', options),
|
||||
cancelAuthorizationInDefaultBrowser: options =>
|
||||
invokeWithNormalizedError('cancelAuthorizationInDefaultBrowser', options),
|
||||
setMenuBarVisibility: options => ipcRenderer.send('setMenuBarVisibility', options),
|
||||
multipartBufferToArray: options => ipcRenderer.invoke('multipartBufferToArray', options),
|
||||
multipartBufferToArray: options => invokeWithNormalizedError('multipartBufferToArray', options),
|
||||
installPlugin: (lookupName: string, allowScopedPackageNames = false) =>
|
||||
ipcRenderer.invoke('installPlugin', lookupName, allowScopedPackageNames),
|
||||
curlRequest: options => ipcRenderer.invoke('curlRequest', options),
|
||||
invokeWithNormalizedError('installPlugin', lookupName, allowScopedPackageNames),
|
||||
initializeWorkspaceBackendProject: options => invokeWithNormalizedError('initializeWorkspaceBackendProject', options),
|
||||
curlRequest: options => invokeWithNormalizedError('curlRequest', options),
|
||||
cancelCurlRequest: options => ipcRenderer.send('cancelCurlRequest', options),
|
||||
writeFile: options => ipcRenderer.invoke('writeFile', options),
|
||||
insecureReadFile: options => ipcRenderer.invoke('insecureReadFile', options),
|
||||
insecureReadFileWithEncoding: options => ipcRenderer.invoke('insecureReadFileWithEncoding', options),
|
||||
secureReadFile: options => ipcRenderer.invoke('secureReadFile', options),
|
||||
parseImport: (...args) => ipcRenderer.invoke('parseImport', ...args),
|
||||
readDir: options => ipcRenderer.invoke('readDir', options),
|
||||
readOrCreateDataDir: options => ipcRenderer.invoke('readOrCreateDataDir', options),
|
||||
lintSpec: options => ipcRenderer.invoke('lintSpec', options),
|
||||
writeFile: options => invokeWithNormalizedError('writeFile', options),
|
||||
writeResponseBodyToFile: options => invokeWithNormalizedError('writeResponseBodyToFile', options),
|
||||
getAuthHeader: (renderedRequest: RenderedRequest, url: string): Promise<RequestHeader | undefined> =>
|
||||
invokeWithNormalizedError('getAuthHeader', renderedRequest, url),
|
||||
getOAuth2Token: (
|
||||
requestId: string,
|
||||
authentication: AuthTypeOAuth2,
|
||||
forceRefresh?: boolean,
|
||||
): Promise<OAuth2Token | undefined> =>
|
||||
invokeWithNormalizedError('getOAuth2Token', requestId, authentication, forceRefresh),
|
||||
insecureReadFile: options => invokeWithNormalizedError('insecureReadFile', options),
|
||||
insecureReadFileWithEncoding: options => invokeWithNormalizedError('insecureReadFileWithEncoding', options),
|
||||
secureReadFile: options => invokeWithNormalizedError('secureReadFile', options),
|
||||
parseImport: (...args) => invokeWithNormalizedError('parseImport', ...args),
|
||||
readDir: options => invokeWithNormalizedError('readDir', options),
|
||||
readOrCreateDataDir: options => invokeWithNormalizedError('readOrCreateDataDir', options),
|
||||
lintSpec: options => invokeWithNormalizedError('lintSpec', options),
|
||||
on: (channel, listener) => {
|
||||
ipcRenderer.on(channel, listener);
|
||||
return () => ipcRenderer.removeListener(channel, listener);
|
||||
@@ -207,6 +287,8 @@ const main: Window['main'] = {
|
||||
grpc,
|
||||
curl,
|
||||
secretStorage,
|
||||
electronStorage,
|
||||
sync,
|
||||
trackSegmentEvent: options => ipcRenderer.send('trackSegmentEvent', options),
|
||||
trackPageView: options => ipcRenderer.send('trackPageView', options),
|
||||
setCurrentOrganizationId: organizationId => ipcRenderer.send('analytics.setOrganizationId', organizationId),
|
||||
@@ -214,14 +296,14 @@ const main: Window['main'] = {
|
||||
showContextMenu: options => ipcRenderer.send('showContextMenu', options),
|
||||
database: {
|
||||
caCertificate: {
|
||||
create: options => ipcRenderer.invoke('database.caCertificate.create', options),
|
||||
create: options => invokeWithNormalizedError('database.caCertificate.create', options),
|
||||
},
|
||||
},
|
||||
hiddenBrowserWindow: {
|
||||
runScript: options =>
|
||||
new Promise(async (resolve, reject) => {
|
||||
const isPortAlive = ports.get('hiddenWindowPort') !== undefined;
|
||||
await ipcRenderer.invoke('open-channel-to-hidden-browser-window', isPortAlive);
|
||||
await invokeWithNormalizedError('open-channel-to-hidden-browser-window', isPortAlive);
|
||||
|
||||
const port = ports.get('hiddenWindowPort');
|
||||
invariant(port, 'hiddenWindowPort is undefined');
|
||||
@@ -238,8 +320,9 @@ const main: Window['main'] = {
|
||||
}),
|
||||
},
|
||||
extractJsonFileFromPostmanDataDumpArchive: archivePath =>
|
||||
ipcRenderer.invoke('extractJsonFileFromPostmanDataDumpArchive', archivePath),
|
||||
getLocalStorageDataFromFileOrigin: () => ipcRenderer.invoke('getLocalStorageDataFromFileOrigin'),
|
||||
invokeWithNormalizedError('extractJsonFileFromPostmanDataDumpArchive', archivePath),
|
||||
syncNewWorkspaceIfNeeded: options => invokeWithNormalizedError('syncNewWorkspaceIfNeeded', options),
|
||||
getLocalStorageDataFromFileOrigin: () => invokeWithNormalizedError('getLocalStorageDataFromFileOrigin'),
|
||||
generateMockRouteDataFromSpec: (
|
||||
openApiSpec: string | undefined,
|
||||
specUrl: string | undefined,
|
||||
@@ -248,7 +331,7 @@ const main: Window['main'] = {
|
||||
useDynamicMockResponses: boolean,
|
||||
mockServerAdditionalFiles: string[],
|
||||
) =>
|
||||
ipcRenderer.invoke(
|
||||
invokeWithNormalizedError(
|
||||
'generateMockRouteDataFromSpec',
|
||||
openApiSpec,
|
||||
specUrl,
|
||||
@@ -258,15 +341,15 @@ const main: Window['main'] = {
|
||||
mockServerAdditionalFiles,
|
||||
),
|
||||
generateCommitsFromDiff: (input: { diff: string; recent_commits: string }) =>
|
||||
ipcRenderer.invoke('generateCommitsFromDiff', input),
|
||||
invokeWithNormalizedError('generateCommitsFromDiff', input),
|
||||
generateMcpSamplingResponse: (parameters: Parameters<GenerateMcpSamplingResponseFunction>[0]) =>
|
||||
ipcRenderer.invoke('generateMcpSamplingResponse', parameters),
|
||||
invokeWithNormalizedError('generateMcpSamplingResponse', parameters),
|
||||
};
|
||||
|
||||
ipcRenderer.on('hidden-browser-window-response-listener', event => {
|
||||
const [port] = event.ports;
|
||||
ports.set('hiddenWindowPort', port);
|
||||
ipcRenderer.invoke('main-window-script-port-ready');
|
||||
invokeWithNormalizedError('main-window-script-port-ready');
|
||||
});
|
||||
const path: Window['path'] = {
|
||||
dirname: (p: string) => ipcRenderer.sendSync('path.dirname', p),
|
||||
@@ -275,8 +358,8 @@ const path: Window['path'] = {
|
||||
resolve: (...paths: string[]) => ipcRenderer.sendSync('path.resolve', ...paths),
|
||||
};
|
||||
const dialog: Window['dialog'] = {
|
||||
showOpenDialog: options => ipcRenderer.invoke('showOpenDialog', options),
|
||||
showSaveDialog: options => ipcRenderer.invoke('showSaveDialog', options),
|
||||
showOpenDialog: options => invokeWithNormalizedError('showOpenDialog', options),
|
||||
showSaveDialog: options => invokeWithNormalizedError('showSaveDialog', options),
|
||||
};
|
||||
const app: Window['app'] = {
|
||||
getPath: options => ipcRenderer.sendSync('getPath', options),
|
||||
@@ -289,7 +372,7 @@ const app: Window['app'] = {
|
||||
};
|
||||
const shell: Window['shell'] = {
|
||||
showItemInFolder: options => ipcRenderer.send('showItemInFolder', options),
|
||||
openPath: options => ipcRenderer.invoke('openPath', options),
|
||||
openPath: options => invokeWithNormalizedError('openPath', options),
|
||||
};
|
||||
const clipboard: Window['clipboard'] = {
|
||||
readText: () => ipcRenderer.sendSync('readText'),
|
||||
@@ -300,22 +383,9 @@ const webUtils: Window['webUtils'] = {
|
||||
getPathForFile: (file: File) => webUtilities.getPathForFile(file),
|
||||
};
|
||||
const database: Window['database'] = {
|
||||
invoke: (fnName, ...args) => ipcRenderer.invoke('database.invoke', fnName, ...args),
|
||||
invoke: (fnName, ...args) => invokeWithNormalizedError('database.invoke', fnName, ...args),
|
||||
};
|
||||
|
||||
const servicesProxy = new Proxy({} as Services, {
|
||||
get(_target, serviceName: string) {
|
||||
return new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_target, methodName: string) {
|
||||
return (...args: unknown[]) => ipcRenderer.invoke('services.invoke', serviceName, methodName, ...args);
|
||||
},
|
||||
},
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
if (process.contextIsolated) {
|
||||
contextBridge.exposeInMainWorld('main', main);
|
||||
contextBridge.exposeInMainWorld('dialog', dialog);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { type BaseGitCredentialsV2, models, services } from '~/insomnia-data';
|
||||
import type { BaseGitCredentialsV2 } from '~/insomnia-data';
|
||||
import { models, services } from '~/insomnia-data';
|
||||
|
||||
const { init, isGitCredentialsV2, supportsRenewal } = models.gitCredentials;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { services } from '~/insomnia-data';
|
||||
|
||||
import * as models from '../index';
|
||||
import { models, services } from '~/insomnia-data';
|
||||
|
||||
describe('init()', () => {
|
||||
it('contains all required fields', async () => {
|
||||
@@ -1,8 +1,6 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { services } from '~/insomnia-data';
|
||||
|
||||
import * as models from '../index';
|
||||
import { models, services } from '~/insomnia-data';
|
||||
|
||||
describe('init()', () => {
|
||||
it('contains all required fields', async () => {
|
||||
@@ -1,7 +1,8 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { getModel, mustGetModel } from '../index';
|
||||
import * as models from '../index';
|
||||
import { models } from '~/insomnia-data';
|
||||
|
||||
const { getModel, mustGetModel } = models;
|
||||
|
||||
describe('index', () => {
|
||||
describe('getModel()', () => {
|
||||
@@ -1,8 +1,6 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { services } from '~/insomnia-data';
|
||||
|
||||
import * as models from '../index';
|
||||
import { models, services } from '~/insomnia-data';
|
||||
|
||||
describe('init()', () => {
|
||||
it('contains all required fields', async () => {
|
||||
@@ -2,8 +2,6 @@ import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { services } from '~/insomnia-data';
|
||||
|
||||
import * as models from '../index';
|
||||
|
||||
describe('create()', () => {
|
||||
it('fails when missing parentId', async () => {
|
||||
expect(() =>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user