@gusnips/react 0.9.3 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/dist/supabase-session.d.ts +28 -0
- package/dist/supabase-session.d.ts.map +1 -1
- package/dist/supabase-session.js +44 -10
- package/dist/supabase-session.js.map +1 -1
- package/package.json +1 -1
- package/src/supabase-session.test.ts +52 -1
- package/src/supabase-session.ts +43 -9
package/README.md
CHANGED
|
@@ -57,6 +57,28 @@ out. More importantly, it keeps a network failure separate from GoTrue refusing
|
|
|
57
57
|
so losing Wi-Fi does not become a logout. It lives behind a subpath because it imports
|
|
58
58
|
`@supabase/supabase-js`; the main entry does not.
|
|
59
59
|
|
|
60
|
+
The test it makes that decision with is exported, because a refresh is not the only place an app
|
|
61
|
+
asks it:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { isAuthOutage } from "@gusnips/react/supabase";
|
|
65
|
+
|
|
66
|
+
// A callback that just failed to trade its one-use link.
|
|
67
|
+
if (isAuthOutage(error))
|
|
68
|
+
showRetry(); // the link was never spent — the same one still works
|
|
69
|
+
else showLinkSpent(); // auth said no: only a new link helps
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`isAuthOutage` is true when auth FAILED rather than answered — a request that never landed, or a
|
|
73
|
+
5xx. Only a real refusal is an answer, and only an answer may end a session or a link. Use it
|
|
74
|
+
anywhere that decision is made; the sentence you show is a separate question, and a narrower one
|
|
75
|
+
— "check your connection" is true of a request that never landed and false of a 502, which is
|
|
76
|
+
ours. auth-js marks "nothing came back", and only that, with status 0.
|
|
77
|
+
|
|
78
|
+
It is safe to hand it the `error` from a `{ data, error }` result without checking it first:
|
|
79
|
+
`null` answers false. That is worth stating because `null` is the SUCCESS value of every
|
|
80
|
+
supabase-js auth call, so the obvious `if (isAuthOutage(error))` is written against it constantly.
|
|
81
|
+
|
|
60
82
|
Supabase email links have two complete patterns. Keep either one, never half of each:
|
|
61
83
|
|
|
62
84
|
- A link carrying `token_hash` needs one explicit `verifyOtp` call. Guard it against React
|
|
@@ -16,6 +16,34 @@ export interface SupabaseSessionAuth {
|
|
|
16
16
|
error: unknown;
|
|
17
17
|
}>;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Auth FAILED rather than answered, so nothing is known about the credential it was handed:
|
|
21
|
+
* either the request never landed, or GoTrue failed on its own.
|
|
22
|
+
*
|
|
23
|
+
* This is the question every site that ends a session has to ask — a token refresh, an API
|
|
24
|
+
* door, and a callback choosing between "try again" and "that link is spent". Without it one
|
|
25
|
+
* bad minute at GoTrue signs out everybody whose token happened to need refreshing, and tells
|
|
26
|
+
* everybody clicking a recovery link that it has expired, which sends them to ask the service
|
|
27
|
+
* that is down for a replacement.
|
|
28
|
+
*
|
|
29
|
+
* Both clauses carry weight, and the second is why this is a function rather than one call.
|
|
30
|
+
* `isAuthRetryableFetchError` is a NAME check, and the statuses that earn that name are a list
|
|
31
|
+
* auth-js owns. The list has drifted — 502, 503 and 504 at 2.91; those plus the 52x family and
|
|
32
|
+
* still no 500 at 2.106; 500 through 530 at 2.108 — so code leaning on it alone is right at
|
|
33
|
+
* whichever version happens to be installed, which is not the same as being right, and a fleet
|
|
34
|
+
* installs several versions at once. It is also a LIST rather than a range, so it has holes at
|
|
35
|
+
* every version, drift or no drift: at 2.112 nothing covers 505 through 519 or 531 up, and a
|
|
36
|
+
* 507 or a 599 out of a proxy in front of GoTrue arrives as a plain `AuthApiError`. Pinning a
|
|
37
|
+
* recent SDK does not make the status clause redundant.
|
|
38
|
+
*
|
|
39
|
+
* Read the version a tree really serves with `bun why @supabase/auth-js`, which prints one
|
|
40
|
+
* heading per RESOLVED version; `node_modules` keeps copies the resolver does not serve.
|
|
41
|
+
*
|
|
42
|
+
* Anything that is not an auth error at all answers true: a failure from outside auth-js is not
|
|
43
|
+
* a verdict on the credential either. Nothing at all answers false — `null` is what every
|
|
44
|
+
* supabase-js auth call returns on success, and success is not an outage.
|
|
45
|
+
*/
|
|
46
|
+
export declare function isAuthOutage(error: unknown): boolean;
|
|
19
47
|
/**
|
|
20
48
|
* Connects a Supabase auth client to {@link SessionAdapter}.
|
|
21
49
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supabase-session.d.ts","sourceRoot":"","sources":["../src/supabase-session.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,UAAU,qBAAqB;IAC7B,IAAI,EAAE;QAAE,OAAO,EAAE;YAAE,YAAY,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAA;KAAE,CAAC;CACpD;AAED,8FAA8F;AAC9F,MAAM,WAAW,mBAAmB;IAClC,UAAU,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC7C,cAAc,IAAI,OAAO,CAAC,qBAAqB,GAAG;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACtE,OAAO,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACxC;
|
|
1
|
+
{"version":3,"file":"supabase-session.d.ts","sourceRoot":"","sources":["../src/supabase-session.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,UAAU,qBAAqB;IAC7B,IAAI,EAAE;QAAE,OAAO,EAAE;YAAE,YAAY,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAA;KAAE,CAAC;CACpD;AAED,8FAA8F;AAC9F,MAAM,WAAW,mBAAmB;IAClC,UAAU,IAAI,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAC7C,cAAc,IAAI,OAAO,CAAC,qBAAqB,GAAG;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACtE,OAAO,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACxC;AAOD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CASpD;AAaD;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,mBAAmB,GAAG,cAAc,CAsBtF"}
|
package/dist/supabase-session.js
CHANGED
|
@@ -1,15 +1,49 @@
|
|
|
1
1
|
import { isAuthError, isAuthRetryableFetchError } from "@supabase/supabase-js";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Auth FAILED rather than answered, so nothing is known about the credential it was handed:
|
|
4
|
+
* either the request never landed, or GoTrue failed on its own.
|
|
5
|
+
*
|
|
6
|
+
* This is the question every site that ends a session has to ask — a token refresh, an API
|
|
7
|
+
* door, and a callback choosing between "try again" and "that link is spent". Without it one
|
|
8
|
+
* bad minute at GoTrue signs out everybody whose token happened to need refreshing, and tells
|
|
9
|
+
* everybody clicking a recovery link that it has expired, which sends them to ask the service
|
|
10
|
+
* that is down for a replacement.
|
|
11
|
+
*
|
|
12
|
+
* Both clauses carry weight, and the second is why this is a function rather than one call.
|
|
13
|
+
* `isAuthRetryableFetchError` is a NAME check, and the statuses that earn that name are a list
|
|
14
|
+
* auth-js owns. The list has drifted — 502, 503 and 504 at 2.91; those plus the 52x family and
|
|
15
|
+
* still no 500 at 2.106; 500 through 530 at 2.108 — so code leaning on it alone is right at
|
|
16
|
+
* whichever version happens to be installed, which is not the same as being right, and a fleet
|
|
17
|
+
* installs several versions at once. It is also a LIST rather than a range, so it has holes at
|
|
18
|
+
* every version, drift or no drift: at 2.112 nothing covers 505 through 519 or 531 up, and a
|
|
19
|
+
* 507 or a 599 out of a proxy in front of GoTrue arrives as a plain `AuthApiError`. Pinning a
|
|
20
|
+
* recent SDK does not make the status clause redundant.
|
|
21
|
+
*
|
|
22
|
+
* Read the version a tree really serves with `bun why @supabase/auth-js`, which prints one
|
|
23
|
+
* heading per RESOLVED version; `node_modules` keeps copies the resolver does not serve.
|
|
24
|
+
*
|
|
25
|
+
* Anything that is not an auth error at all answers true: a failure from outside auth-js is not
|
|
26
|
+
* a verdict on the credential either. Nothing at all answers false — `null` is what every
|
|
27
|
+
* supabase-js auth call returns on success, and success is not an outage.
|
|
28
|
+
*/
|
|
29
|
+
export function isAuthOutage(error) {
|
|
30
|
+
// Nothing thrown is not an outage, and this clause exists because the function is exported.
|
|
31
|
+
// `null` is the SUCCESS value of every supabase-js auth call, so the obvious
|
|
32
|
+
// `if (isAuthOutage(error))` around a `getUser()` result has to be safe; without this it read
|
|
33
|
+
// every success as auth being down. An adopter had already written that guard at its own call
|
|
34
|
+
// site, which is what a check missing from in here looks like from the outside.
|
|
35
|
+
if (error == null)
|
|
4
36
|
return false;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
37
|
+
if (!isAuthError(error))
|
|
38
|
+
return true;
|
|
39
|
+
return isAuthRetryableFetchError(error) || (error.status ?? 0) >= 500;
|
|
40
|
+
}
|
|
41
|
+
function reachedAuth(error) {
|
|
42
|
+
// Measured across the fleet: four backends answered 401 to their own auth provider's 500,
|
|
43
|
+
// and every client reads a 401 as a dead session, so one bad minute at auth signed out
|
|
44
|
+
// everybody who was signed in. `isAuthError` leads only for the narrowing below —
|
|
45
|
+
// `isAuthOutage` already answers true for anything that is not an auth error.
|
|
46
|
+
if (!isAuthError(error) || isAuthOutage(error))
|
|
13
47
|
return false;
|
|
14
48
|
// Newer clients name a refresh that reached GoTrue but was deliberately discarded because the
|
|
15
49
|
// local session changed mid-flight. That race is a no-op, not proof that either session died.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supabase-session.js","sourceRoot":"","sources":["../src/supabase-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,yBAAyB,EAAuB,MAAM,uBAAuB,CAAC;AAmBpG,
|
|
1
|
+
{"version":3,"file":"supabase-session.js","sourceRoot":"","sources":["../src/supabase-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,yBAAyB,EAAuB,MAAM,uBAAuB,CAAC;AAmBpG;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,4FAA4F;IAC5F,6EAA6E;IAC7E,8FAA8F;IAC9F,8FAA8F;IAC9F,gFAAgF;IAChF,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,KAAK,CAAC;IAChC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACrC,OAAO,yBAAyB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC;AACxE,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,0FAA0F;IAC1F,uFAAuF;IACvF,kFAAkF;IAClF,8EAA8E;IAC9E,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,YAAY,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7D,8FAA8F;IAC9F,8FAA8F;IAC9F,OAAO,KAAK,CAAC,IAAI,KAAK,2BAA2B,CAAC;AACpD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,4BAA4B,CAAC,IAAyB;IACpE,OAAO;QACL,QAAQ,EAAE,KAAK,IAAI,EAAE;YACnB,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;YACzC,OAAO,IAAI,CAAC,OAAO,EAAE,YAAY,IAAI,IAAI,CAAC;QAC5C,CAAC;QACD,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,IAAI,CAAC;gBACH,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;gBACpD,OAAO;oBACL,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,YAAY,IAAI,IAAI;oBACzC,WAAW,EAAE,KAAK,KAAK,IAAI,IAAI,WAAW,CAAC,KAAK,CAAC;iBAClD,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1D,CAAC;QACH,CAAC;QACD,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACvC,IAAI,KAAK;gBAAE,MAAM,KAAK,CAAC;QACzB,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gusnips/react",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5",
|
|
4
4
|
"description": "The headless runtime under a Vite + React SPA: typed fetch client with single-flight refresh, auth store, query defaults, route guards, error boundary, stale-tab recovery, i18n bootstrap.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -2,9 +2,14 @@ import {
|
|
|
2
2
|
AuthApiError,
|
|
3
3
|
AuthRefreshDiscardedError,
|
|
4
4
|
AuthRetryableFetchError,
|
|
5
|
+
AuthUnknownError,
|
|
5
6
|
} from "@supabase/supabase-js";
|
|
6
7
|
import { describe, expect, it, vi } from "vitest";
|
|
7
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
createSupabaseSessionAdapter,
|
|
10
|
+
isAuthOutage,
|
|
11
|
+
type SupabaseSessionAuth,
|
|
12
|
+
} from "./supabase-session.ts";
|
|
8
13
|
|
|
9
14
|
function auth(overrides: Partial<SupabaseSessionAuth> = {}): SupabaseSessionAuth {
|
|
10
15
|
return {
|
|
@@ -103,3 +108,49 @@ describe("createSupabaseSessionAdapter", () => {
|
|
|
103
108
|
expect(signOut).toHaveBeenCalledOnce();
|
|
104
109
|
});
|
|
105
110
|
});
|
|
111
|
+
|
|
112
|
+
describe("isAuthOutage", () => {
|
|
113
|
+
it("lets a real refusal through as an answer", () => {
|
|
114
|
+
expect(
|
|
115
|
+
isAuthOutage(new AuthApiError("Invalid login credentials", 400, "invalid_credentials")),
|
|
116
|
+
).toBe(false);
|
|
117
|
+
expect(
|
|
118
|
+
isAuthOutage(new AuthApiError("Token has expired or is invalid", 403, "otp_expired")),
|
|
119
|
+
).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("refuses to read a failure as an answer", () => {
|
|
123
|
+
// Nothing came back, and the two shapes that carries.
|
|
124
|
+
expect(isAuthOutage(new AuthRetryableFetchError("Failed to fetch", 0))).toBe(true);
|
|
125
|
+
expect(isAuthOutage(new TypeError("Load failed"))).toBe(true);
|
|
126
|
+
// A gateway between us and GoTrue, which auth-js wraps by name.
|
|
127
|
+
expect(isAuthOutage(new AuthRetryableFetchError("Bad Gateway", 502))).toBe(true);
|
|
128
|
+
// And the reason the status clause is not redundant: the statuses earning that name are a
|
|
129
|
+
// LIST, so it has holes at every version. 507 and 599 are on no version of it, and arrive
|
|
130
|
+
// as ordinary AuthApiErrors that `isAuthRetryableFetchError` answers false for.
|
|
131
|
+
expect(isAuthOutage(new AuthApiError("Insufficient Storage", 507, undefined))).toBe(true);
|
|
132
|
+
expect(isAuthOutage(new AuthApiError("Network Connect Timeout", 599, undefined))).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("reads nothing-went-wrong as nothing went wrong", () => {
|
|
136
|
+
// `null` is the SUCCESS value of every supabase-js auth call, and this function is
|
|
137
|
+
// exported, so the obvious `if (isAuthOutage(error))` around a `getUser()` result has to
|
|
138
|
+
// be safe. Before this, it answered true and put an outage on screen over every success.
|
|
139
|
+
// One adopter had already written the guard at its own call site, which is what a missing
|
|
140
|
+
// check in here looks like from the outside.
|
|
141
|
+
expect(isAuthOutage(null)).toBe(false);
|
|
142
|
+
expect(isAuthOutage(undefined)).toBe(false);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("stays narrow for an error auth-js could not read", () => {
|
|
146
|
+
// AuthUnknownError is raised whenever the body does not parse as JSON, at ANY status, and
|
|
147
|
+
// it never fills one in — but `status` is still PRESENT, because the base class declares
|
|
148
|
+
// it, so `"status" in error` answers true and says nothing. That makes a 507 behind an
|
|
149
|
+
// HTML-answering proxy indistinguishable from a malformed 400, and the narrow answer is
|
|
150
|
+
// the right one: widening by class name would call the malformed 400 an outage too, and a
|
|
151
|
+
// dead session read as retryable is the same failure from the other side.
|
|
152
|
+
const unreadable = new AuthUnknownError("Unexpected token < in JSON", new Error("parse"));
|
|
153
|
+
expect(unreadable.status).toBeUndefined();
|
|
154
|
+
expect(isAuthOutage(unreadable)).toBe(false);
|
|
155
|
+
});
|
|
156
|
+
});
|
package/src/supabase-session.ts
CHANGED
|
@@ -17,16 +17,50 @@ type _SupabaseAuthFitsAdapter = Satisfied<
|
|
|
17
17
|
SupabaseClient["auth"] extends SupabaseSessionAuth ? true : false
|
|
18
18
|
>;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Auth FAILED rather than answered, so nothing is known about the credential it was handed:
|
|
22
|
+
* either the request never landed, or GoTrue failed on its own.
|
|
23
|
+
*
|
|
24
|
+
* This is the question every site that ends a session has to ask — a token refresh, an API
|
|
25
|
+
* door, and a callback choosing between "try again" and "that link is spent". Without it one
|
|
26
|
+
* bad minute at GoTrue signs out everybody whose token happened to need refreshing, and tells
|
|
27
|
+
* everybody clicking a recovery link that it has expired, which sends them to ask the service
|
|
28
|
+
* that is down for a replacement.
|
|
29
|
+
*
|
|
30
|
+
* Both clauses carry weight, and the second is why this is a function rather than one call.
|
|
31
|
+
* `isAuthRetryableFetchError` is a NAME check, and the statuses that earn that name are a list
|
|
32
|
+
* auth-js owns. The list has drifted — 502, 503 and 504 at 2.91; those plus the 52x family and
|
|
33
|
+
* still no 500 at 2.106; 500 through 530 at 2.108 — so code leaning on it alone is right at
|
|
34
|
+
* whichever version happens to be installed, which is not the same as being right, and a fleet
|
|
35
|
+
* installs several versions at once. It is also a LIST rather than a range, so it has holes at
|
|
36
|
+
* every version, drift or no drift: at 2.112 nothing covers 505 through 519 or 531 up, and a
|
|
37
|
+
* 507 or a 599 out of a proxy in front of GoTrue arrives as a plain `AuthApiError`. Pinning a
|
|
38
|
+
* recent SDK does not make the status clause redundant.
|
|
39
|
+
*
|
|
40
|
+
* Read the version a tree really serves with `bun why @supabase/auth-js`, which prints one
|
|
41
|
+
* heading per RESOLVED version; `node_modules` keeps copies the resolver does not serve.
|
|
42
|
+
*
|
|
43
|
+
* Anything that is not an auth error at all answers true: a failure from outside auth-js is not
|
|
44
|
+
* a verdict on the credential either. Nothing at all answers false — `null` is what every
|
|
45
|
+
* supabase-js auth call returns on success, and success is not an outage.
|
|
46
|
+
*/
|
|
47
|
+
export function isAuthOutage(error: unknown): boolean {
|
|
48
|
+
// Nothing thrown is not an outage, and this clause exists because the function is exported.
|
|
49
|
+
// `null` is the SUCCESS value of every supabase-js auth call, so the obvious
|
|
50
|
+
// `if (isAuthOutage(error))` around a `getUser()` result has to be safe; without this it read
|
|
51
|
+
// every success as auth being down. An adopter had already written that guard at its own call
|
|
52
|
+
// site, which is what a check missing from in here looks like from the outside.
|
|
53
|
+
if (error == null) return false;
|
|
54
|
+
if (!isAuthError(error)) return true;
|
|
55
|
+
return isAuthRetryableFetchError(error) || (error.status ?? 0) >= 500;
|
|
56
|
+
}
|
|
57
|
+
|
|
20
58
|
function reachedAuth(error: unknown): boolean {
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
// being right. Without this clause one bad minute at GoTrue signs out everybody whose token
|
|
27
|
-
// happened to need refreshing. Measured across the fleet: four backends answered 401 to
|
|
28
|
-
// their own auth provider's 500 for exactly this reason.
|
|
29
|
-
if ((error.status ?? 0) >= 500) return false;
|
|
59
|
+
// Measured across the fleet: four backends answered 401 to their own auth provider's 500,
|
|
60
|
+
// and every client reads a 401 as a dead session, so one bad minute at auth signed out
|
|
61
|
+
// everybody who was signed in. `isAuthError` leads only for the narrowing below —
|
|
62
|
+
// `isAuthOutage` already answers true for anything that is not an auth error.
|
|
63
|
+
if (!isAuthError(error) || isAuthOutage(error)) return false;
|
|
30
64
|
// Newer clients name a refresh that reached GoTrue but was deliberately discarded because the
|
|
31
65
|
// local session changed mid-flight. That race is a no-op, not proof that either session died.
|
|
32
66
|
return error.name !== "AuthRefreshDiscardedError";
|