Compare commits

..

163 Commits

Author SHA1 Message Date
Szilárd Dóró
1c6f1e3b33 Merge pull request #1656 from nhost/changeset-release/main
chore: update versions
2023-02-23 11:56:07 +01:00
github-actions[bot]
d1365ea516 chore: update versions 2023-02-23 10:20:53 +00:00
Szilárd Dóró
72dbba7881 Merge pull request #1659 from nhost/chore/revert-graphql-client
chore(graphql-js): revert GraphQL Client
2023-02-23 11:19:26 +01:00
Szilárd Dóró
a3f3991d5a Merge pull request #1658 from nhost/fix/user-creation
fix(dashboard): false positive message on user creation
2023-02-23 11:01:44 +01:00
Szilárd Dóró
c71fe2cf72 Revert "chore(graphql-js): revert GraphQL client for now"
This reverts commit 9a0ab5b887.
2023-02-23 10:15:02 +01:00
Szilárd Dóró
24c5ed3ea4 chore(graphql-js): cleanup 2023-02-23 10:14:43 +01:00
Szilárd Dóró
2d9145f918 chore(graphql-js): revert GraphQL client for now 2023-02-23 10:10:14 +01:00
Szilárd Dóró
9a0ab5b887 chore(graphql-js): revert GraphQL client for now 2023-02-23 10:06:58 +01:00
Szilárd Dóró
1ddf704c5b fix(dashboard): false positive message on user creation 2023-02-23 09:39:32 +01:00
Szilárd Dóró
6f4ee845c6 Merge pull request #1643 from nhost/fix/auth-last-seen
fix(dashboard): use correct date for last seen
2023-02-22 20:01:18 +01:00
Pilou
76ce7d7b6e Merge pull request #1653 from nhost/changeset-release/main
chore: update versions
2023-02-22 15:43:41 +01:00
github-actions[bot]
538bfbcb3e chore: update versions 2023-02-22 14:23:55 +00:00
Pilou
07b35d1e5f Merge pull request #1650 from nhost/docs/graphql-client-readme
GraphQL client: fix snake_case bug and improve readme
2023-02-22 15:22:34 +01:00
Pierre-Louis Mercereau
2200a0ed07 fix: correct types on snake case operations 2023-02-22 15:13:43 +01:00
Szilárd Dóró
df23d97126 Merge pull request #1652 from nhost/changeset-release/main
chore: update versions
2023-02-22 14:44:46 +01:00
github-actions[bot]
104f149369 chore: update versions 2023-02-22 13:26:08 +00:00
Szilárd Dóró
01228583a0 Merge pull request #1651 from nhost/fix/permission-editor-dropdown
fix(dashboard): prevent permission editor dropdown from being always open
2023-02-22 14:24:51 +01:00
Pierre-Louis Mercereau
93309dd851 docs: strictNullChecks 2023-02-22 14:06:10 +01:00
Szilárd Dóró
2cc18dcb51 fix(dashboard): prevent permission editor dropdown from being always open 2023-02-22 13:54:31 +01:00
Pierre-Louis Mercereau
3b48a62790 docs: ✏️ improve readme instructions 2023-02-22 13:44:15 +01:00
Pierre-Louis Mercereau
8897dec056 docs: add package.json script instructions 2023-02-22 12:23:54 +01:00
Pierre-Louis Mercereau
324dda8309 docs: streamline readme instructions 2023-02-22 11:58:51 +01:00
Pilou
95f62bed07 Merge pull request #1644 from nhost/changeset-release/main
chore: update versions
2023-02-22 08:50:38 +01:00
Pierre-Louis Mercereau
0e4d8ff118 chore: manually bump @nhost/react-apollo 2023-02-21 18:20:41 +01:00
github-actions[bot]
baec5bada7 chore: update versions 2023-02-21 17:03:03 +00:00
Pilou
4e56cfc628 Merge pull request #1348 from nhost/sdk-next-major
Sdk next major version
2023-02-21 18:01:13 +01:00
Pierre-Louis Mercereau
54bc91923f docs: update changeset 2023-02-21 12:40:52 +01:00
Szilárd Dóró
77b12feb95 Merge pull request #1639 from nhost/renovate/testing-library-react-14.x
chore(deps): update dependency @testing-library/react to v14
2023-02-20 16:30:11 +01:00
Pierre-Louis Mercereau
32d4670bbb Merge branch 'main' into sdk-next-major 2023-02-20 16:05:23 +01:00
Pilou
1dc09942d2 Merge pull request #1587 from nhost/next-client
Next GraphQL client
2023-02-20 16:01:49 +01:00
Szilárd Dóró
3343a36358 chore(deps): bump @testing-library 2023-02-20 15:52:18 +01:00
Szilárd Dóró
b755e9086c fix(dashboard): use correct date for last seen 2023-02-20 14:19:56 +01:00
Pierre-Louis Mercereau
48866d0ee1 chore: fail on runtime when passing an invalid field 2023-02-20 12:45:52 +01:00
renovate[bot]
5b3b76bd41 chore(deps): update dependency @testing-library/react to v14 2023-02-20 10:08:36 +00:00
Pierre-Louis Mercereau
284ef7e7f2 test: multiple where 2023-02-17 08:57:02 +01:00
Pierre-Louis Mercereau
6d5c202da9 changes 2023-02-17 08:44:48 +01:00
Pierre-Louis Mercereau
9342937440 readme 2023-02-15 13:39:24 +01:00
Pierre-Louis Mercereau
e89cd4e262 simple mutation example 2023-02-15 13:30:16 +01:00
Pierre-Louis Mercereau
a05438352b inline documentation 2023-02-15 13:28:04 +01:00
Pierre-Louis Mercereau
78437959bb dew 2023-02-15 13:08:20 +01:00
Pierre-Louis Mercereau
e1a7433adb docs: static doc for nhost.functions 2023-02-15 12:41:54 +01:00
Pierre-Louis Mercereau
e23cf74975 fix tests 2023-02-10 22:11:53 +01:00
Pierre-Louis Mercereau
a3d01c4fad fix lint config 2023-02-10 21:33:26 +01:00
Pierre-Louis Mercereau
4cdcef9ef5 fix broken links 2023-02-10 19:12:29 +01:00
Pierre-Louis Mercereau
df894ef7e2 upgrade typescript version 2023-02-10 15:51:59 +01:00
Pierre-Louis Mercereau
f7dd6a9fc6 deactivate functions doc 2023-02-10 14:42:56 +01:00
Pierre-Louis Mercereau
2949ff0f62 fix docgen 2023-02-10 14:33:56 +01:00
Pierre-Louis Mercereau
1527b0a455 remove type-fest dependency 2023-02-06 14:18:13 +01:00
Pierre-Louis Mercereau
375e53a3f0 update ts 2023-02-06 13:35:33 +01:00
Pierre-Louis Mercereau
96e3ca5a32 basic readme 2023-02-06 12:53:11 +01:00
Pierre-Louis Mercereau
0e570df9c5 missing dependency 2023-02-06 12:11:02 +01:00
Pierre-Louis Mercereau
1f4c67283e lockfile 2023-02-06 10:49:47 +01:00
Pierre-Louis Mercereau
fc1c4861a3 cleanup 2023-02-06 10:49:02 +01:00
Pierre-Louis Mercereau
74feaf6add unions 2023-02-02 11:47:29 +01:00
Pierre-Louis Mercereau
8cd97206cc Merge branch 'next-client' of https://github.com/nhost/nhost into next-client 2023-01-31 21:06:17 +01:00
Pierre-Louis Mercereau
02197639f2 dew 2023-01-31 20:59:14 +01:00
Pilou
38b594aef9 Update README.md 2023-01-31 19:40:31 +01:00
Pierre-Louis Mercereau
f3a8886cd0 dew 2023-01-31 19:08:19 +01:00
Pierre-Louis Mercereau
8d76cf8d40 simplify 2023-01-31 17:37:44 +01:00
Pierre-Louis Mercereau
3e1fb974e4 dew 2023-01-31 16:37:40 +01:00
Pierre-Louis Mercereau
f74871d872 dew 2023-01-31 15:01:54 +01:00
Pierre-Louis Mercereau
3f26056688 Merge branch 'main' into sdk-next-major 2023-01-30 11:11:51 +01:00
Pierre-Louis Mercereau
6a7801be93 chore: update changeset 2023-01-10 09:10:33 +01:00
Pierre-Louis Mercereau
7bc5bb857c chore: add the deprecated useAxios option 2023-01-10 09:02:45 +01:00
Pierre-Louis Mercereau
c957039d75 chore: update lock file 2023-01-09 20:17:12 +01:00
Pierre-Louis Mercereau
96c4032424 Merge branch 'main' into sdk-next-major 2023-01-09 20:13:13 +01:00
Pierre-Louis Mercereau
ec70126b56 fix: iso 8859-1 2023-01-09 19:25:02 +01:00
Pierre-Louis Mercereau
86b9f9040c Merge branch 'main' into sdk-next-major 2023-01-09 16:31:04 +01:00
Pierre-Louis Mercereau
222f03725b fix(dashboard): use fetch instead of axios 2023-01-06 16:42:40 +01:00
Pierre-Louis Mercereau
10b786e5c6 chore: fix lockfile 2023-01-06 16:30:22 +01:00
Pierre-Louis Mercereau
aa8ae88d12 Merge branch 'main' into sdk-next-major 2023-01-06 16:25:12 +01:00
Pilou
0f2c86b41a Merge pull request #1351 from nhost/refactor/cross-fetch
refactor: replace axios by cross-fetch
2023-01-06 16:17:03 +01:00
Pilou
a4c76892dd Update packages/hasura-storage-js/src/hasura-storage-api.ts
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2023-01-03 10:36:33 +01:00
Pierre-Louis Mercereau
00d278b2cc refactor: adjustments from review 2023-01-02 17:32:34 +01:00
Pilou
cb6b5faeb9 Update packages/hasura-storage-js/src/hasura-storage-client.ts
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2023-01-02 17:29:21 +01:00
Pierre-Louis Mercereau
7c4c847b91 Merge branch 'sdk-next-major' into refactor/cross-fetch 2022-12-26 12:54:40 +01:00
Pierre-Louis Mercereau
908887d8c5 Merge branch 'main' into sdk-next-major 2022-12-26 12:54:19 +01:00
Pierre-Louis Mercereau
a2d67bc2db fix(dashboard): js error from error payload 2022-12-26 12:45:33 +01:00
Pierre-Louis Mercereau
1a6cd78254 Merge branch 'sdk-next-major' into refactor/cross-fetch 2022-12-21 16:42:58 +01:00
Pierre-Louis Mercereau
6500629c4b Merge branch 'main' into sdk-next-major 2022-12-21 16:22:28 +01:00
Pierre-Louis Mercereau
add3c2c10e Merge branch 'sdk-next-major' into refactor/cross-fetch 2022-12-21 14:29:00 +01:00
Pierre-Louis Mercereau
dd29b06260 Merge branch 'main' into sdk-next-major 2022-12-21 14:27:48 +01:00
Pierre-Louis Mercereau
490cb25a0f Merge branch 'sdk-next-major' into refactor/cross-fetch 2022-12-20 10:13:18 +01:00
Pierre-Louis Mercereau
0df0dd741e Merge branch 'main' into sdk-next-major 2022-12-20 10:12:56 +01:00
Pierre-Louis Mercereau
2172946879 Merge branch 'sdk-next-major' into refactor/cross-fetch 2022-12-20 09:17:50 +01:00
Pierre-Louis Mercereau
40e50f0e75 Merge branch 'main' into sdk-next-major 2022-12-20 09:15:30 +01:00
Pierre-Louis Mercereau
65cf0888b5 chore: extract patch changes 2022-12-16 12:14:38 +01:00
Pierre-Louis Mercereau
21833019ca chore: correct error from several mergers 2022-12-14 21:14:48 +01:00
Pierre-Louis Mercereau
b3171ba3e9 chore: remove lockfile 2022-12-14 20:40:16 +01:00
Pierre-Louis Mercereau
6f01f19d02 refactor: remove docgen 2022-12-14 20:33:03 +01:00
Pierre-Louis Mercereau
ce92b01eac Merge branch 'main' into sdk-next-major 2022-12-14 19:25:15 +01:00
Pierre-Louis Mercereau
e24a177434 Merge branch 'main' into sdk-next-major 2022-12-14 19:06:50 +01:00
Pierre-Louis Mercereau
56a52b6d48 chore: remove duplicate changesets 2022-12-12 16:45:42 +01:00
Pierre-Louis Mercereau
92bfa8c723 chore: phrasing 2022-12-12 16:03:07 +01:00
Pierre-Louis Mercereau
2a52aaa4a6 Merge branch 'sdk-next-major' into refactor/cross-fetch 2022-12-12 14:46:39 +01:00
Pierre-Louis Mercereau
8280a3e9d8 chore: use explicit changeset links 2022-12-12 14:30:38 +01:00
Pierre-Louis Mercereau
523f60bf68 Merge branch 'sdk-next-major' into refactor/cross-fetch 2022-12-12 14:20:28 +01:00
Pierre-Louis Mercereau
19b11d4084 chore: details on removed deprecations 2022-12-12 14:15:08 +01:00
Pierre-Louis Mercereau
805bae1507 chore: bump nextjs as major as React removed deprecated methods are reexported 2022-12-12 13:36:25 +01:00
Pierre-Louis Mercereau
f6c014c06f chore: correct changeset 2022-12-12 13:34:19 +01:00
Pierre-Louis Mercereau
c5794f4596 chore: improve changesets 2022-12-12 13:32:09 +01:00
Pierre-Louis Mercereau
fc28817380 chore: explain xhr in changeset 2022-12-12 10:06:56 +01:00
Pierre-Louis Mercereau
80bbd3a165 chore: changesets 2022-12-12 10:02:21 +01:00
Pierre-Louis Mercereau
7a10617a72 test: simplify email verification test 2022-12-12 09:26:02 +01:00
Pierre-Louis Mercereau
f0b6dca1a5 Merge branch 'sdk-next-major' into refactor/cross-fetch 2022-12-08 14:28:03 +01:00
Szilárd Dóró
5db20adc34 fix(react): eliminate error when testing the project 2022-12-08 12:01:05 +01:00
Pierre-Louis Mercereau
12dc41a517 test: TypedDocumentNode 2022-12-08 09:07:46 +01:00
Pierre-Louis Mercereau
768fd56891 feat: graphql-typed-document-node 2022-12-07 21:53:21 +01:00
Pierre-Louis Mercereau
8a508cb1cc chore: run CI on evey pull request, not only the ones to main 2022-12-07 20:47:26 +01:00
Pierre-Louis Mercereau
34f6a8eef4 refactor: upload progress with XHR 2022-12-07 19:48:34 +01:00
Pierre-Louis Mercereau
c9d2d31a9b feat: nhost.graphql.httpUrl and nhost.graphql.wsUrl 2022-12-07 18:53:58 +01:00
Pierre-Louis Mercereau
68fb23a361 refactor: remove last pieces of axios 2022-12-07 17:16:42 +01:00
Pierre-Louis Mercereau
476139e528 refactor: remove axios from hasura-storage-js, with breaking change 2022-12-07 13:27:34 +01:00
Pierre-Louis Mercereau
6a850818a0 Merge branch 'sdk-next-major' into refactor/cross-fetch 2022-12-07 11:41:55 +01:00
Pilou
3970dbba0d Merge pull request #1342 from nhost/chore/improve-internal-dependencies
chore: improve internal dependencies
2022-12-07 11:40:02 +01:00
Pierre-Louis Mercereau
8ee2166f0d fix: correct nhost clients 2022-12-07 11:08:54 +01:00
Szilárd Dóró
e13500a185 fix(docgen): correct class references when used as interface members 2022-12-07 10:47:04 +01:00
Pierre-Louis Mercereau
411f574a51 refactor: re-export @nhost/react in @nhost/nextjs 2022-12-07 09:47:45 +01:00
Szilárd Dóró
7fc91b992e chore: cleanup dependencies, fix references 2022-12-07 08:59:06 +01:00
Pierre-Louis Mercereau
b840012be0 fix: correct fetch errors 2022-12-07 08:56:11 +01:00
Pierre-Louis Mercereau
645c51a9dc refactor: 💡 replace axios by cross-fetch, step 1
Changes not done yet in `nhost.auth.graphql` (will soon use
graphql-request instead) and the upload machines (depends on prrogress)
2022-12-06 22:08:46 +01:00
Pierre-Louis Mercereau
0ce6f05539 Merge branch 'sdk-next-major' into chore/improve-internal-dependencies 2022-12-06 14:10:19 +01:00
Pierre-Louis Mercereau
8b1188af53 Merge branch 'main' into sdk-next-major 2022-12-06 14:08:55 +01:00
Pilou
12b01f8dee Merge pull request #1298 from nhost/fix/react-strict-mode
fix: react strict mode
2022-12-06 14:06:07 +01:00
Pierre-Louis Mercereau
60f4faf409 chore: remove @nhost/core from changeset 2022-12-06 13:05:45 +01:00
Pierre-Louis Mercereau
528dff3f1b Merge branch 'sdk-next-major' into fix/react-strict-mode 2022-12-06 12:52:33 +01:00
Pierre-Louis Mercereau
d429fb4a3e Merge branch 'sdk-next-major' into chore/improve-internal-dependencies 2022-12-06 12:41:25 +01:00
Pilou
816c916709 Merge pull request #1233 from nhost/docs/hide-old-deprecations
chore: remove deprecated methods
2022-12-06 12:40:42 +01:00
Pierre-Louis Mercereau
b7a2b8b537 docs: update installation instructions 2022-12-06 10:50:40 +01:00
Pierre-Louis Mercereau
261d8cf434 merge 2022-12-06 10:10:13 +01:00
Pierre-Louis Mercereau
41f49bde76 Merge branch 'sdk-next-major' into chore/improve-internal-dependencies 2022-12-06 10:06:25 +01:00
Pilou
65f685bdb2 Update packages/core/src/client.ts
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2022-12-06 09:57:29 +01:00
Pilou
f52a7f4aac Update packages/core/src/machines/index.ts
Co-authored-by: Szilárd Dóró <doroszilard@icloud.com>
2022-12-06 09:57:01 +01:00
Pierre-Louis Mercereau
e71b9903d9 Merge branch 'sdk-next-major' into docs/hide-old-deprecations 2022-12-06 09:50:40 +01:00
Pilou
325fd08aef Merge pull request #1246 from nhost/refactor/merge-core
refactor: decommission `@nhost/core`
2022-12-06 09:46:50 +01:00
Pierre-Louis Mercereau
3888704464 chore: remove comment 2022-12-06 09:23:31 +01:00
Pierre-Louis Mercereau
38e8a10a29 chore: 🤖 remove lockfile 2022-12-06 09:19:02 +01:00
Pierre-Louis Mercereau
d8545eae12 chore: 🤖 add changeset 2022-12-06 09:18:16 +01:00
Pierre-Louis Mercereau
3d5bfd87d2 refactor: simplify 2022-12-06 08:59:11 +01:00
Pierre-Louis Mercereau
e66c5626bd fix: don't fetch session when present in initial state 2022-12-05 23:14:17 +01:00
Pierre-Louis Mercereau
a227c6561e refactor: work with nextjs 2022-12-05 23:09:26 +01:00
Pierre-Louis Mercereau
e885c159df refactor: simplify onStart 2022-12-05 14:53:10 +01:00
Pierre-Louis Mercereau
09fcb74bef refactor: use Johan's function name 2022-12-05 14:31:03 +01:00
Pierre-Louis Mercereau
a089197197 Merge branch 'main' into fix/react-strict-mode 2022-12-05 14:29:27 +01:00
Pierre-Louis Mercereau
34f843875b Merge branch 'main' into fix/react-strict-mode 2022-12-05 12:15:01 +01:00
Pierre-Louis Mercereau
ca278a8c39 Merge branch 'main' into fix/react-strict-mode 2022-12-05 11:54:01 +01:00
Pierre-Louis Mercereau
75603786e0 refactor: 💡 Remove unused immer dependency 2022-12-05 11:43:47 +01:00
Pierre-Louis Mercereau
4e4e699b94 refactor: adapt to nextjs 2022-12-05 11:39:36 +01:00
Pierre-Louis Mercereau
da31fa9fba ci: explicit pnpm run 2022-12-02 20:46:25 +01:00
Pierre-Louis Mercereau
95e2afaf47 Merge branch 'main' into fix/react-strict-mode 2022-12-02 15:46:13 +01:00
Pierre-Louis Mercereau
958a56dde9 fix: adapt to nextjs 2022-12-02 15:11:08 +01:00
Pierre-Louis Mercereau
74cb15930e Merge branch 'main' into fix/react-strict-mode 2022-12-02 13:35:53 +01:00
Pierre-Louis Mercereau
aa37a98424 Merge branch 'main' into fix/react-strict-mode 2022-12-02 13:27:37 +01:00
Pierre-Louis Mercereau
11cbdda3a5 Merge branch 'main' into fix/react-strict-mode 2022-12-02 13:20:55 +01:00
Pierre-Louis Mercereau
6d1f4adf10 chore: update changeset 2022-12-02 13:16:31 +01:00
Pierre-Louis Mercereau
ddbc50c15e refactor: avoid ambiguous interpreter getter/setter 2022-12-02 13:15:04 +01:00
Pierre-Louis Mercereau
b2cbf570a3 fix: 🐛 don't throw error when re-setting the interpreter 2022-12-02 12:08:52 +01:00
Pierre-Louis Mercereau
22b8e65031 fix: 🐛 onstart only when interpreter is initialised 2022-12-02 11:26:14 +01:00
Pierre-Louis Mercereau
63c94d2036 chore: fine-tune (peer) dependencies 2022-11-30 12:56:46 +01:00
Pierre-Louis Mercereau
010df48c1e chore: remove deprecations 2022-11-28 20:44:24 +01:00
Pierre-Louis Mercereau
fdc11db93d Merge branch 'main' into docs/hide-old-deprecations 2022-11-28 20:18:26 +01:00
Pierre-Louis Mercereau
cb4749f168 chore: fix lint 2022-11-28 15:53:54 +01:00
Pierre-Louis Mercereau
46a8fcf471 docs: hide > 6m.o. deprecations in the generated documentation 2022-11-28 15:34:09 +01:00
142 changed files with 1704 additions and 1469 deletions

View File

@@ -40,14 +40,14 @@ runs:
- shell: bash
name: Build packages
if: ${{ inputs.BUILD == 'all' }}
run: pnpm build:all
run: pnpm run build:all
env:
TURBO_TOKEN: ${{ inputs.TURBO_TOKEN }}
TURBO_TEAM: ${{ inputs.TURBO_TEAM }}
- shell: bash
name: Build everything in the monorepo
if: ${{ inputs.BUILD == 'default' }}
run: pnpm build
run: pnpm run build
env:
TURBO_TOKEN: ${{ inputs.TURBO_TOKEN }}
TURBO_TEAM: ${{ inputs.TURBO_TEAM }}

View File

@@ -8,7 +8,6 @@ on:
- '**.md'
- 'LICENSE'
pull_request:
branches: [main]
types: [opened, synchronize]
paths-ignore:
- 'assets/**'
@@ -56,7 +55,7 @@ jobs:
| xargs -I@ realpath --relative-to=$PWD @ \
| xargs -I@ jq "if (.scripts.e2e | length) != 0 then {name: .name, path: \"@\"} else null end" @/package.json \
| awk "!/null/" \
| jq -c --slurp)
| jq -c --slurp 'map(select(length > 0))')
echo "matrix=$PACKAGES" >> $GITHUB_OUTPUT
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}

View File

@@ -45,6 +45,9 @@
"@nhost/docgen": [
"../packages/docgen/src/index.ts"
],
"@nhost/graphql-js": [
"../packages/graphql-js/src/index.ts"
],
"@nhost/hasura-auth-js": [
"../packages/hasura-auth-js/src/index.ts"
],

View File

@@ -1,7 +1,6 @@
import replace from '@rollup/plugin-replace'
import fs from 'fs'
import path from 'path'
import { defineConfig } from 'vite'
import dts from 'vite-plugin-dts'
import tsconfigPaths from 'vite-tsconfig-paths'
@@ -61,7 +60,6 @@ export default defineConfig({
'@apollo/client/utilities': '@apollo/client/utilities',
'graphql-ws': 'graphql-ws',
xstate: 'xstate',
axios: 'axios',
'js-cookie': 'Cookies',
react: 'React',
'react-dom': 'ReactDOM',

View File

@@ -1,5 +1,36 @@
# @nhost/dashboard
## 0.11.16
### Patch Changes
- b755e908: fix(dashboard): use correct date for last seen
- 2d9145f9: chore(deps): revert GraphQL client
- 1ddf704c: fix(dashboard): don't show false positive message for failed user creation
- @nhost/react-apollo@5.0.3
- @nhost/nextjs@1.13.8
## 0.11.15
### Patch Changes
- @nhost/react-apollo@5.0.2
- @nhost/nextjs@1.13.7
## 0.11.14
### Patch Changes
- 2cc18dcb: fix(dashboard): prevent permission editor dropdown from being always open
## 0.11.13
### Patch Changes
- 3343a363: chore(dashboard): bump `@testing-library/react` to v14 and `@testing-library/dom` to v9
- @nhost/react-apollo@5.0.1
- @nhost/nextjs@1.13.6
## 0.11.12
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/dashboard",
"version": "0.11.12",
"version": "0.11.16",
"private": true,
"scripts": {
"preinstall": "npx only-allow pnpm",
@@ -44,7 +44,6 @@
"@tanstack/react-table": "^8.5.30",
"@tanstack/react-virtual": "^3.0.0-beta.23",
"analytics-node": "^6.2.0",
"axios": "^0.27.2",
"bcryptjs": "^2.4.3",
"clsx": "^1.2.1",
"cross-fetch": "^3.1.5",
@@ -98,9 +97,9 @@
"@storybook/manager-webpack5": "^6.5.14",
"@storybook/react": "^6.5.14",
"@storybook/testing-library": "^0.0.13",
"@testing-library/dom": "^8.19.0",
"@testing-library/dom": "^9.0.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/lodash.debounce": "^4.0.7",
"@types/node": "^16.11.7",

View File

@@ -213,7 +213,6 @@ export default function RuleValueInput({
freeSolo={!isHasuraInput}
autoSelect={!isHasuraInput}
autoHighlight={isHasuraInput}
open
isOptionEqualToValue={(option, value) => {
if (typeof value === 'string') {
return option.value.toLowerCase() === (value as string).toLowerCase();

View File

@@ -277,7 +277,7 @@ export default function FilesDataGrid(props: FilesDataGridProps) {
}
if (fileError) {
throw fileError;
throw new Error(fileError.message);
}
triggerToast(`File has been uploaded successfully (${fileMetadata?.id})`);

View File

@@ -67,7 +67,7 @@ export function InviteAnnounce() {
triggerToast('An error occurred when trying to accept the invitation.');
return setSubmitState({
error: res.error,
error: new Error(res.error.message),
loading: false,
});
}

View File

@@ -107,7 +107,7 @@ export default function SystemEnvironmentVariableSettings() {
),
},
{ key: 'NHOST_AUTH_URL', value: appClient.auth.url },
{ key: 'NHOST_GRAPHQL_URL', value: appClient.graphql.url },
{ key: 'NHOST_GRAPHQL_URL', value: appClient.graphql.httpUrl },
{ key: 'NHOST_STORAGE_URL', value: appClient.storage.url },
{ key: 'NHOST_FUNCTIONS_URL', value: appClient.functions.url },
];

View File

@@ -6,7 +6,7 @@ import Input from '@/ui/v2/Input';
import generateAppServiceUrl from '@/utils/common/generateAppServiceUrl';
import { getToastStyleProps } from '@/utils/settings/settingsConstants';
import { yupResolver } from '@hookform/resolvers/yup';
import axios from 'axios';
import fetch from 'cross-fetch';
import { useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { toast } from 'react-hot-toast';
@@ -79,28 +79,36 @@ export default function CreateUserForm({
try {
await toast.promise(
axios.post(signUpUrl, {
email,
password,
fetch(signUpUrl, {
method: 'POST',
body: JSON.stringify({ email, password }),
}).then(async (res) => {
const data = await res.json();
if (res.ok) {
return data;
}
if (res.status === 409) {
setError('email', { message: data?.message });
}
throw new Error(data?.message || 'Something went wrong.');
}),
{
loading: 'Creating user...',
success: 'User created successfully.',
error: 'An error occurred while trying to create the user.',
error: (arg) =>
arg?.message
? `Error: ${arg.message}`
: 'An error occurred while trying to create the user.',
},
getToastStyleProps(),
);
onSuccess?.();
} catch (error) {
if (error.response?.status === 409) {
setError('email', {
message: error.response.data.message,
});
return;
}
setCreateUserFormError(
new Error(error.response.data.message || 'Something went wrong.'),
);
} catch {
// Note: Error is already handled by toast.promise
}
}
@@ -137,7 +145,7 @@ export default function CreateUserForm({
{createUserFormError && (
<Alert
severity="error"
className="grid items-center justify-between grid-flow-col px-4 py-3"
className="grid grid-flow-col items-center justify-between px-4 py-3"
>
<span className="text-left">
<strong>Error:</strong> {createUserFormError.message}

View File

@@ -268,7 +268,7 @@ export default function EditUserForm({
Created At
</InputLabel>
<Text className="col-span-3 font-medium">
{format(new Date(user.createdAt), 'yyyy-MM-dd hh:mm:ss')}
{format(new Date(user.createdAt), 'yyyy-MM-dd HH:mm:ss')}
</Text>
<InputLabel as="h3" className="col-span-1 self-center ">
@@ -276,7 +276,7 @@ export default function EditUserForm({
</InputLabel>
<Text className="col-span-3 font-medium">
{user.lastSeen
? `${format(new Date(user.lastSeen), 'yyyy-mm-dd hh:mm:ss')}`
? `${format(new Date(user.lastSeen), 'yyyy-MM-dd HH:mm:ss')}`
: '-'}
</Text>
</Box>

View File

@@ -28,7 +28,7 @@ The GraphQL API is available at `https://[subdomain].graphql.[region].nhost.run/
## GraphQL Clients for JavaScript
The [Nhost JavaScript client](/reference/javascript) comes with a simple [GraphQL client](/reference/javascript/nhost-js/graphql) that works well for the backend or simple applications.
The [Nhost JavaScript client](/reference/javascript) comes with a simple [GraphQL client](/reference/javascript/graphql) that works well for the backend or simple applications.
When building more complex frontend applications, we recommend using a more advanced GraphQL client such as:

View File

@@ -10,8 +10,8 @@ In this section:
- [Overview](/reference/javascript)
- [Authentication](/reference/javascript/auth)
- [Storage](/reference/javascript/storage)
- [Functions](/reference/javascript/nhost-js/functions)
- [GraphQL](/reference/javascript/nhost-js/graphql)
- [Functions](/reference/javascript/functions)
- [GraphQL](/reference/javascript/graphql)
### React

View File

@@ -0,0 +1,60 @@
---
title: call()
sidebar_label: call()
slug: /reference/javascript/functions/call
description: Use `nhost.functions.call` to call (sending a POST request to) a serverless function.
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/nhost-js/src/clients/functions/index.ts#L55
---
# `call()`
## Overload 1 of 2
Use `nhost.functions.call` to call (sending a POST request to) a serverless function.
:::caution Deprecated
Axios will be replaced by cross-fetch in the near future. Only the headers configuration will be kept.
:::
### Parameters
---
**<span className="parameter-name">url</span>** <span className="optional-status">required</span> <code>string</code>
---
**<span className="parameter-name">data</span>** <span className="optional-status">optional</span> <code>D</code>
---
**<span className="parameter-name">config</span>** <span className="optional-status">optional</span> <code>AxiosRequestConfig&lt;any&gt; &amp; { useAxios: "true" } &amp; [`NhostFunctionCallConfig`](/reference/javascript/functions/types/nhost-function-call-config) &amp; { useAxios: "true" }</code>
---
## Overload 2 of 2
Use `nhost.functions.call` to call (sending a POST request to) a serverless function.
```ts
await nhost.functions.call('send-welcome-email', {
email: 'joe@example.com',
name: 'Joe Doe'
})
```
### Parameters
---
**<span className="parameter-name">url</span>** <span className="optional-status">required</span> <code>string</code>
---
**<span className="parameter-name">data</span>** <span className="optional-status">required</span> <code>D</code>
---
**<span className="parameter-name">config</span>** <span className="optional-status">optional</span> <code>[`NhostFunctionCallConfig`](/reference/javascript/functions/types/nhost-function-call-config) &amp; { useAxios: "false" }</code>
---

View File

@@ -0,0 +1,23 @@
---
title: setAccessToken()
sidebar_label: setAccessToken()
slug: /reference/javascript/functions/set-access-token
description: Use `nhost.functions.setAccessToken` to a set an access token to be used in subsequent functions requests. Note that if you're signin in users with `nhost.auth.signIn()` the access token will be set automatically.
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/nhost-js/src/clients/functions/index.ts#L155
---
# `setAccessToken()`
Use `nhost.functions.setAccessToken` to a set an access token to be used in subsequent functions requests. Note that if you're signin in users with `nhost.auth.signIn()` the access token will be set automatically.
```ts
nhost.functions.setAccessToken('some-access-token')
```
## Parameters
---
**<span className="parameter-name">accessToken</span>** <span className="optional-status">required</span> <code>undefined &#124; string</code>
---

View File

@@ -0,0 +1,22 @@
---
title: NhostFunctionsClient
sidebar_label: Functions
description: No description provided.
slug: /reference/javascript/functions
custom_edit_url: https://github.com/nhost/nhost/edit/main/docs/docs/reference/javascript/functions/index.mdx
---
# `NhostFunctionsClient`
## Parameters
---
**<span className="parameter-name">params</span>** <span className="optional-status">required</span> [`NhostFunctionsConstructorParams`](/reference/javascript/functions/types/nhost-functions-constructor-params)
| Property | Type | Required | Notes |
| :--------------------------------------------------------------------------------------------- | :------------------ | :------: | :---------------------------------------------------------------------------------------- |
| <span className="parameter-name"><span className="light-grey">params.</span>url</span> | <code>string</code> | ✔️ | Serverless Functions endpoint. |
| <span className="parameter-name"><span className="light-grey">params.</span>adminSecret</span> | <code>string</code> | | Admin secret. When set, it is sent as an `x-hasura-admin-secret` header for all requests. |
---

View File

@@ -0,0 +1,19 @@
---
title: NhostFunctionCallConfig
sidebar_label: NhostFunctionCallConfig
description: Subset of RequestInit parameters that are supported by the functions client
displayed_sidebar: referenceSidebar
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/nhost-js/src/clients/functions/types.ts#L41
---
# `NhostFunctionCallConfig`
Subset of RequestInit parameters that are supported by the functions client
## Parameters
---
**<span className="parameter-name">headers</span>** <span className="optional-status">optional</span> <code>Record&lt;string, string&gt;</code>
---

View File

@@ -0,0 +1,15 @@
---
title: NhostFunctionCallResponse
sidebar_label: NhostFunctionCallResponse
description: No description provided.
displayed_sidebar: referenceSidebar
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/nhost-js/src/clients/functions/types.ts#L15
---
# `NhostFunctionCallResponse`
```ts
type NhostFunctionCallResponse =
| { res: { data: T; status: number; statusText: string }; error: null }
| { res: null; error: ErrorPayload }
```

View File

@@ -0,0 +1,25 @@
---
title: NhostFunctionsConstructorParams
sidebar_label: NhostFunctionsConstructorParams
description: No description provided.
displayed_sidebar: referenceSidebar
custom_edit_url: https://github.com/nhost/nhost/edit/main/packages/nhost-js/src/clients/functions/types.ts#L4
---
# `NhostFunctionsConstructorParams`
## Parameters
---
**<span className="parameter-name">url</span>** <span className="optional-status">required</span> <code>string</code>
Serverless Functions endpoint.
---
**<span className="parameter-name">adminSecret</span>** <span className="optional-status">optional</span> <code>string</code>
Admin secret. When set, it is sent as an `x-hasura-admin-secret` header for all requests.
---

View File

@@ -10,12 +10,12 @@ The Nhost JavaScript client is the primary way of interacting with your Nhost pr
- [Authentication](/reference/javascript/auth)
- [Storage](/reference/javascript/storage)
- [Functions](/reference/javascript/nhost-js/functions)
- [GraphQL](/reference/javascript/nhost-js/graphql)
- [Functions](/reference/javascript/functions)
- [GraphQL](/reference/javascript/graphql)
## Installation
Install the the Nhost client together with GraphQL:
Install the Nhost client together with GraphQL:
<Tabs groupId="package-manager">
<TabItem value="npm" label="npm" default>

View File

@@ -111,12 +111,12 @@ const sidebars = {
label: 'Functions',
link: {
type: 'doc',
id: 'reference/docgen/javascript/nhost-js/content/nhost-functions-client/index'
id: 'reference/javascript/functions/index'
},
items: [
{
type: 'autogenerated',
dirName: 'reference/docgen/javascript/nhost-js/content/nhost-functions-client/content'
dirName: 'reference/javascript/functions/content'
}
]
},
@@ -125,12 +125,12 @@ const sidebars = {
label: 'GraphQL',
link: {
type: 'doc',
id: 'reference/docgen/javascript/nhost-js/content/nhost-graphql-client/index'
id: 'reference/docgen/javascript/graphql/content/nhost-graphql-client/index'
},
items: [
{
type: 'autogenerated',
dirName: 'reference/docgen/javascript/nhost-js/content/nhost-graphql-client/content'
dirName: 'reference/docgen/javascript/graphql/content/nhost-graphql-client/content'
}
]
}

View File

@@ -18,6 +18,7 @@ export function authProtected(Comp) {
if (isLoading) {
return <div>Loading...</div>
}
return <Comp {...props} />
}
}

View File

@@ -35,8 +35,8 @@
"@types/react": "18.0.25",
"@xstate/inspect": "^0.6.2",
"eslint-config-next": "12.0.10",
"typescript": "4.5.5",
"typescript": "^4.8.2",
"ws": "^8.8.1",
"xstate": "^4.33.5"
}
}
}

View File

@@ -4,7 +4,7 @@
"version": "0.0.5",
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"build": "vite build",
"preview": "vite preview",
"prettier": "prettier --check src/",
"prettier:fix": "prettier --write src/",
@@ -34,7 +34,7 @@
"@vitejs/plugin-vue": "^4.0.0",
"@xstate/inspect": "^0.6.2",
"sass": "1.32.0",
"typescript": "^4.8.4",
"typescript": "4.9.4",
"vite": "^4.0.2",
"vue-tsc": "^0.38.9"
},

View File

@@ -1,5 +1,28 @@
# @nhost/apollo
## 5.0.2
### Patch Changes
- Updated dependencies [2d9145f9]
- @nhost/nhost-js@2.0.2
## 5.0.1
### Patch Changes
- @nhost/nhost-js@2.0.1
## 5.0.0
### Patch Changes
- Updated dependencies [c9d2d31a]
- Updated dependencies [80bbd3a1]
- Updated dependencies [80bbd3a1]
- Updated dependencies [2949ff0f]
- @nhost/nhost-js@2.0.0
## 4.13.4
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/apollo",
"version": "4.13.4",
"version": "5.0.2",
"description": "Nhost Apollo Client library",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,28 @@
# @nhost/react-apollo
## 5.0.3
### Patch Changes
- @nhost/apollo@5.0.2
- @nhost/react@2.0.2
## 5.0.2
### Patch Changes
- @nhost/apollo@5.0.1
- @nhost/react@2.0.1
## 5.0.1
### Patch Changes
- Updated dependencies [19b11d40]
- Updated dependencies [19b11d40]
- @nhost/react@2.0.0
- @nhost/apollo@5.0.0
## 4.13.5
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/react-apollo",
"version": "4.13.5",
"version": "5.0.3",
"description": "Nhost React Apollo client",
"license": "MIT",
"keywords": [

View File

@@ -1,5 +1,25 @@
# @nhost/react-urql
## 2.0.2
### Patch Changes
- @nhost/react@2.0.2
## 2.0.1
### Patch Changes
- @nhost/react@2.0.1
## 2.0.0
### Patch Changes
- Updated dependencies [19b11d40]
- Updated dependencies [19b11d40]
- @nhost/react@2.0.0
## 1.0.5
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/react-urql",
"version": "1.0.5",
"version": "2.0.2",
"description": "Nhost React URQL client",
"license": "MIT",
"keywords": [

View File

@@ -0,0 +1,9 @@
const base = require('../../config/.eslintrc.js')
module.exports = {
...base,
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname
},
ignorePatterns: [...base.ignorePatterns, 'functions/**/*.ts']
}

View File

@@ -0,0 +1,14 @@
# @nhost/graphql-js
## 0.0.3
### Patch Changes
- 2d9145f9: chore(deps): revert GraphQL client
## 0.0.2
### Patch Changes
- 2200a0ed: Correct type inference on snake case operations
- 3b48a627: Improve readme instructions

View File

@@ -0,0 +1,16 @@
<h1 align="center">@nhost/graphql-js</h1>
<h2 align="center">Nhost GraphQL client</h2>
<p align="center">
<img alt="npm" src="https://img.shields.io/npm/v/@nhost/graphql-js">
<img alt="npm" src="https://img.shields.io/npm/dm/@nhost/graphql-js">
<a href="LICENSE">
<img src="https://img.shields.io/badge/license-MIT-yellow.svg" alt="license: MIT" />
</a>
</p>
Nhost GraphQL client.
## Documentation
[https://docs.nhost.io/reference/javascript/graphql](https://docs.nhost.io/reference/javascript/graphql)

View File

@@ -0,0 +1,10 @@
{
"title": "GraphQL",
"path": "./.docgen/graphql.json",
"output": "../../docs/docs/reference/docgen/javascript/graphql",
"root": "reference/docgen/javascript/graphql",
"slug": "/reference/javascript/graphql",
"sidebarConfig": "referenceSidebar",
"baseEditUrl": "https://github.com/nhost/nhost/edit/main/packages",
"cleanup": true
}

View File

@@ -7,8 +7,8 @@
"sort": [
"source-order"
],
"json": "./.docgen/nhost-js.json",
"name": "Nhost JS",
"json": "./.docgen/graphql.json",
"name": "GraphQL",
"readme": "none",
"githubPages": false,
"cleanOutputDir": false,
@@ -26,6 +26,9 @@
"@nhost/hasura-storage-js": [
"../hasura-storage-js/src/index.ts"
],
"@nhost/graphql-js": [
"../graphql-js/src/index.ts"
],
"@nhost/nextjs": [
"../nextjs/src/index.ts"
],

View File

@@ -0,0 +1,65 @@
{
"name": "@nhost/graphql-js",
"version": "0.0.3",
"description": "Nhost GraphQL client",
"license": "MIT",
"keywords": [
"nhost",
"hasura",
"graphql"
],
"author": "Nhost",
"homepage": "https://nhost.io",
"bugs": "https://github.com/nhost/nhost/issues",
"repository": {
"type": "git",
"url": "https://github.com/nhost/nhost.git"
},
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"source": "src/index.ts",
"files": [
"dist",
"umd",
"README.md"
],
"exports": {
"./package.json": "./package.json",
".": {
"import": {
"node": "./dist/index.cjs.js",
"default": "./dist/index.esm.js"
},
"require": "./dist/index.cjs.js"
}
},
"publishConfig": {
"access": "public"
},
"scripts": {
"dev": "vite build",
"build": "run-p build:lib build:umd",
"build:lib": "vite build",
"build:umd": "vite build --config ../../config/vite.lib.umd.config.js",
"prettier": "prettier --check src/",
"prettier:fix": "prettier --write src/",
"lint": "eslint . --ext .ts,.tsx",
"lint:fix": "eslint . --ext .ts,.tsx --fix",
"verify": "run-p prettier lint",
"verify:fix": "run-p prettier:fix lint:fix",
"typedoc": "typedoc --options ./graphql.typedoc.json --tsconfig ./typedoc.tsconfig.json",
"docgen": "pnpm typedoc && docgen --config ./graphql.docgen.json"
},
"dependencies": {
"@graphql-typed-document-node/core": "^3.1.1",
"cross-fetch": "^3.1.5"
},
"peerDependencies": {
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
},
"devDependencies": {
"@nhost/docgen": "workspace:*",
"graphql": "16.6.0"
}
}

View File

@@ -0,0 +1,205 @@
import { TypedDocumentNode } from '@graphql-typed-document-node/core'
import fetch from 'cross-fetch'
import { parseRequestArgs } from './parse-args'
import { resolveRequestDocument } from './resolve-request-document'
import {
NhostGraphqlConstructorParams,
NhostGraphqlRequestConfig,
NhostGraphqlRequestResponse,
RemoveIndex,
RequestDocument,
RequestOptions,
Variables
} from './types'
/**
* @alias GraphQL
*/
export class NhostGraphqlClient {
readonly _url: string
private accessToken: string | null
private adminSecret?: string
constructor(params: NhostGraphqlConstructorParams) {
const { url, adminSecret } = params
this._url = url
this.accessToken = null
this.adminSecret = adminSecret
}
/**
* Use `nhost.graphql.request` to send a GraphQL request. For more serious GraphQL usage we recommend using a GraphQL client such as Apollo Client (https://www.apollographql.com/docs/react).
*
* @example
* ```ts
* const CUSTOMERS = gql`
* query {
* customers {
* id
* name
* }
* }
* `
* const { data, error } = await nhost.graphql.request(CUSTOMERS)
* ```
*
* @docs https://docs.nhost.io/reference/javascript/graphql/request
*/
request<T = any, V = Variables>(
document: RequestDocument | TypedDocumentNode<T, V>,
...variablesAndRequestHeaders: V extends Record<any, never>
? [variables?: V, config?: NhostGraphqlRequestConfig]
: keyof RemoveIndex<V> extends never
? [variables?: V, config?: NhostGraphqlRequestConfig]
: [variables: V, config?: NhostGraphqlRequestConfig]
): Promise<NhostGraphqlRequestResponse<T>>
async request<T = any, V extends Variables = Variables>(
options: RequestOptions<V, T>
): Promise<NhostGraphqlRequestResponse<T>>
async request<T = any, V extends Variables = Variables>(
documentOrOptions: RequestDocument | TypedDocumentNode<T, V> | RequestOptions<V>,
...variablesAndRequestHeaders: V extends Record<any, never>
? [variables?: V, config?: NhostGraphqlRequestConfig]
: keyof RemoveIndex<V> extends never
? [variables?: V, config?: NhostGraphqlRequestConfig]
: [variables: V, config?: NhostGraphqlRequestConfig]
): Promise<NhostGraphqlRequestResponse<T>> {
const [variables, config] = variablesAndRequestHeaders
const requestOptions = parseRequestArgs(documentOrOptions, variables, config)
const { headers, ...otherOptions } = config || {}
const { query, operationName } = resolveRequestDocument(requestOptions.document)
try {
const response = await fetch(this.httpUrl, {
method: 'POST',
body: JSON.stringify({
operationName,
query,
variables
}),
headers: {
'Content-Type': 'application/json',
...this.generateAccessTokenHeaders(),
...headers
},
...otherOptions
})
if (!response.ok) {
return {
data: null,
error: {
error: response.statusText,
message: response.statusText,
status: response.status
}
}
}
const { data, errors } = await response.json()
if (errors) {
return {
data: null,
error: errors
}
}
if (typeof data !== 'object' || Array.isArray(data) || data === null) {
return {
data: null,
error: {
error: 'invalid-response',
message: 'incorrect response data from GraphQL server',
status: 0
}
}
}
return { data, error: null }
} catch (e) {
const error = e as Error
return {
data: null,
error: {
message: error.message,
status: error.name === 'AbortError' ? 0 : 500,
error: error.name === 'AbortError' ? 'abort-error' : 'unknown'
}
}
}
}
/**
* Use `nhost.graphql.httpUrl` to get the GraphQL HTTP URL.
* @example
* ```ts
* const url = nhost.graphql.httpUrl;
* ```
*
* @docs https://docs.nhost.io/reference/javascript/graphql/get-http-url
*/
get httpUrl(): string {
return this._url
}
/**
* Use `nhost.graphql.wsUrl` to get the GraphQL WebSocket URL.
* @example
* ```ts
* const url = nhost.graphql.wsUrl;
* ```
*
* @docs https://docs.nhost.io/reference/javascript/graphql/get-ws-url
*/
get wsUrl(): string {
return this._url.replace(/^(http)(s?):\/\//, 'ws$2://')
}
/**
* Use `nhost.graphql.url` to get the GraphQL URL.
* @deprecated Use `nhost.graphql.httpUrl` and `nhost.graphql.wsUrl` instead.
*/
get url(): string {
return this._url
}
/**
* Use `nhost.graphql.getUrl()` to get the GraphQL URL.
* @deprecated Use `nhost.graphql.httpUrl` and `nhost.graphql.wsUrl` instead.
*/
getUrl(): string {
return this._url
}
/**
* Use `nhost.graphql.setAccessToken` to a set an access token to be used in subsequent graphql requests. Note that if you're signin in users with `nhost.auth.signIn()` the access token will be set automatically.
*
* @example
* ```ts
* nhost.graphql.setAccessToken('some-access-token')
* ```
*
* @docs https://docs.nhost.io/reference/javascript/graphql/set-access-token
*/
setAccessToken(accessToken: string | undefined) {
if (!accessToken) {
this.accessToken = null
return
}
this.accessToken = accessToken
}
private generateAccessTokenHeaders(): NhostGraphqlRequestConfig['headers'] {
if (this.adminSecret) {
return {
'x-hasura-admin-secret': this.adminSecret
}
}
if (this.accessToken) {
return {
Authorization: `Bearer ${this.accessToken}`
}
}
return {}
}
}

View File

@@ -0,0 +1 @@
export * from './client'

View File

@@ -0,0 +1,17 @@
import { RequestDocument, RequestOptions, Variables } from './types'
export function parseRequestArgs<V extends Variables = Variables>(
documentOrOptions: RequestDocument | RequestOptions<V>,
variables?: V,
config?: RequestInit
): RequestOptions<V> {
return (
(documentOrOptions as RequestOptions<V>).document
? documentOrOptions
: {
document: documentOrOptions,
variables,
config
}
) as RequestOptions<V>
}

View File

@@ -0,0 +1,42 @@
import { DocumentNode, OperationDefinitionNode, parse, print } from 'graphql'
import { RequestDocument } from './types'
/**
* helpers
*/
function extractOperationName(document: DocumentNode): string | undefined {
let operationName = undefined
const operationDefinitions = document.definitions.filter(
(definition) => definition.kind === 'OperationDefinition'
) as OperationDefinitionNode[]
if (operationDefinitions.length === 1) {
operationName = operationDefinitions[0].name?.value
}
return operationName
}
export function resolveRequestDocument(document: RequestDocument): {
query: string
operationName?: string
} {
if (typeof document === 'string') {
let operationName = undefined
try {
const parsedDocument = parse(document)
operationName = extractOperationName(parsedDocument)
} catch (err) {
// Failed parsing the document, the operationName will be undefined
}
return { query: document, operationName }
}
const operationName = extractOperationName(document)
return { query: print(document), operationName }
}

View File

@@ -0,0 +1,52 @@
import { TypedDocumentNode } from '@graphql-typed-document-node/core'
import { DocumentNode, GraphQLError } from 'graphql'
// TODO shared with other packages
export type ErrorPayload = {
error: string
status: number
message: string
}
export type RequestOptions<V extends Variables = Variables, T = any> = NhostGraphqlRequestConfig & {
document: RequestDocument | TypedDocumentNode<T, V>
} & (V extends Record<any, never>
? { variables?: V }
: keyof RemoveIndex<V> extends never
? { variables?: V }
: { variables: V })
export type Variables = { [key: string]: any }
export type RequestDocument = string | DocumentNode
export type RemoveIndex<T> = {
[K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]
}
export interface NhostGraphqlConstructorParams {
/**
* GraphQL endpoint.
*/
url: string
/**
* Admin secret. When set, it is sent as an `x-hasura-admin-secret` header for all requests.
*/
adminSecret?: string
}
export type NhostGraphqlRequestResponse<T = unknown> =
| {
data: null
error: GraphQLError[] | ErrorPayload
}
| {
data: T
error: null
}
/** Subset of RequestInit parameters that are supported by the graphql client */
export interface NhostGraphqlRequestConfig {
headers?: Record<string, string>
/** @deprecated Axios has been replaced by cross-fetch. You should now remove this option. */
useAxios?: false
}

View File

@@ -0,0 +1,4 @@
{
"extends": "../../config/tsconfig.base.json",
"include": ["src"]
}

View File

@@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"$schema": "https://json.schemastore.org/tsconfig",
"include": [
"tests/types"
]
}

View File

@@ -0,0 +1,13 @@
import { defineConfig } from 'vite'
import baseConfig from '../../config/vite.lib.config'
export default defineConfig({
...baseConfig,
test: {
...(baseConfig.test || {}),
include: [`tests/e2e/**/*.{spec,test}.{ts,tsx}`],
testTimeout: 30000,
environment: 'node'
}
})

View File

@@ -0,0 +1,5 @@
import { defineConfig } from 'vite'
import baseConfig from '../../config/vite.lib.config'
export default defineConfig(baseConfig)

View File

@@ -0,0 +1,13 @@
import { defineConfig } from 'vite'
import baseConfig from '../../config/vite.lib.config'
export default defineConfig({
...baseConfig,
test: {
...(baseConfig.test || {}),
include: [`tests/unit/**/*.{spec,test}.{ts,tsx}`],
testTimeout: 30000,
environment: 'node'
}
})

View File

@@ -1,5 +1,41 @@
# @nhost/hasura-auth-js
## 2.0.0
### Major Changes
- 19b11d40: Remove the deprecated `AuthCookieClient` and `AuthClientSSR` constructors
Use the `clientStorageType` option instead:
```ts
const nhost = new NhostClient({ clientStorageType: 'cookie' })
```
- 19b11d40: Remove the deprecated `nhost.auth.getJWTToken` method
Use `nhost.auth.getAccessToken()` instead.
- 19b11d40: Remove the deprecated `autoLogin` option
Use `autoSignIn` instead:
```ts
const nhost = new NhostClient({ autoSignIn: true })
```
- 19b11d40: Remove the deprecated `clientStorageGetter` and `clientStorageSetter` options
Use `clientStorageType` and `clientStorage` instead:
```ts
const nhost = new NhostClient({ clientStorageType: 'custom', clientStorage: TODO })
```
### Minor Changes
- 80bbd3a1: Replace `axios` by `cross-fetch`
## 1.12.4
### Patch Changes

View File

@@ -1,7 +1,6 @@
import { faker } from '@faker-js/faker'
import axios from 'axios'
import fetch from 'cross-fetch'
import { afterEach, describe, expect, it } from 'vitest'
import { auth, getHtmlLink, mailhog } from './helpers'
describe('emails', () => {
@@ -22,10 +21,11 @@ describe('emails', () => {
const verifyEmailLink = await getHtmlLink(email, 'verifyEmail')
// verify email
await axios.get(verifyEmailLink, {
maxRedirects: 0,
validateStatus: (status) => status === 302
})
try {
await fetch(verifyEmailLink, { method: 'GET', redirect: 'follow' })
} catch {
// ignore
}
const signInA = await auth.signIn({
email,
@@ -46,10 +46,11 @@ describe('emails', () => {
const changeEmailLink = await getHtmlLink(email, 'emailConfirmChange')
// verify email
await axios.get(changeEmailLink, {
maxRedirects: 0,
validateStatus: (status) => status === 302
})
try {
await fetch(changeEmailLink, { method: 'GET', redirect: 'follow' })
} catch {
// ignore
}
})
it('reset email verification', async () => {
@@ -71,28 +72,21 @@ describe('emails', () => {
expect(signInA.error).toBeTruthy()
expect(signInA.session).toBeNull()
await mailhog.deleteAll()
await auth.sendVerificationEmail({ email })
// make sure onle a single message exists
const messages = await mailhog.messages()
if (!messages) {
throw new Error('no messages')
}
expect(messages.count).toBe(1)
const message = await mailhog.latestTo(email)
expect(message?.subject).toBe('Verify your email')
// test email link
// get verify email link
const verifyEmailLink = await getHtmlLink(email, 'verifyEmail')
// verify email
await axios.get(verifyEmailLink, {
maxRedirects: 0,
validateStatus: (status) => status === 302
})
try {
await fetch(verifyEmailLink, { method: 'GET', redirect: 'follow' })
} catch {
// ignore
}
// sign in should work
const signInB = await auth.signIn({

View File

@@ -1,8 +1,7 @@
import axios from 'axios'
import { load } from 'cheerio'
import fetch from 'cross-fetch'
import createMailhogClient from 'mailhog'
import { expect } from 'vitest'
import { HasuraAuthClient, SignUpParams } from '../src'
const AUTH_BACKEND_URL = 'http://localhost:1337/v1/auth'
@@ -43,11 +42,11 @@ export const signUpAndVerifyUser = async (params: SignUpParams) => {
// get verify email link
const verifyEmailLink = await getHtmlLink(email, 'verifyEmail')
// verify email
await axios.get(verifyEmailLink, {
maxRedirects: 0,
validateStatus: (status) => status === 302
})
try {
await fetch(verifyEmailLink, { method: 'GET', redirect: 'follow' })
} catch {
// ignore
}
}
export const signUpAndInUser = async (params: SignUpParams) => {
@@ -60,10 +59,11 @@ export const signUpAndInUser = async (params: SignUpParams) => {
const verifyEmailLink = await getHtmlLink(email, 'verifyEmail')
// verify email
await axios.get(verifyEmailLink, {
maxRedirects: 0,
validateStatus: (status) => status === 302
})
try {
await fetch(verifyEmailLink, { method: 'GET', redirect: 'follow' })
} catch {
// ignore
}
// sign in
const { session, error } = await auth.signIn({ email, password })

View File

@@ -1,7 +1,6 @@
import { faker } from '@faker-js/faker'
import axios from 'axios'
import fetch from 'cross-fetch'
import { afterEach, describe, expect, it } from 'vitest'
import { auth, getHtmlLink, signUpAndInUser, signUpAndVerifyUser } from './helpers'
describe('passwords', () => {
@@ -48,9 +47,10 @@ describe('passwords', () => {
const resetPasswordLink = await getHtmlLink(email, 'passwordReset')
// verify email
await axios.get(resetPasswordLink, {
maxRedirects: 0,
validateStatus: (status) => status === 302
})
try {
await fetch(resetPasswordLink, { method: 'GET', redirect: 'follow' })
} catch {
// ignore
}
})
})

View File

@@ -1,8 +1,7 @@
import { faker } from '@faker-js/faker'
import axios from 'axios'
import fetch from 'cross-fetch'
import { afterEach, describe, expect, it } from 'vitest'
import { USER_ALREADY_SIGNED_IN } from '../src'
import { auth, getHtmlLink, signUpAndInUser, signUpAndVerifyUser } from './helpers'
describe('sign-in', () => {
@@ -57,10 +56,11 @@ describe('sign-in', () => {
const emailLink = await getHtmlLink(email, 'signinPasswordless')
// verify email
await axios.get(emailLink, {
maxRedirects: 0,
validateStatus: (status) => status === 302
})
try {
await fetch(emailLink, { method: 'GET', redirect: 'follow' })
} catch {
// ignore
}
})
it('should not be possible to sign in with email+password in when already authenticated', async () => {

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/hasura-auth-js",
"version": "1.12.4",
"version": "2.0.0",
"description": "Hasura-auth client",
"license": "MIT",
"keywords": [
@@ -63,7 +63,7 @@
},
"dependencies": {
"@simplewebauthn/browser": "^6.0.0",
"axios": "^1.2.0",
"cross-fetch": "^3.1.5",
"js-cookie": "^3.0.1",
"jwt-decode": "^3.1.2",
"xstate": "^4.33.5"

View File

@@ -75,11 +75,8 @@ export class HasuraAuthClient {
url,
autoRefreshToken = true,
autoSignIn = true,
autoLogin,
clientStorage,
clientStorageType,
clientStorageGetter,
clientStorageSetter,
refreshIntervalTime,
start = true
}: NhostAuthConstructorParams) {
@@ -88,12 +85,10 @@ export class HasuraAuthClient {
backendUrl: url,
clientUrl: (typeof window !== 'undefined' && window.location?.origin) || '',
autoRefreshToken,
autoSignIn: typeof autoLogin === 'boolean' ? autoLogin : autoSignIn,
autoSignIn,
start,
clientStorage,
clientStorageType,
clientStorageGetter,
clientStorageSetter,
refreshIntervalTime
})
}
@@ -552,16 +547,6 @@ export class HasuraAuthClient {
return { isAuthenticated: this.isAuthenticated(), isLoading: false, connectionAttempts }
}
/**
* @internal
* @deprecated Use `nhost.auth.getAccessToken()` instead.
* @docs https://docs.nhost.io/reference/javascript/auth/get-access-token
*/
getJWTToken(): string | undefined {
return this.getAccessToken()
}
/**
* Use `nhost.auth.getAccessToken` to get the access token of the user.
*

View File

@@ -5,7 +5,6 @@ import type {
PublicKeyCredentialRequestOptionsJSON,
RegistrationCredentialJSON
} from '@simplewebauthn/typescript-types'
import type { AxiosRequestConfig } from 'axios'
import { assign, createMachine, InterpreterFrom, send } from 'xstate'
import {
NHOST_JWT_EXPIRES_AT_KEY,
@@ -42,16 +41,14 @@ import {
} from '../../types'
import {
getParameterByName,
nhostApiClient,
removeParameterFromWindow,
rewriteRedirectTo
} from '../../utils'
import {
isValidEmail,
isValidPassword,
isValidPhoneNumber,
isValidTicket
} from '../../utils/validators'
isValidTicket,
postFetch,
removeParameterFromWindow,
rewriteRedirectTo
} from '../../utils'
import { AuthContext, INITIAL_MACHINE_CONTEXT } from './context'
import { AuthEvents } from './events'
@@ -81,23 +78,20 @@ type AuthServices = {
export const createAuthMachine = ({
backendUrl,
clientUrl,
clientStorageGetter,
clientStorageSetter,
clientStorageType = 'web',
clientStorage,
refreshIntervalTime,
autoRefreshToken = true,
autoSignIn = true
}: AuthMachineOptions) => {
const storageGetter = clientStorageGetter || localStorageGetter(clientStorageType, clientStorage)
const storageSetter = clientStorageSetter || localStorageSetter(clientStorageType, clientStorage)
const api = nhostApiClient(backendUrl)
const storageGetter = localStorageGetter(clientStorageType, clientStorage)
const storageSetter = localStorageSetter(clientStorageType, clientStorage)
const postRequest = async <T = any, D = any>(
url: string,
data?: D,
config?: AxiosRequestConfig<D>
token?: string | null
): Promise<T> => {
const result = await api.post(url, data, config)
const result = await postFetch<T>(`${backendUrl}${url}`, data, token)
return result.data
}
@@ -704,11 +698,7 @@ export const createAuthMachine = ({
phoneNumber,
options: rewriteRedirectTo(clientUrl, options)
},
{
headers: {
authorization: `Bearer ${context.accessToken.value}`
}
}
context.accessToken.value
)
} else {
return postRequest('/signin/passwordless/sms', {
@@ -739,11 +729,7 @@ export const createAuthMachine = ({
email,
options: rewriteRedirectTo(clientUrl, options)
},
{
headers: {
authorization: `Bearer ${context.accessToken.value}`
}
}
context.accessToken.value
)
} else {
return postRequest('/signin/passwordless/email', {
@@ -811,11 +797,7 @@ export const createAuthMachine = ({
password,
options: rewriteRedirectTo(clientUrl, options)
},
{
headers: {
authorization: `Bearer ${context.accessToken.value}`
}
}
context.accessToken.value
)
} else {
return postRequest<SignUpResponse>('/signup/email-password', {

View File

@@ -1,9 +1,8 @@
import { assign, createMachine, send } from 'xstate'
import { INVALID_EMAIL_ERROR } from '../errors'
import { AuthClient } from '../internal-client'
import { ChangeEmailOptions, ChangeEmailResponse, ErrorPayload } from '../types'
import { nhostApiClient, rewriteRedirectTo } from '../utils'
import { postFetch, rewriteRedirectTo } from '../utils'
import { isValidEmail } from '../utils/validators'
export type ChangeEmailContext = {
@@ -26,7 +25,6 @@ export type ChangeEmailServices = {
export type ChangeEmailMachine = ReturnType<typeof createChangeEmailMachine>
export const createChangeEmailMachine = ({ backendUrl, clientUrl, interpreter }: AuthClient) => {
const api = nhostApiClient(backendUrl)
return createMachine(
{
schema: {
@@ -86,17 +84,10 @@ export const createChangeEmailMachine = ({ backendUrl, clientUrl, interpreter }:
},
services: {
requestChange: async (_, { email, options }) => {
const res = await api.post(
'/user/email/change',
{
newEmail: email,
options: rewriteRedirectTo(clientUrl, options)
},
{
headers: {
authorization: `Bearer ${interpreter?.getSnapshot().context.accessToken.value}`
}
}
const res = await postFetch(
`${backendUrl}/user/email/change`,
{ newEmail: email, options: rewriteRedirectTo(clientUrl, options) },
interpreter?.getSnapshot().context.accessToken.value
)
return res.data
}

View File

@@ -1,9 +1,8 @@
import { assign, createMachine, send } from 'xstate'
import { INVALID_PASSWORD_ERROR } from '../errors'
import { AuthClient } from '../internal-client'
import { ChangePasswordResponse, ErrorPayload } from '../types'
import { nhostApiClient } from '../utils'
import { postFetch } from '../utils'
import { isValidPassword } from '../utils/validators'
export type ChangePasswordContext = {
@@ -25,7 +24,6 @@ export type ChangePasswordServices = {
export type ChangePasswordMachine = ReturnType<typeof createChangePasswordMachine>
export const createChangePasswordMachine = ({ backendUrl, interpreter }: AuthClient) => {
const api = nhostApiClient(backendUrl)
return createMachine(
{
schema: {
@@ -84,14 +82,10 @@ export const createChangePasswordMachine = ({ backendUrl, interpreter }: AuthCli
},
services: {
requestChange: (_, { password, ticket }) =>
api.post<string, ChangePasswordResponse>(
'/user/password',
postFetch<ChangePasswordResponse>(
`${backendUrl}/user/password`,
{ newPassword: password, ticket: ticket },
{
headers: {
authorization: `Bearer ${interpreter?.getSnapshot().context.accessToken.value}`
}
}
interpreter?.getSnapshot().context.accessToken.value
)
}
}

View File

@@ -1,9 +1,8 @@
import { assign, createMachine, send } from 'xstate'
import { INVALID_MFA_CODE_ERROR, INVALID_MFA_TYPE_ERROR } from '../errors'
import { AuthClient } from '../internal-client'
import { ErrorPayload } from '../types'
import { nhostApiClient } from '../utils'
import { getFetch, postFetch } from '../utils'
export type EnableMfaContext = {
error: ErrorPayload | null
@@ -28,7 +27,6 @@ export type EnableMfaEvents =
export type EnableMfadMachine = ReturnType<typeof createEnableMfaMachine>
export const createEnableMfaMachine = ({ backendUrl, interpreter }: AuthClient) => {
const api = nhostApiClient(backendUrl)
return createMachine(
{
schema: {
@@ -107,7 +105,10 @@ export const createEnableMfaMachine = ({ backendUrl, interpreter }: AuthClient)
imageUrl: (_, { data: { imageUrl } }: any) => imageUrl,
secret: (_, { data: { totpSecret } }: any) => totpSecret
}),
reportError: send((ctx) => ({ type: 'ERROR', error: ctx.error })),
reportError: send((ctx, event) => {
console.log('REPORT', ctx, event)
return { type: 'ERROR', error: ctx.error }
}),
reportSuccess: send('SUCCESS'),
reportGeneratedSuccess: send('GENERATED'),
reportGeneratedError: send((ctx) => ({ type: 'GENERATED_ERROR', error: ctx.error }))
@@ -118,25 +119,17 @@ export const createEnableMfaMachine = ({ backendUrl, interpreter }: AuthClient)
},
services: {
generate: async (_) => {
const { data } = await api.get('/mfa/totp/generate', {
headers: {
authorization: `Bearer ${interpreter?.getSnapshot().context.accessToken.value}`
}
})
const { data } = await getFetch(
`${backendUrl}/mfa/totp/generate`,
interpreter?.getSnapshot().context.accessToken.value
)
return data
},
activate: (_, { code, activeMfaType }) =>
api.post(
'/user/mfa',
{
code,
activeMfaType
},
{
headers: {
authorization: `Bearer ${interpreter?.getSnapshot().context.accessToken.value}`
}
}
postFetch(
`${backendUrl}/user/mfa`,
{ code, activeMfaType },
interpreter?.getSnapshot().context.accessToken.value
)
}
}

View File

@@ -1,9 +1,8 @@
import { assign, createMachine, send } from 'xstate'
import { INVALID_EMAIL_ERROR } from '../errors'
import { AuthClient } from '../internal-client'
import { ErrorPayload, ResetPasswordOptions, ResetPasswordResponse } from '../types'
import { nhostApiClient, rewriteRedirectTo } from '../utils'
import { postFetch, rewriteRedirectTo } from '../utils'
import { isValidEmail } from '../utils/validators'
export type ResetPasswordContext = {
@@ -25,7 +24,6 @@ export type ResetPasswordServices = {
export type ResetPasswordMachine = ReturnType<typeof createResetPasswordMachine>
export const createResetPasswordMachine = ({ backendUrl, clientUrl }: AuthClient) => {
const api = nhostApiClient(backendUrl)
return createMachine(
{
schema: {
@@ -84,7 +82,7 @@ export const createResetPasswordMachine = ({ backendUrl, clientUrl }: AuthClient
},
services: {
requestChange: (_, { email, options }) =>
api.post<string, ResetPasswordResponse>('/user/password/reset', {
postFetch<ResetPasswordResponse>(`${backendUrl}/user/password/reset`, {
email,
options: rewriteRedirectTo(clientUrl, options)
})

View File

@@ -1,9 +1,8 @@
import { assign, createMachine, send } from 'xstate'
import { INVALID_EMAIL_ERROR } from '../errors'
import { AuthClient } from '../internal-client'
import { ErrorPayload, SendVerificationEmailOptions, SendVerificationEmailResponse } from '../types'
import { nhostApiClient, rewriteRedirectTo } from '../utils'
import { postFetch, rewriteRedirectTo } from '../utils'
import { isValidEmail } from '../utils/validators'
export type SendVerificationEmailContext = {
@@ -25,7 +24,6 @@ export type SendVerificationEmailServices = {
export type SendVerificationEmailMachine = ReturnType<typeof createSendVerificationEmailMachine>
export const createSendVerificationEmailMachine = ({ backendUrl, clientUrl }: AuthClient) => {
const api = nhostApiClient(backendUrl)
return createMachine(
{
schema: {
@@ -84,12 +82,9 @@ export const createSendVerificationEmailMachine = ({ backendUrl, clientUrl }: Au
},
services: {
request: async (_, { email, options }) => {
const res = await api.post<SendVerificationEmailResponse>(
'/user/email/send-verification-email',
{
email,
options: rewriteRedirectTo(clientUrl, options)
}
const res = await postFetch<SendVerificationEmailResponse>(
`${backendUrl}/user/email/send-verification-email`,
{ email, options: rewriteRedirectTo(clientUrl, options) }
)
return res.data
}

View File

@@ -3,13 +3,12 @@ import {
PublicKeyCredentialCreationOptionsJSON,
RegistrationCredentialJSON
} from '@simplewebauthn/typescript-types'
import { postFetch } from '..'
import { CodifiedError } from '../errors'
import { AuthClient } from '../internal-client'
import { ErrorPayload, SecurityKey } from '../types'
import { nhostApiClient } from '../utils'
import { ActionErrorState, ActionLoadingState, ActionSuccessState } from './types'
export interface AddSecurityKeyHandlerResult extends ActionErrorState, ActionSuccessState {
key?: SecurityKey
}
@@ -20,16 +19,11 @@ export const addSecurityKeyPromise = async (
{ backendUrl, interpreter }: AuthClient,
nickname?: string
): Promise<AddSecurityKeyHandlerResult> => {
const api = nhostApiClient(backendUrl)
try {
const { data: options } = await api.post<PublicKeyCredentialCreationOptionsJSON>(
const { data: options } = await postFetch<PublicKeyCredentialCreationOptionsJSON>(
'/user/webauthn/add',
{},
{
headers: {
authorization: `Bearer ${interpreter?.getSnapshot().context.accessToken.value}`
}
}
interpreter?.getSnapshot().context.accessToken.value
)
let credential: RegistrationCredentialJSON
try {
@@ -37,14 +31,10 @@ export const addSecurityKeyPromise = async (
} catch (e) {
throw new CodifiedError(e as Error)
}
const { data: key } = await api.post<SecurityKey>(
'/user/webauthn/verify',
const { data: key } = await postFetch<SecurityKey>(
`${backendUrl}/user/webauthn/verify`,
{ credential, nickname },
{
headers: {
authorization: `Bearer ${interpreter?.getSnapshot().context.accessToken.value}`
}
}
interpreter?.getSnapshot().context.accessToken.value
)
return { key, isError: false, error: null, isSuccess: true }
} catch (e) {

View File

@@ -1,7 +1,7 @@
import { ErrorPayload, NhostSession } from './common'
// Hasura-auth API response types
interface NullableErrorResponse {
export interface NullableErrorResponse {
error: ErrorPayload | null
}

View File

@@ -1,25 +0,0 @@
import axios, { AxiosError } from 'axios'
import { NETWORK_ERROR_CODE } from '../errors'
import { ErrorPayload } from '../types'
export const nhostApiClient = (backendUrl: string) => {
const client = axios.create({ baseURL: backendUrl })
client.interceptors.response.use(
(response) => response,
(error: AxiosError<{ message: string; error?: string; statusCode?: number }>) =>
Promise.reject<{ error: ErrorPayload }>({
error: {
message:
error.response?.data?.message ??
error.message ??
error.request.responseText ??
JSON.stringify(error),
status: error.response?.status ?? error.response?.data?.statusCode ?? NETWORK_ERROR_CODE,
error: error.response?.data?.error || error.request.statusText || 'network'
}
})
)
return client
}

View File

@@ -0,0 +1,58 @@
import fetch from 'cross-fetch'
import { NETWORK_ERROR_CODE } from '../errors'
import { NullableErrorResponse } from '../types'
interface FetcResponse<T> extends NullableErrorResponse {
data: T
}
const fetchWrapper = async <T>(
url: string,
method: 'GET' | 'POST',
{ token, body }: { token?: string | null; body?: any } = {}
): Promise<FetcResponse<T>> => {
const headers: HeadersInit = {
'Content-Type': 'application/json',
Accept: '*/*'
}
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
const options: RequestInit = {
method,
headers
}
if (body) {
options.body = JSON.stringify(body)
}
try {
const result = await fetch(url, options)
if (!result.ok) {
const error = await result.json()
return Promise.reject<FetcResponse<T>>({ error })
}
try {
const data = await result.json()
return { data, error: null }
} catch {
console.warn(`Unexpected response: can't parse the response of the server at ${url}`)
return { data: 'OK' as any, error: null }
}
} catch (e) {
const error = {
message: 'Network Error',
status: NETWORK_ERROR_CODE,
error: 'network'
}
return Promise.reject<FetcResponse<T>>({ error })
}
}
export const postFetch = async <T>(
url: string,
body: any,
token?: string | null
): Promise<FetcResponse<T>> => fetchWrapper<T>(url, 'POST', { token, body })
export const getFetch = <T>(url: string, token?: string | null): Promise<FetcResponse<T>> =>
fetchWrapper<T>(url, 'GET', { token })

View File

@@ -1,5 +1,5 @@
export * from './axios'
export * from './client-helpers'
export * from './environment'
export * from './fetch'
export * from './url'
export * from './validators'

View File

@@ -10,7 +10,6 @@ import {
invalidDeamonymisationEmailError
} from './helpers/handlers'
import { fakeAnonymousUser } from './helpers/mocks/user'
import server from './helpers/server'
import CustomClientStorage from './helpers/storage'
@@ -51,7 +50,7 @@ describe('Anonymous Sign-in', () => {
expect(state.context.errors).toMatchInlineSnapshot(`
{
"authentication": {
"error": "OK",
"error": "network",
"message": "Network Error",
"status": 0,
},

View File

@@ -61,7 +61,7 @@ test(`should fail if there is a network error`, async () => {
expect(state.context.error).toMatchInlineSnapshot(`
{
"error": "OK",
"error": "network",
"message": "Network Error",
"status": 0,
}

View File

@@ -64,7 +64,7 @@ test(`should fail if there is a network error`, async () => {
expect(state.context.error).toMatchInlineSnapshot(`
{
"error": "OK",
"error": "network",
"message": "Network Error",
"status": 0,
}

View File

@@ -54,7 +54,7 @@ test(`should fail if network is unavailable`, async () => {
expect(state.context.errors).toMatchInlineSnapshot(`
{
"registration": {
"error": "OK",
"error": "network",
"message": "Network Error",
"status": 0,
},

View File

@@ -68,7 +68,7 @@ describe(`Generation`, () => {
expect(state.context.error).toMatchInlineSnapshot(`
{
"error": "OK",
"error": "network",
"message": "Network Error",
"status": 0,
}

View File

@@ -53,7 +53,7 @@ export const correctAnonymousHandler = rest.post(
export const deamonymisationSuccessfulHandler = rest.post(
`${BASE_URL}/user/deanonymize`,
(_req, res, ctx) => {
return res(ctx.status(200))
return res(ctx.status(200), ctx.json('OK'))
}
)

View File

@@ -48,6 +48,6 @@ export const changeEmailUnauthorizedErrorHandler = rest.post(
export const changeEmailSuccessHandler = rest.post(
`${BASE_URL}/user/email/change`,
(_req, res, ctx) => {
return res(ctx.status(200))
return res(ctx.status(200), ctx.json('OK'))
}
)

View File

@@ -48,6 +48,6 @@ export const changePasswordUnauthorizedErrorHandler = rest.post(
export const changePasswordSuccessHandler = rest.post(
`${BASE_URL}/user/password`,
(_req, res, ctx) => {
return res(ctx.status(200))
return res(ctx.status(200), ctx.json('OK'))
}
)

View File

@@ -101,5 +101,5 @@ export const activateMfaTotpUnauthorizedErrorHandler = rest.post(
* Request handler for MSW to mock an successful network request when activating MFA.
*/
export const activateMfaTotpSuccessHandler = rest.post(`${BASE_URL}/user/mfa`, (_req, res, ctx) => {
return res(ctx.status(200))
return res(ctx.status(200), ctx.json('OK'))
})

View File

@@ -8,7 +8,7 @@ import { BASE_URL } from '../config'
export const correctPasswordlessEmailHandler = rest.post(
`${BASE_URL}/signin/passwordless/email`,
(_req, res, ctx) => {
return res(ctx.status(200))
return res(ctx.status(200), ctx.json('OK'))
}
)

View File

@@ -11,7 +11,7 @@ import fakeUser from '../mocks/user'
export const correctPasswordlessSmsHandler = rest.post(
`${BASE_URL}/signin/passwordless/sms`,
(_req, res, ctx) => {
return res(ctx.status(200))
return res(ctx.status(200), ctx.json('OK'))
}
)

View File

@@ -64,6 +64,6 @@ export const resetPasswordUserNotFoundHandler = rest.post(
export const resetPasswordSuccessHandler = rest.post(
`${BASE_URL}/user/password/reset`,
(_req, res, ctx) => {
return res(ctx.status(200))
return res(ctx.status(200), ctx.json('OK'))
}
)

View File

@@ -64,6 +64,6 @@ export const sendVerificationEmailUserNotFoundHandler = rest.post(
export const sendVerificationEmailSuccessHandler = rest.post(
`${BASE_URL}/user/email/send-verification-email`,
(_req, res, ctx) => {
return res(ctx.status(200))
return res(ctx.status(200), ctx.json('OK'))
}
)

View File

@@ -35,7 +35,7 @@ export const signOutHandler = rest.post(
)
}
return res(ctx.status(200))
return res(ctx.status(200), ctx.json('OK'))
}
)

View File

@@ -53,7 +53,7 @@ test(`should fail if network is unavailable`, async () => {
expect(state.context.errors).toMatchInlineSnapshot(`
{
"authentication": {
"error": "OK",
"error": "network",
"message": "Network Error",
"status": 0,
},

View File

@@ -57,7 +57,7 @@ test(`should fail if network is unavailable`, async () => {
expect(state.context.errors).toMatchInlineSnapshot(`
{
"authentication": {
"error": "OK",
"error": "network",
"message": "Network Error",
"status": 0,
},
@@ -81,7 +81,7 @@ test(`should fail if server returns an error`, async () => {
expect(state.context.errors).toMatchInlineSnapshot(`
{
"authentication": {
"error": "OK",
"error": "network",
"message": "Network Error",
"status": 0,
},

View File

@@ -51,7 +51,7 @@ test('should fail if network is unavailable', async () => {
expect(state.context.errors).toMatchInlineSnapshot(`
{
"registration": {
"error": "OK",
"error": "network",
"message": "Network Error",
"status": 0,
},

View File

@@ -51,7 +51,7 @@ test(`should fail if network is unavailable`, async () => {
expect(state.context.errors).toMatchInlineSnapshot(`
{
"registration": {
"error": "OK",
"error": "network",
"message": "Network Error",
"status": 0,
},

View File

@@ -340,7 +340,7 @@ describe('General and disabled auto-sign in', () => {
expect(state.context.errors).toMatchInlineSnapshot(`
{
"authentication": {
"error": "OK",
"error": "network",
"message": "Network Error",
"status": 0,
},

View File

@@ -49,7 +49,7 @@ test(`should fail if there is a network error`, async () => {
expect(state.context.error).toMatchInlineSnapshot(`
{
"error": "OK",
"error": "network",
"message": "Network Error",
"status": 0,
}

View File

@@ -3,9 +3,7 @@ import { AuthenticationCredentialJSON } from '@simplewebauthn/typescript-types'
import { afterAll, afterEach, beforeAll, beforeEach, expect, test, vi } from 'vitest'
import { interpret } from 'xstate'
import { waitFor } from 'xstate/lib/waitFor'
import { createAuthMachine } from '../src'
import { BASE_URL } from './helpers/config'
import {
authTokenNetworkErrorHandler,
@@ -78,7 +76,7 @@ test(`should fail if network is unavailable`, async () => {
expect(state.context.errors).toMatchInlineSnapshot(`
{
"authentication": {
"error": "OK",
"error": "network",
"message": "Network Error",
"status": 0,
},
@@ -99,7 +97,7 @@ test(`should fail if server returns an error`, async () => {
expect(state.context.errors).toMatchInlineSnapshot(`
{
"authentication": {
"error": "OK",
"error": "network",
"message": "Network Error",
"status": 0,
},

View File

@@ -80,7 +80,7 @@ describe('Security Key', () => {
expect(state.context.errors).toMatchInlineSnapshot(`
{
"registration": {
"error": "OK",
"error": "network",
"message": "Network Error",
"status": 0,
},

View File

@@ -51,7 +51,7 @@ test(`should fail if there is a network error`, async () => {
expect(state.context.error).toMatchInlineSnapshot(`
{
"error": "OK",
"error": "network",
"message": "Network Error",
"status": 0,
}

View File

@@ -70,7 +70,7 @@ test(`should fail if network is unavailable`, async () => {
expect(state.context.errors).toMatchInlineSnapshot(`
{
"authentication": {
"error": "OK",
"error": "network",
"message": "Network Error",
"status": 0,
},

View File

@@ -53,7 +53,7 @@ test(`should fail if network is unavailable`, async () => {
expect(state.context.errors).toMatchInlineSnapshot(`
{
"registration": {
"error": "OK",
"error": "network",
"message": "Network Error",
"status": 0,
},

View File

@@ -1,5 +1,22 @@
# @nhost/hasura-storage-js
## 2.0.0
### Major Changes
- 19b11d40: Remove the deprecated `nhost.storage.getUrl` method
Use `nhost.storage.getPublicUrl` instead.
- 80bbd3a1: Replace `axios` by `cross-fetch`
`@nhost/hasura-storage-js` now uses `cross-fetch` instead of `axios`.
When in a browser, it uploads files using `XMLHttpRequest` to be able to track upload progress (feature available in React and Vue)
**Breaking Changes**
The error returned in `const { error } = nhost.storage.upload()` is not a JavaScript `Error`, but an object of type `{ error: string; status: number; message: string}`.
## 1.13.2
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@nhost/hasura-storage-js",
"version": "1.13.2",
"version": "2.0.0",
"description": "Hasura-storage client",
"license": "MIT",
"keywords": [
@@ -59,7 +59,6 @@
"docgen": "pnpm typedoc && docgen --config ./storage.docgen.json"
},
"dependencies": {
"axios": "^1.2.0",
"form-data": "^4.0.0",
"xstate": "^4.33.5"
},

View File

@@ -1,6 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import axios, { AxiosInstance } from 'axios'
import { toIso88591 } from './utils'
import fetch from 'cross-fetch'
import {
ApiDeleteParams,
@@ -8,9 +6,9 @@ import {
ApiGetPresignedUrlParams,
ApiGetPresignedUrlResponse,
ApiUploadParams,
ApiUploadResponse,
UploadHeaders
} from './utils'
StorageUploadResponse
} from './utils/types'
import { fetchUpload } from './utils/upload'
/**
* @internal
@@ -18,52 +16,37 @@ import {
*/
export class HasuraStorageApi {
private url: string
private httpClient: AxiosInstance
private accessToken?: string
private adminSecret?: string
constructor({ url }: { url: string }) {
this.url = url
this.httpClient = axios.create({
baseURL: this.url
})
}
async upload(params: ApiUploadParams): Promise<ApiUploadResponse> {
async upload(params: ApiUploadParams): Promise<StorageUploadResponse> {
const { formData } = params
try {
const res = await this.httpClient.post('/files', formData, {
headers: {
...this.generateUploadHeaders(params),
...this.generateAuthHeaders(),
'Content-Type': 'multipart/form-data'
}
})
return { fileMetadata: res.data, error: null }
} catch (error) {
return { fileMetadata: null, error: error as Error }
}
return fetchUpload(this.url, formData, {
accessToken: this.accessToken,
adminSecret: this.accessToken,
bucketId: params.bucketId,
fileId: params.id,
name: params.name
})
}
async getPresignedUrl(params: ApiGetPresignedUrlParams): Promise<ApiGetPresignedUrlResponse> {
try {
const { fileId } = params
const url = `/files/${fileId}/presignedurl`
// TODO not implemented yet in hasura-storage
// const { fileId, ...imageTransformationParams } = params
// const url = appendImageTransformationParameters(
// `/files/${fileId}/presignedurl`,
// imageTransformationParams
// )
const res = await this.httpClient.get(url, {
headers: {
...this.generateAuthHeaders()
}
const response = await fetch(`${this.url}/files/${fileId}/presignedurl`, {
method: 'GET',
headers: this.generateAuthHeaders()
})
return { presignedUrl: res.data, error: null }
if (!response.ok) {
throw new Error(await response.text())
}
const presignedUrl = await response.json()
return { presignedUrl, error: null }
} catch (error) {
return { presignedUrl: null, error: error as Error }
}
@@ -72,11 +55,13 @@ export class HasuraStorageApi {
async delete(params: ApiDeleteParams): Promise<ApiDeleteResponse> {
try {
const { fileId } = params
await this.httpClient.delete(`/files/${fileId}`, {
headers: {
...this.generateAuthHeaders()
}
const response = await fetch(`${this.url}/files/${fileId}`, {
method: 'DELETE',
headers: this.generateAuthHeaders()
})
if (!response.ok) {
throw new Error(await response.text())
}
return { error: null }
} catch (error) {
return { error: error as Error }
@@ -107,29 +92,9 @@ export class HasuraStorageApi {
return this
}
private generateUploadHeaders(params: ApiUploadParams): UploadHeaders {
const { bucketId, name, id } = params
const uploadheaders: UploadHeaders = {}
if (bucketId) {
uploadheaders['x-nhost-bucket-id'] = bucketId
}
if (id) {
uploadheaders['x-nhost-file-id'] = id
}
if (name) {
uploadheaders['x-nhost-file-name'] = toIso88591(name)
}
return uploadheaders
}
private generateAuthHeaders():
| { Authorization: string }
| { 'x-hasura-admin-secret': string }
| null {
private generateAuthHeaders(): HeadersInit | undefined {
if (!this.adminSecret && !this.accessToken) {
return null
return undefined
}
if (this.adminSecret) {

View File

@@ -1,5 +1,4 @@
import FormData from 'form-data'
import { HasuraStorageApi } from './hasura-storage-api'
import {
appendImageTransformationParameters,
@@ -86,26 +85,10 @@ export class HasuraStorageClient {
formData = params.formData
}
const { fileMetadata, error } = await this.api.upload({
return this.api.upload({
...params,
formData: formData
formData
})
if (error) {
return { fileMetadata: null, error }
}
if (!fileMetadata) {
return { fileMetadata: null, error: new Error('Invalid file returned') }
}
return { fileMetadata, error: null }
}
/**
* @deprecated Use `nhost.storage.getPublicUrl()` instead.
*/
getUrl(params: StorageGetUrlParams): string {
return this.getPublicUrl(params)
}
/**

View File

@@ -1,8 +1,7 @@
import axios, { AxiosError, AxiosProgressEvent, RawAxiosRequestHeaders } from 'axios'
import FormData from 'form-data'
import { assign, createMachine } from 'xstate'
import { toIso88591 } from '../utils'
import { ErrorPayload, FileUploadConfig } from '../utils'
import { fetchUpload } from '../utils/upload'
export type FileUploadContext = {
progress: number | null
@@ -117,76 +116,40 @@ export const createFileUploadMachine = () =>
},
services: {
uploadFile: (context, event) => (callback) => {
const headers: RawAxiosRequestHeaders = {
'Content-Type': 'multipart/form-data'
}
const fileId = event.id || context.id
if (fileId) {
headers['x-nhost-file-id'] = fileId
}
const bucketId = event.bucketId || context.bucketId
if (bucketId) {
headers['x-nhost-bucket-id'] = bucketId
}
const file = (event.file || context.file)!
headers['x-nhost-file-name'] = toIso88591(event.name || file.name)
const data = new FormData()
data.append('file', file)
if (event.adminSecret) {
headers['x-hasura-admin-secret'] = event.adminSecret
}
if (event.accessToken) {
headers['Authorization'] = `Bearer ${event.accessToken}`
}
let currentLoaded = 0
const controller = new AbortController()
axios
.post<{
bucketId: string
createdAt: string
etag: string
id: string
isUploaded: true
mimeType: string
name: string
size: number
updatedAt: string
uploadedByUserId: string
}>(event.url + '/files', data, {
headers,
signal: controller.signal,
onUploadProgress: (event: AxiosProgressEvent) => {
const loaded = event.total
? Math.round((event.loaded * file.size!) / event.total)
: 0
const additions = loaded - currentLoaded
currentLoaded = loaded
callback({
type: 'UPLOAD_PROGRESS',
progress: event.total ? Math.round((loaded * 100) / event.total) : 0,
loaded,
additions
})
}
})
.then(({ data: { id, bucketId } }) => {
callback({ type: 'UPLOAD_DONE', id, bucketId })
})
.catch(({ response, message }: AxiosError<{ error?: { message: string } }>) => {
callback({
type: 'UPLOAD_ERROR',
error: {
status: response?.status ?? 0,
message: response?.data?.error?.message || message,
// TODO errors from hasura-storage are not codified
error: response?.data?.error?.message || message
}
})
})
return () => {
controller.abort()
}
let currentLoaded = 0
fetchUpload(event.url, data, {
fileId: event.id || context.id,
bucketId: event.bucketId || context.bucketId,
accessToken: event.accessToken,
adminSecret: event.adminSecret,
name: event.name || file.name,
onUploadProgress: (event) => {
const loaded = event.total ? Math.round((event.loaded * file.size!) / event.total) : 0
const additions = loaded - currentLoaded
currentLoaded = loaded
callback({
type: 'UPLOAD_PROGRESS',
progress: event.total ? Math.round((loaded * 100) / event.total) : 0,
loaded,
additions
})
}
}).then(({ fileMetadata, error }) => {
if (error) {
callback({ type: 'UPLOAD_ERROR', error })
}
if (fileMetadata) {
const { id, bucketId } = fileMetadata
callback({ type: 'UPLOAD_DONE', id, bucketId })
}
})
return () => {}
}
}
}

View File

@@ -11,13 +11,3 @@ export const appendImageTransformationParameters = (
.join('&')
return queryParameters ? `${url}?${queryParameters}` : url
}
/** Convert any string into ISO-8859-1 */
export const toIso88591 = (fileName: string) => {
try {
btoa(fileName)
return fileName
} catch {
return encodeURIComponent(fileName)
}
}

View File

@@ -46,7 +46,7 @@ export type StorageUploadParams = StorageUploadFileParams | StorageUploadFormDat
export type StorageUploadResponse =
| { fileMetadata: FileResponse; error: null }
| { fileMetadata: null; error: Error }
| { fileMetadata: null; error: ErrorPayload }
export interface StorageImageTransformationParams {
/** Image width, in pixels */
@@ -83,7 +83,7 @@ export interface StorageDeleteResponse {
error: Error | null
}
interface FileResponse {
export interface FileResponse {
id: string
name: string
size: number
@@ -91,6 +91,9 @@ interface FileResponse {
etag: string
createdAt: string
bucketId: string
isUploaded: true
updatedAt: string
uploadedByUserId: string
}
export interface ApiUploadParams {
@@ -100,10 +103,6 @@ export interface ApiUploadParams {
bucketId?: string
}
export type ApiUploadResponse =
| { fileMetadata: FileResponse; error: null }
| { fileMetadata: null; error: Error }
// TODO not implemented yet in hasura-storage
// export interface ApiGetPresignedUrlParams extends StorageImageTransformationParams {
export interface ApiGetPresignedUrlParams {
@@ -122,7 +121,7 @@ export interface ApiDeleteResponse {
error: Error | null
}
export interface UploadHeaders {
export type UploadHeaders = HeadersInit & {
'x-nhost-bucket-id'?: string
'x-nhost-file-id'?: string
'x-nhost-file-name'?: string

View File

@@ -0,0 +1,118 @@
import fetch from 'cross-fetch'
import FormData from 'form-data'
import { ErrorPayload, StorageUploadResponse } from './types'
/** Convert any string into ISO-8859-1 */
export const toIso88591 = (fileName: string) => {
try {
btoa(fileName)
return fileName
} catch {
return encodeURIComponent(fileName)
}
}
export const fetchUpload = async (
backendUrl: string,
data: FormData,
{
accessToken,
name,
fileId,
bucketId,
adminSecret,
onUploadProgress
}: {
accessToken?: string
name?: string
fileId?: string
bucketId?: string
adminSecret?: string
onUploadProgress?: (event: { total: number; loaded: number }) => void
} = {}
): Promise<StorageUploadResponse> => {
const headers: HeadersInit = {}
if (fileId) {
headers['x-nhost-file-id'] = fileId
}
if (bucketId) {
headers['x-nhost-bucket-id'] = bucketId
}
if (name) {
headers['x-nhost-file-name'] = toIso88591(name)
}
if (adminSecret) {
headers['x-hasura-admin-secret'] = adminSecret
}
if (accessToken) {
headers['Authorization'] = `Bearer ${accessToken}`
}
const url = `${backendUrl}/files`
if (typeof XMLHttpRequest === 'undefined') {
// * Non-browser environment: XMLHttpRequest is not available
try {
const response = await fetch(url, {
method: 'POST',
headers,
body: data as any // * https://github.com/form-data/form-data/issues/513
})
if (!response.ok) {
const error: ErrorPayload = {
status: response.status,
message: await response.text(),
// * errors from hasura-storage are not codified
error: response.statusText
}
return { error, fileMetadata: null }
}
const fileMetadata = await response.json()
return { fileMetadata, error: null }
} catch (e) {
const error: ErrorPayload = {
status: 0,
message: (e as Error).message,
error: (e as Error).message
}
return { error, fileMetadata: null }
}
}
// * Browser environment: XMLHttpRequest is available
return new Promise((resolve) => {
console.log('NOOOOOOO')
let xhr = new XMLHttpRequest()
xhr.responseType = 'json'
xhr.onload = () => {
if (xhr.status < 200 && xhr.status >= 300) {
return resolve({
fileMetadata: null,
error: { error: xhr.statusText, message: xhr.statusText, status: xhr.status }
})
}
return resolve({ fileMetadata: xhr.response, error: null })
}
xhr.onerror = () => {
// only triggers if the request couldn't be made at all e.g. network error
return resolve({
fileMetadata: null,
error: { error: xhr.statusText, message: xhr.statusText, status: xhr.status }
})
}
if (onUploadProgress) {
xhr.upload.addEventListener('progress', onUploadProgress, false)
}
xhr.open('POST', url, true)
Object.entries(headers).forEach(([key, value]) => {
xhr.setRequestHeader(key, value)
})
xhr.send(data as any) // * https://github.com/form-data/form-data/issues/513
})
}

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { toIso88591 } from '../src/utils'
import { toIso88591 } from '../src/utils/upload'
describe('test file names', () => {
it('should be able to use ISO 8859-1 strings', async () => {

Some files were not shown because too many files have changed in this diff Show More