@supabase/supabase-js 2.110.3 → 2.110.4-canary.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supabase/supabase-js",
3
- "version": "2.110.3",
3
+ "version": "2.110.4-canary.0",
4
4
  "description": "Isomorphic Javascript SDK for Supabase",
5
5
  "keywords": [
6
6
  "javascript",
@@ -59,11 +59,11 @@
59
59
  "directory": "packages/core/supabase-js"
60
60
  },
61
61
  "dependencies": {
62
- "@supabase/auth-js": "2.110.3",
63
- "@supabase/functions-js": "2.110.3",
64
- "@supabase/postgrest-js": "2.110.3",
65
- "@supabase/storage-js": "2.110.3",
66
- "@supabase/realtime-js": "2.110.3"
62
+ "@supabase/auth-js": "2.110.4-canary.0",
63
+ "@supabase/functions-js": "2.110.4-canary.0",
64
+ "@supabase/postgrest-js": "2.110.4-canary.0",
65
+ "@supabase/realtime-js": "2.110.4-canary.0",
66
+ "@supabase/storage-js": "2.110.4-canary.0"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@arethetypeswrong/cli": "^0.18.2",
@@ -20,7 +20,7 @@ import {
20
20
  DEFAULT_REALTIME_OPTIONS,
21
21
  DEFAULT_TRACE_PROPAGATION_OPTIONS,
22
22
  } from './lib/constants'
23
- import { fetchWithAuth } from './lib/fetch'
23
+ import { assertSupportedApiKey, fetchWithAuth } from './lib/fetch'
24
24
  import {
25
25
  applySettingDefaults,
26
26
  validateSupabaseUrl,
@@ -89,6 +89,7 @@ export default class SupabaseClient<
89
89
  protected rest: PostgrestClient<Database, ClientOptions, SchemaName>
90
90
  protected storageKey: string
91
91
  protected fetch?: Fetch
92
+ protected functionsFetch?: Fetch
92
93
  protected changedAccessToken?: string
93
94
  protected accessToken?: () => Promise<string | null>
94
95
 
@@ -314,6 +315,7 @@ export default class SupabaseClient<
314
315
  ) {
315
316
  const baseUrl = validateSupabaseUrl(supabaseUrl)
316
317
  if (!supabaseKey) throw new Error('supabaseKey is required.')
318
+ assertSupportedApiKey(supabaseKey)
317
319
 
318
320
  this.realtimeUrl = new URL('realtime/v1', baseUrl)
319
321
  this.realtimeUrl.protocol = this.realtimeUrl.protocol.replace('http', 'ws')
@@ -357,13 +359,25 @@ export default class SupabaseClient<
357
359
  })
358
360
  }
359
361
 
362
+ // The fetch wrappers receive the raw session token (null when there is no session) and
363
+ // decide the `Authorization` fallback themselves, so the API-key fallback lives in one place.
360
364
  this.fetch = fetchWithAuth(
361
365
  supabaseKey,
362
366
  supabaseUrl,
363
- this._getAccessToken.bind(this),
367
+ this._getSessionToken.bind(this),
364
368
  settings.global.fetch,
365
369
  settings.tracePropagation
366
370
  )
371
+ // Edge Functions use a dedicated fetch that never falls back to a new-format API key in
372
+ // the Authorization header (see `isNewApiKey` in ./lib/fetch). Other services use `this.fetch`.
373
+ this.functionsFetch = fetchWithAuth(
374
+ supabaseKey,
375
+ supabaseUrl,
376
+ this._getSessionToken.bind(this),
377
+ settings.global.fetch,
378
+ settings.tracePropagation,
379
+ { omitApiKeyAsBearer: true }
380
+ )
367
381
  this.realtime = this._initRealtimeClient({
368
382
  headers: this.headers,
369
383
  accessToken: this._getAccessToken.bind(this),
@@ -404,7 +418,7 @@ export default class SupabaseClient<
404
418
  get functions(): FunctionsClient {
405
419
  return new FunctionsClient(this.functionsUrl.href, {
406
420
  headers: this.headers,
407
- customFetch: this.fetch,
421
+ customFetch: this.functionsFetch,
408
422
  })
409
423
  }
410
424
 
@@ -568,14 +582,23 @@ export default class SupabaseClient<
568
582
  return this.realtime.removeAllChannels()
569
583
  }
570
584
 
571
- private async _getAccessToken() {
585
+ /**
586
+ * The raw session token — the custom `accessToken` result or the signed-in user's JWT —
587
+ * or `null` when there is no session. Unlike {@link _getAccessToken} it does not fall back
588
+ * to `supabaseKey`, so callers can distinguish "no session" from "has session".
589
+ */
590
+ private async _getSessionToken(): Promise<string | null> {
572
591
  if (this.accessToken) {
573
592
  return await this.accessToken()
574
593
  }
575
594
 
576
595
  const { data } = await this.auth.getSession()
577
596
 
578
- return data.session?.access_token ?? this.supabaseKey
597
+ return data.session?.access_token ?? null
598
+ }
599
+
600
+ private async _getAccessToken() {
601
+ return (await this._getSessionToken()) ?? this.supabaseKey
579
602
  }
580
603
 
581
604
  private _initSupabaseAuthClient(
package/src/lib/fetch.ts CHANGED
@@ -21,12 +21,39 @@ export const resolveHeadersConstructor = () => {
21
21
  return Headers
22
22
  }
23
23
 
24
+ /**
25
+ * New-format Supabase API keys (`sb_publishable_…` / `sb_secret_…`) are not JWTs and
26
+ * must never be sent as a Bearer token — they belong only in the `apikey` header.
27
+ * Legacy (JWT) keys predate this format (they don't start with `sb_`) and keep their
28
+ * existing behavior. Any other `sb_`-prefixed key is an unrecognized future subtype;
29
+ * see {@link assertSupportedApiKey}.
30
+ */
31
+ const isNewApiKey = (key: string): boolean =>
32
+ key.startsWith('sb_publishable_') || key.startsWith('sb_secret_')
33
+
34
+ /**
35
+ * Fail fast on an `sb_`-family key whose subtype this SDK version does not recognize, so a
36
+ * future key type can never be silently mistreated as a legacy JWT and sent as a Bearer
37
+ * token. Recognized new-format keys and legacy JWT keys (no `sb_` prefix) pass through.
38
+ * The key itself is never included in the message to avoid leaking secret keys.
39
+ */
40
+ export const assertSupportedApiKey = (key: string): void => {
41
+ if (key.startsWith('sb_') && !isNewApiKey(key)) {
42
+ throw new Error(
43
+ '@supabase/supabase-js: Unrecognized Supabase API key format. Expected a legacy JWT key ' +
44
+ 'or a new-format key (sb_publishable_… / sb_secret_…). This "sb_" key type is not ' +
45
+ 'supported by this version of the SDK — please upgrade @supabase/supabase-js.'
46
+ )
47
+ }
48
+ }
49
+
24
50
  export const fetchWithAuth = (
25
51
  supabaseKey: string,
26
52
  supabaseUrl: string,
27
53
  getAccessToken: () => Promise<string | null>,
28
54
  customFetch?: Fetch,
29
- tracePropagationOptions?: TracePropagationOptions
55
+ tracePropagationOptions?: TracePropagationOptions,
56
+ options?: { omitApiKeyAsBearer?: boolean }
30
57
  ): Fetch => {
31
58
  const fetch = resolveFetch(customFetch)
32
59
  const HeadersConstructor = resolveHeadersConstructor()
@@ -39,8 +66,13 @@ export const fetchWithAuth = (
39
66
  ? getDefaultPropagationTargets(supabaseUrl)
40
67
  : null
41
68
 
69
+ // Whether the API key may be used as the `Authorization` Bearer fallback when there is no
70
+ // session token. Disabled for Edge Functions with a new-format key (see `isNewApiKey`).
71
+ // Static per instance, so it is computed once here rather than on every request.
72
+ const allowKeyAsBearer = !(options?.omitApiKeyAsBearer && isNewApiKey(supabaseKey))
73
+
42
74
  return async (input, init) => {
43
- const accessToken = (await getAccessToken()) ?? supabaseKey
75
+ const realToken = await getAccessToken()
44
76
  let headers = new HeadersConstructor(init?.headers)
45
77
 
46
78
  if (!headers.has('apikey')) {
@@ -48,7 +80,10 @@ export const fetchWithAuth = (
48
80
  }
49
81
 
50
82
  if (!headers.has('Authorization')) {
51
- headers.set('Authorization', `Bearer ${accessToken}`)
83
+ const bearer = realToken ?? (allowKeyAsBearer ? supabaseKey : null)
84
+ if (bearer) {
85
+ headers.set('Authorization', `Bearer ${bearer}`)
86
+ }
52
87
  }
53
88
 
54
89
  if (traceTargets) {
@@ -4,4 +4,4 @@
4
4
  // - Debugging and support (identifying which version is running)
5
5
  // - Telemetry and logging (version reporting in errors/analytics)
6
6
  // - Ensuring build artifacts match the published package version
7
- export const version = '2.110.3'
7
+ export const version = '2.110.4-canary.0'