Init
This commit is contained in:
+11
@@ -0,0 +1,11 @@
|
||||
# Agent instructions for `@supabase/realtime-js`
|
||||
|
||||
Listen to realtime updates from a Supabase PostgreSQL database. Handles channels, presence, broadcast, and Postgres CDC subscriptions.
|
||||
|
||||
When helping a user work with this package:
|
||||
|
||||
- **Usage and quick start**: [`README.md`](./README.md)
|
||||
- **Full API reference**: [`src/`](./src/) — every public method and type has TSDoc with `@example` blocks. Read the source for the canonical, version-pinned answer.
|
||||
- **Migration notes**: [`migrations/`](./migrations/) — per-theme markdown files for changes that need caller action.
|
||||
|
||||
For broader Supabase guidance (docs site, MCP tools, schema and project workflows), use the centralized [Supabase agent skills](https://github.com/supabase/agent-skills): `npx skills add supabase/agent-skills`. Or pair with the Supabase MCP server via the [Supabase Plugin for AI Coding Agents](https://supabase.com/docs/guides/getting-started/plugins).
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Supabase
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+447
@@ -0,0 +1,447 @@
|
||||
<br />
|
||||
<p align="center">
|
||||
<a href="https://supabase.io">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/supabase/supabase/master/packages/common/assets/images/supabase-logo-wordmark--dark.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/supabase/supabase/master/packages/common/assets/images/supabase-logo-wordmark--light.svg">
|
||||
<img alt="Supabase Logo" width="300" src="https://raw.githubusercontent.com/supabase/supabase/master/packages/common/assets/images/logo-preview.jpg">
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
<h1 align="center">Supabase Realtime JS SDK</h1>
|
||||
|
||||
<h3 align="center">Send ephemeral messages with <b>Broadcast</b>, track and synchronize state with <b>Presence</b>, and listen to database changes with <b>Postgres Change Data Capture (CDC)</b>.</h3>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://supabase.com/docs/guides/realtime">Guides</a>
|
||||
·
|
||||
<a href="https://supabase.com/docs/reference/javascript">Reference Docs</a>
|
||||
·
|
||||
<a href="https://multiplayer.dev">Multiplayer Demo</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://github.com/supabase/supabase-js/actions?query=branch%3Amaster)
|
||||
[](https://www.npmjs.com/package/@supabase/realtime-js)
|
||||
[](#license)
|
||||
[](https://pkg.pr.new/~/supabase/realtime-js)
|
||||
|
||||
</div>
|
||||
|
||||
# Overview
|
||||
|
||||
This SDK enables you to use the following Supabase Realtime's features:
|
||||
|
||||
- **Broadcast**: send ephemeral messages from client to clients with minimal latency. Use cases include sharing cursor positions between users.
|
||||
- **Presence**: track and synchronize shared state across clients with the help of CRDTs. Use cases include tracking which users are currently viewing a specific webpage.
|
||||
- **Postgres Change Data Capture (CDC)**: listen for changes in your PostgreSQL database and send them to clients.
|
||||
|
||||
# Usage
|
||||
|
||||
## Installing the Package
|
||||
|
||||
```bash
|
||||
npm install @supabase/realtime-js
|
||||
```
|
||||
|
||||
## Creating a Channel
|
||||
|
||||
```js
|
||||
import { RealtimeClient } from '@supabase/realtime-js'
|
||||
|
||||
const client = new RealtimeClient(REALTIME_URL, {
|
||||
params: {
|
||||
apikey: API_KEY,
|
||||
},
|
||||
})
|
||||
|
||||
const channel = client.channel('test-channel', {})
|
||||
|
||||
channel.subscribe((status, err) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
console.log('Connected!')
|
||||
}
|
||||
|
||||
if (status === 'CHANNEL_ERROR') {
|
||||
console.log(`There was an error subscribing to channel: ${err.message}`)
|
||||
}
|
||||
|
||||
if (status === 'TIMED_OUT') {
|
||||
console.log('Realtime server did not respond in time.')
|
||||
}
|
||||
|
||||
if (status === 'CLOSED') {
|
||||
console.log('Realtime channel was unexpectedly closed.')
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Notes:
|
||||
|
||||
- `REALTIME_URL` is `'ws://localhost:4000/socket'` when developing locally and `'wss://<project_ref>.supabase.co/realtime/v1'` when connecting to your Supabase project.
|
||||
- `API_KEY` is a JWT whose claims must contain `exp` and `role` (existing database role).
|
||||
- Channel name can be any `string`.
|
||||
- Setting `private` to `true` means that the client will use RLS to determine if the user can connect or not to a given channel.
|
||||
|
||||
## Broadcast
|
||||
|
||||
Your client can send and receive messages based on the `event`.
|
||||
|
||||
```js
|
||||
// Setup...
|
||||
|
||||
const channel = client.channel('broadcast-test', { broadcast: { ack: false, self: false } })
|
||||
|
||||
channel.on('broadcast', { event: 'some-event' }, (payload) => console.log(payload))
|
||||
|
||||
channel.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
// Send message to other clients listening to 'broadcast-test' channel
|
||||
await channel.send({
|
||||
type: 'broadcast',
|
||||
event: 'some-event',
|
||||
payload: { hello: 'world' },
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Notes:
|
||||
|
||||
- Setting `ack` to `true` means that the `channel.send` promise will resolve once server replies with acknowledgment that it received the broadcast message request.
|
||||
- Setting `self` to `true` means that the client will receive the broadcast message it sent out.
|
||||
|
||||
### Broadcast Replay
|
||||
|
||||
Broadcast Replay enables **private** channels to access messages that were sent earlier. Only messages published via [Broadcast From the Database](https://supabase.com/docs/guides/realtime/broadcast#trigger-broadcast-messages-from-your-database) are available for replay.
|
||||
|
||||
You can configure replay with the following options:
|
||||
|
||||
- **`since`** (Required): The epoch timestamp in milliseconds, specifying the earliest point from which messages should be retrieved.
|
||||
- **`limit`** (Optional): The number of messages to return. This must be a positive integer, with a maximum value of 25.
|
||||
|
||||
Example:
|
||||
|
||||
```typescript
|
||||
const twelveHours = 12 * 60 * 60 * 1000
|
||||
const twelveHoursAgo = Date.now() - twelveHours
|
||||
|
||||
const config = { private: true, broadcast: { replay: { since: twelveHoursAgo, limit: 10 } } }
|
||||
|
||||
supabase
|
||||
.channel('main:room', { config })
|
||||
.on('broadcast', { event: 'my_event' }, (payload) => {
|
||||
if (payload?.meta?.replayed) {
|
||||
console.log('This message was sent earlier:', payload)
|
||||
} else {
|
||||
console.log('This is a new message', payload)
|
||||
}
|
||||
// ...
|
||||
})
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
## Presence
|
||||
|
||||
Your client can track and sync state that's stored in the channel.
|
||||
|
||||
```js
|
||||
// Setup...
|
||||
|
||||
const channel = client.channel('presence-test', {
|
||||
config: {
|
||||
presence: {
|
||||
key: '',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
channel.on('presence', { event: 'sync' }, () => {
|
||||
console.log('Online users: ', channel.presenceState())
|
||||
})
|
||||
|
||||
channel.on('presence', { event: 'join' }, ({ newPresences }) => {
|
||||
console.log('New users have joined: ', newPresences)
|
||||
})
|
||||
|
||||
channel.on('presence', { event: 'leave' }, ({ leftPresences }) => {
|
||||
console.log('Users have left: ', leftPresences)
|
||||
})
|
||||
|
||||
channel.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
const status = await channel.track({ user_id: 1 })
|
||||
console.log(status)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
> `config.presence.enabled` (set automatically if you add an `.on('presence', ...)` listener)
|
||||
> controls whether _this_ client receives presence state and updates from other clients —
|
||||
> without it, `presenceState()` stays empty for you. It does not affect whether other clients
|
||||
> see you: calling `track()` always makes you visible to subscribers that do have presence
|
||||
> enabled. On RLS-protected channels, receiving presence updates additionally requires the
|
||||
> `presence.read` policy to authorize this client.
|
||||
|
||||
## Postgres CDC
|
||||
|
||||
Receive database changes on the client.
|
||||
|
||||
```js
|
||||
// Setup...
|
||||
|
||||
const channel = client.channel('db-changes')
|
||||
|
||||
channel.on('postgres_changes', { event: '*', schema: 'public' }, (payload) => {
|
||||
console.log('All changes in public schema: ', payload)
|
||||
})
|
||||
|
||||
channel.on(
|
||||
'postgres_changes',
|
||||
{ event: 'INSERT', schema: 'public', table: 'messages' },
|
||||
(payload) => {
|
||||
console.log('All inserts in messages table: ', payload)
|
||||
}
|
||||
)
|
||||
|
||||
channel.on(
|
||||
'postgres_changes',
|
||||
{ event: 'UPDATE', schema: 'public', table: 'users', filter: 'username=eq.Realtime' },
|
||||
(payload) => {
|
||||
console.log('All updates on users table when username is Realtime: ', payload)
|
||||
}
|
||||
)
|
||||
|
||||
channel.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
console.log('Ready to receive database changes!')
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### Filters
|
||||
|
||||
The `filter` option accepts **either** a raw string **or** a
|
||||
`postgresChangesFilter()` builder — both produce the exact same wire format, so
|
||||
you can mix and match and existing string filters keep working unchanged:
|
||||
|
||||
```js
|
||||
// Raw string — always supported, fully backward compatible
|
||||
{ event: 'UPDATE', schema: 'public', table: 'users', filter: 'id=eq.1' }
|
||||
|
||||
// Builder — type-checked, ergonomic; the SDK serializes it for you
|
||||
{ event: 'UPDATE', schema: 'public', table: 'users', filter: postgresChangesFilter().eq('id', 1) }
|
||||
```
|
||||
|
||||
A filter is a `column=operator.value` expression evaluated server-side. The
|
||||
following operators are supported:
|
||||
|
||||
| Operator | String form | Builder | Meaning |
|
||||
| --------------------- | ---------------------------- | -------------------------------------- | --------------------------------- |
|
||||
| `eq` | `id=eq.1` | `.eq('id', 1)` | equal |
|
||||
| `neq` | `id=neq.1` | `.neq('id', 1)` | not equal |
|
||||
| `lt` `lte` `gt` `gte` | `age=gte.18` | `.gte('age', 18)` | comparison |
|
||||
| `in` | `status=in.(active,pending)` | `.in('status', ['active', 'pending'])` | in list |
|
||||
| `like` `ilike` | `title=like.%foo%` | `.like('title', '%foo%')` | pattern match (case in/sensitive) |
|
||||
| `is` | `deleted_at=is.null` | `.is('deleted_at', null)` | `IS null/true/false/unknown` |
|
||||
| `match` `imatch` | `title=match.^foo` | `.match('title', '^foo')` | POSIX regex match (`~` / `~*`) |
|
||||
| `isdistinct` | `value=isdistinct.1` | `.isDistinct('value', 1)` | NULL-safe inequality |
|
||||
|
||||
**Negation** — prefix any operator with `not.` (string) or use
|
||||
`.not(column, operator, value)` (builder):
|
||||
|
||||
```js
|
||||
// String
|
||||
{ event: '*', schema: 'public', table: 'posts', filter: 'status=not.in.(draft,archived)' }
|
||||
|
||||
// Builder
|
||||
{
|
||||
event: '*',
|
||||
schema: 'public',
|
||||
table: 'posts',
|
||||
filter: postgresChangesFilter().not('status', 'in', ['draft', 'archived']),
|
||||
}
|
||||
```
|
||||
|
||||
**AND composition** — multiple conditions are combined with commas and applied
|
||||
as an `AND`. With the builder you just chain calls:
|
||||
|
||||
```js
|
||||
// String
|
||||
{ event: 'UPDATE', schema: 'public', table: 'orders', filter: 'amount=gt.100,status=in.(open,pending)' }
|
||||
|
||||
// Builder — equivalent, chained
|
||||
{
|
||||
event: 'UPDATE',
|
||||
schema: 'public',
|
||||
table: 'orders',
|
||||
filter: postgresChangesFilter().gt('amount', 100).in('status', ['open', 'pending']),
|
||||
}
|
||||
```
|
||||
|
||||
#### Building filters with `postgresChangesFilter()`
|
||||
|
||||
The builder (modeled on the `postgrest-js` filter methods) is the recommended,
|
||||
type-checked way to compose filters — but it is entirely optional; raw strings
|
||||
remain fully supported.
|
||||
|
||||
```js
|
||||
import { postgresChangesFilter } from '@supabase/realtime-js'
|
||||
|
||||
channel.on(
|
||||
'postgres_changes',
|
||||
{
|
||||
event: 'UPDATE',
|
||||
schema: 'public',
|
||||
table: 'orders',
|
||||
// → 'amount=gt.100,status=not.in.(draft,archived)'
|
||||
filter: postgresChangesFilter().gt('amount', 100).not('status', 'in', ['draft', 'archived']),
|
||||
},
|
||||
(payload) => console.log(payload)
|
||||
)
|
||||
```
|
||||
|
||||
The builder exposes `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `like`,
|
||||
`ilike`, `match`, `imatch`, `is`, `isDistinct` and `not`. Call `.build()` if you
|
||||
need the raw string yourself (e.g. to log it or store it).
|
||||
|
||||
**Values are sent verbatim** — the server has no quoting/escaping, so spaces and
|
||||
quotes are preserved as-is. The server separates conditions by commas outside
|
||||
parentheses, so a literal comma in a scalar value can't be expressed (commas
|
||||
inside `in.(…)` are fine); the builder throws on such values rather than
|
||||
silently producing a broken filter.
|
||||
|
||||
> **Note for PostgREST users:** Realtime evaluates filters server-side over a
|
||||
> single table's WAL — there is no resource embedding (`!inner`, embedded
|
||||
> filters) and no `or()` grouping. Use `%` (not `*`) for `like`/`ilike`
|
||||
> wildcards, since filters travel in the WebSocket payload rather than a URL.
|
||||
|
||||
### Selecting columns
|
||||
|
||||
Use `select` to receive only a subset of columns instead of the full row. This
|
||||
reduces payload size (helpful for large `bytea`/`jsonb` columns). The selected
|
||||
columns must be selectable by the subscribing role:
|
||||
|
||||
```js
|
||||
channel.on(
|
||||
'postgres_changes',
|
||||
{ event: '*', schema: 'public', table: 'users', select: ['id', 'first_name'] },
|
||||
(payload) => {
|
||||
// payload.new only contains { id, first_name }
|
||||
console.log(payload)
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Get All Channels
|
||||
|
||||
You can see all the channels that your client has instantiatied.
|
||||
|
||||
```js
|
||||
// Setup...
|
||||
|
||||
client.getChannels()
|
||||
```
|
||||
|
||||
## Cleanup
|
||||
|
||||
It is highly recommended that you clean up your channels after you're done with them.
|
||||
|
||||
- Remove a single channel
|
||||
|
||||
```js
|
||||
// Setup...
|
||||
|
||||
const channel = client.channel('some-channel-to-remove')
|
||||
|
||||
channel.unsubscribe()
|
||||
client.removeChannel(channel)
|
||||
```
|
||||
|
||||
- Remove all channels and close the connection
|
||||
|
||||
```js
|
||||
// Setup...
|
||||
|
||||
client.removeAllChannels()
|
||||
client.disconnect()
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
This package is part of the [Supabase JavaScript monorepo](https://github.com/supabase/supabase-js). To work on this package:
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
# Complete build (from monorepo root)
|
||||
pnpm nx build realtime-js
|
||||
|
||||
# Build with watch mode for development
|
||||
pnpm nx build realtime-js --watch
|
||||
|
||||
# Individual build targets
|
||||
pnpm nx build:main realtime-js # CommonJS build (dist/main/)
|
||||
pnpm nx build:module realtime-js # ES Modules build (dist/module/)
|
||||
|
||||
# Other useful commands
|
||||
pnpm nx clean realtime-js # Clean build artifacts
|
||||
pnpm nx lint realtime-js # Run ESLint
|
||||
pnpm nx typecheck realtime-js # TypeScript type checking
|
||||
```
|
||||
|
||||
#### Build Outputs
|
||||
|
||||
- **CommonJS (`dist/main/`)** - For Node.js environments
|
||||
- **ES Modules (`dist/module/`)** - For modern bundlers (Webpack, Vite, Rollup)
|
||||
- **TypeScript definitions (`dist/module/index.d.ts`)** - Type definitions for TypeScript projects
|
||||
|
||||
Note: Unlike some other packages, realtime-js doesn't include a UMD build since it's primarily used in Node.js or bundled applications.
|
||||
|
||||
#### Validating Package Exports
|
||||
|
||||
```bash
|
||||
# Check if package exports are correctly configured
|
||||
pnpm nx check-exports realtime-js
|
||||
```
|
||||
|
||||
This command uses ["Are the types wrong?"](https://github.com/arethetypeswrong/arethetypeswrong.github.io) to verify that the package exports work correctly in different environments. Run this before publishing to ensure your package can be imported correctly by all consumers.
|
||||
|
||||
### Testing
|
||||
|
||||
**No Docker or Supabase instance required!** The realtime-js tests use mocked WebSocket connections, so they're completely self-contained.
|
||||
|
||||
```bash
|
||||
# Run unit tests (from monorepo root)
|
||||
pnpm nx test realtime-js
|
||||
|
||||
# Run tests with coverage report
|
||||
pnpm nx test:coverage realtime-js
|
||||
|
||||
# Run tests in watch mode during development
|
||||
pnpm nx test:watch realtime-js
|
||||
```
|
||||
|
||||
#### Test Scripts Explained
|
||||
|
||||
- **test** - Runs all unit tests once using Vitest
|
||||
- **test:coverage** - Runs tests and generates coverage report with terminal output
|
||||
- **test:watch** - Runs tests in interactive watch mode for development
|
||||
|
||||
The tests mock WebSocket connections using `mock-socket`, so you can run them anytime without any external dependencies.
|
||||
|
||||
### Contributing
|
||||
|
||||
We welcome contributions! Please see our [Contributing Guide](../../../CONTRIBUTING.md) for details on how to get started.
|
||||
|
||||
For major changes or if you're unsure about something, please open an issue first to discuss your proposed changes.
|
||||
|
||||
## Credits
|
||||
|
||||
This repo draws heavily from [phoenix-js](https://github.com/phoenixframework/phoenix/tree/master/assets/js/phoenix).
|
||||
|
||||
## License
|
||||
|
||||
MIT.
|
||||
+575
@@ -0,0 +1,575 @@
|
||||
import type { ChannelState } from './lib/constants';
|
||||
import type RealtimeClient from './RealtimeClient';
|
||||
import RealtimePresence, { REALTIME_PRESENCE_LISTEN_EVENTS } from './RealtimePresence';
|
||||
import type { RealtimePresenceJoinPayload, RealtimePresenceLeavePayload, RealtimePresenceState } from './RealtimePresence';
|
||||
import { ChannelBindingCallback } from './phoenix/types';
|
||||
import type { Timer } from './phoenix/types';
|
||||
import { RealtimePostgresFilterBuilder } from './RealtimePostgresFilterBuilder';
|
||||
export type { RealtimePostgresChangesFilterOperator } from './RealtimePostgresFilterBuilder';
|
||||
export { RealtimePostgresFilterBuilder, postgresChangesFilter, } from './RealtimePostgresFilterBuilder';
|
||||
type ReplayOption = {
|
||||
since: number;
|
||||
limit?: number;
|
||||
};
|
||||
export type RealtimeChannelOptions = {
|
||||
config: {
|
||||
/**
|
||||
* self option enables client to receive message it broadcast
|
||||
* ack option instructs server to acknowledge that broadcast message was received
|
||||
* replay option instructs server to replay broadcast messages
|
||||
* replication_ready option instructs the server to emit a `system` event once the
|
||||
* Postgres replication connection backing this channel is established and ready to
|
||||
* stream changes. Listen for it with `channel.on('system', {}, (payload) => ...)`;
|
||||
* the payload's `status` is `'ok'` (`message: 'Replication connection established'`)
|
||||
* on success or `'error'` if the connection is not ready in time.
|
||||
*/
|
||||
broadcast?: {
|
||||
self?: boolean;
|
||||
ack?: boolean;
|
||||
replay?: ReplayOption;
|
||||
replication_ready?: boolean;
|
||||
};
|
||||
/**
|
||||
* key option is used to track presence payload across clients
|
||||
*
|
||||
* enabled controls whether this client receives presence state and updates from other
|
||||
* clients — set it to true (or add an `.on('presence', ...)` listener, which enables it
|
||||
* automatically) if you want to see who else is present. Without it, this client's
|
||||
* `presenceState()` stays empty and no `presence` events fire for you, because the
|
||||
* underlying presence state machine buffers incoming updates until it has received an
|
||||
* initial snapshot, which is only requested when this flag is set.
|
||||
*
|
||||
* It does not gate the other direction: calling `track()` always makes this client
|
||||
* visible to other subscribers that have presence enabled, regardless of this client's
|
||||
* own `enabled` setting. On RLS-protected (private) channels, receiving presence updates
|
||||
* additionally requires the `presence.read` policy to authorize this client.
|
||||
*/
|
||||
presence?: {
|
||||
key?: string;
|
||||
enabled?: boolean;
|
||||
};
|
||||
/**
|
||||
* defines if the channel is private or not and if RLS policies will be used to check data
|
||||
*/
|
||||
private?: boolean;
|
||||
};
|
||||
};
|
||||
type RealtimeChangesPayloadBase = {
|
||||
schema: string;
|
||||
table: string;
|
||||
};
|
||||
type RealtimeBroadcastChangesPayloadBase = RealtimeChangesPayloadBase & {
|
||||
id: string;
|
||||
};
|
||||
export type RealtimeBroadcastInsertPayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = RealtimeBroadcastChangesPayloadBase & {
|
||||
operation: `${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.INSERT}`;
|
||||
record: T;
|
||||
old_record: null;
|
||||
};
|
||||
export type RealtimeBroadcastUpdatePayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = RealtimeBroadcastChangesPayloadBase & {
|
||||
operation: `${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.UPDATE}`;
|
||||
record: T;
|
||||
old_record: T;
|
||||
};
|
||||
export type RealtimeBroadcastDeletePayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = RealtimeBroadcastChangesPayloadBase & {
|
||||
operation: `${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.DELETE}`;
|
||||
record: null;
|
||||
old_record: T;
|
||||
};
|
||||
export type RealtimeBroadcastPayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = RealtimeBroadcastInsertPayload<T> | RealtimeBroadcastUpdatePayload<T> | RealtimeBroadcastDeletePayload<T>;
|
||||
type RealtimePostgresChangesPayloadBase = {
|
||||
schema: string;
|
||||
table: string;
|
||||
commit_timestamp: string;
|
||||
errors: string[];
|
||||
};
|
||||
export type RealtimePostgresInsertPayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = RealtimePostgresChangesPayloadBase & {
|
||||
eventType: `${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.INSERT}`;
|
||||
new: T;
|
||||
old: {};
|
||||
};
|
||||
export type RealtimePostgresUpdatePayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = RealtimePostgresChangesPayloadBase & {
|
||||
eventType: `${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.UPDATE}`;
|
||||
new: T;
|
||||
old: Partial<T>;
|
||||
};
|
||||
export type RealtimePostgresDeletePayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = RealtimePostgresChangesPayloadBase & {
|
||||
eventType: `${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.DELETE}`;
|
||||
new: {};
|
||||
old: Partial<T>;
|
||||
};
|
||||
export type RealtimePostgresChangesPayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = RealtimePostgresInsertPayload<T> | RealtimePostgresUpdatePayload<T> | RealtimePostgresDeletePayload<T>;
|
||||
export type RealtimePostgresChangesFilter<T extends `${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT}`> = {
|
||||
/**
|
||||
* The type of database change to listen to.
|
||||
*/
|
||||
event: T;
|
||||
/**
|
||||
* The database schema to listen to.
|
||||
*/
|
||||
schema: string;
|
||||
/**
|
||||
* The database table to listen to.
|
||||
*/
|
||||
table?: string;
|
||||
/**
|
||||
* Receive database changes only when the filter is matched.
|
||||
*
|
||||
* A filter is a `column=operator.value` expression, e.g. `id=eq.1` or
|
||||
* `title=like.%foo%`. See {@link RealtimePostgresChangesFilterOperator} for
|
||||
* the available operators.
|
||||
*
|
||||
* Multiple filters can be combined with commas; they are applied as an `AND`
|
||||
* condition: `filter: 'id=gt.0,id=lt.100'`.
|
||||
*
|
||||
* Any operator can be negated with the `not.` prefix: `filter: 'status=not.in.(draft,archived)'`.
|
||||
*
|
||||
* The server splits conditions on commas outside quotes/parentheses. To
|
||||
* include a reserved character (`,`, `(`, `)`) in a value, wrap it in double
|
||||
* quotes PostgREST-style: `name=eq."a,b"`. The {@link RealtimePostgresFilterBuilder}
|
||||
* does this quoting for you.
|
||||
*
|
||||
* Instead of a raw string you can pass a {@link RealtimePostgresFilterBuilder}
|
||||
* (via `postgresChangesFilter()`) for a type-checked, ergonomic way to compose filters; the
|
||||
* SDK serializes it to a string automatically.
|
||||
*/
|
||||
filter?: string | RealtimePostgresFilterBuilder;
|
||||
/**
|
||||
* Restrict the change payload to a subset of columns instead of receiving the
|
||||
* full row. Reduces payload size (helpful for large `bytea`/`jsonb` columns)
|
||||
* and the data transferred per event.
|
||||
*
|
||||
* The listed columns must be selectable by the subscribing role.
|
||||
*
|
||||
* @example
|
||||
* channel.on('postgres_changes', {
|
||||
* event: '*',
|
||||
* schema: 'public',
|
||||
* table: 'users',
|
||||
* select: ['id', 'first_name'],
|
||||
* }, (payload) => {
|
||||
* // payload.new only contains { id, first_name }
|
||||
* })
|
||||
*/
|
||||
select?: string[];
|
||||
};
|
||||
export type RealtimeChannelSendResponse = 'ok' | 'timed out' | 'error' | (string & {});
|
||||
/**
|
||||
* Payload of a `system` event emitted by the server.
|
||||
*
|
||||
* Most notably, when a channel is created with `config.broadcast.replication_ready: true`,
|
||||
* the server sends one of these once the Postgres replication connection is ready
|
||||
* (`status: 'ok'`) or fails to become ready in time (`status: 'error'`).
|
||||
*/
|
||||
export type RealtimeSystemPayload = {
|
||||
/** The extension that produced the message, e.g. `'system'` or `'postgres_changes'`. */
|
||||
extension: 'system' | 'postgres_changes' | (string & {});
|
||||
/** `'ok'` on success, `'error'` on failure. */
|
||||
status: 'ok' | 'error' | (string & {});
|
||||
/** Human-readable description, e.g. `'Replication connection established'`. */
|
||||
message: string;
|
||||
/** The channel (sub)topic the message refers to. */
|
||||
channel: string;
|
||||
};
|
||||
export declare enum REALTIME_POSTGRES_CHANGES_LISTEN_EVENT {
|
||||
ALL = "*",
|
||||
INSERT = "INSERT",
|
||||
UPDATE = "UPDATE",
|
||||
DELETE = "DELETE"
|
||||
}
|
||||
export declare enum REALTIME_LISTEN_TYPES {
|
||||
BROADCAST = "broadcast",
|
||||
PRESENCE = "presence",
|
||||
POSTGRES_CHANGES = "postgres_changes",
|
||||
SYSTEM = "system"
|
||||
}
|
||||
export declare enum REALTIME_SUBSCRIBE_STATES {
|
||||
SUBSCRIBED = "SUBSCRIBED",
|
||||
TIMED_OUT = "TIMED_OUT",
|
||||
CLOSED = "CLOSED",
|
||||
CHANNEL_ERROR = "CHANNEL_ERROR"
|
||||
}
|
||||
export declare const REALTIME_CHANNEL_STATES: {
|
||||
readonly closed: "closed";
|
||||
readonly errored: "errored";
|
||||
readonly joined: "joined";
|
||||
readonly joining: "joining";
|
||||
readonly leaving: "leaving";
|
||||
};
|
||||
type Binding = {
|
||||
type: string;
|
||||
filter: {
|
||||
[key: string]: any;
|
||||
};
|
||||
callback: ChannelBindingCallback;
|
||||
ref: number;
|
||||
id?: string;
|
||||
};
|
||||
/** A channel is the basic building block of Realtime
|
||||
* and narrows the scope of data flow to subscribed clients.
|
||||
* You can think of a channel as a chatroom where participants are able to see who's online
|
||||
* and send and receive messages.
|
||||
*/
|
||||
export default class RealtimeChannel {
|
||||
/** Topic name can be any string. */
|
||||
topic: string;
|
||||
params: RealtimeChannelOptions;
|
||||
socket: RealtimeClient;
|
||||
bindings: Record<string, Binding[]>;
|
||||
subTopic: string;
|
||||
broadcastEndpointURL: string;
|
||||
private: boolean;
|
||||
presence: RealtimePresence;
|
||||
get state(): ChannelState;
|
||||
set state(state: ChannelState);
|
||||
get joinedOnce(): boolean;
|
||||
get timeout(): number;
|
||||
get joinPush(): import("@supabase/phoenix").Push;
|
||||
get rejoinTimer(): Timer;
|
||||
/**
|
||||
* Creates a channel that can broadcast messages, sync presence, and listen to Postgres changes.
|
||||
*
|
||||
* The topic determines which realtime stream you are subscribing to. Config options let you
|
||||
* enable acknowledgement for broadcasts, presence tracking, or private channels.
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Using supabase-js (recommended)
|
||||
* ```ts
|
||||
* import { createClient } from '@supabase/supabase-js'
|
||||
*
|
||||
* const supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key')
|
||||
* const channel = supabase.channel('room1')
|
||||
* channel
|
||||
* .on('broadcast', { event: 'cursor-pos' }, (payload) => console.log(payload))
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @example Standalone import for bundle-sensitive environments
|
||||
* ```ts
|
||||
* import RealtimeClient from '@supabase/realtime-js'
|
||||
*
|
||||
* const client = new RealtimeClient('https://xyzcompany.supabase.co/realtime/v1', {
|
||||
* params: { apikey: 'your-publishable-key' },
|
||||
* })
|
||||
* const channel = new RealtimeChannel('realtime:public:messages', { config: {} }, client)
|
||||
* ```
|
||||
*/
|
||||
constructor(
|
||||
/** Topic name can be any string. */
|
||||
topic: string, params: RealtimeChannelOptions | undefined, socket: RealtimeClient);
|
||||
/**
|
||||
* Subscribe registers your client with the server.
|
||||
*
|
||||
* The optional `callback` receives a `status` and, on failure, an `err` argument.
|
||||
* Log the full `err` so its `cause`, `name`, and any structured fields aren't hidden
|
||||
* behind `err.message`.
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Handling errors
|
||||
* ```js
|
||||
* supabase.channel('room1').subscribe((status, err) => {
|
||||
* if (status === 'CHANNEL_ERROR' || status === 'TIMED_OUT') {
|
||||
* // Log the full error: its `cause` often holds the underlying reason.
|
||||
* console.error(status, err)
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
subscribe(callback?: (status: REALTIME_SUBSCRIBE_STATES, err?: Error) => void, timeout?: number): RealtimeChannel;
|
||||
private _updatePostgresBindings;
|
||||
/**
|
||||
* Returns the current presence state for this channel.
|
||||
*
|
||||
* The shape is a map keyed by presence key (for example a user id) where each entry contains the
|
||||
* tracked metadata for that user.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
presenceState<T extends {
|
||||
[key: string]: any;
|
||||
} = {}>(): RealtimePresenceState<T>;
|
||||
/**
|
||||
* Sends the supplied payload to the presence tracker so other subscribers can see that this
|
||||
* client is online. Use `untrack` to stop broadcasting presence for the same key.
|
||||
*
|
||||
* Tracking makes this client visible to other subscribers immediately, regardless of this
|
||||
* channel's `config.presence.enabled` setting or whether it has a `presence` listener — that
|
||||
* flag only affects whether *this* client receives presence updates from others (and, on
|
||||
* RLS-protected channels, whether it's authorized to do so).
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
track(payload: {
|
||||
[key: string]: any;
|
||||
}, opts?: {
|
||||
[key: string]: any;
|
||||
}): Promise<RealtimeChannelSendResponse>;
|
||||
/**
|
||||
* Removes the current presence state for this client.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
untrack(opts?: {
|
||||
[key: string]: any;
|
||||
}): Promise<RealtimeChannelSendResponse>;
|
||||
/**
|
||||
* Listen for presence events on this channel — when peers join, leave, or
|
||||
* sync presence state.
|
||||
*/
|
||||
on(type: `${REALTIME_LISTEN_TYPES.PRESENCE}`, filter: {
|
||||
event: `${REALTIME_PRESENCE_LISTEN_EVENTS.SYNC}`;
|
||||
}, callback: () => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.PRESENCE}`, filter: {
|
||||
event: `${REALTIME_PRESENCE_LISTEN_EVENTS.JOIN}`;
|
||||
}, callback: (payload: RealtimePresenceJoinPayload<T>) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.PRESENCE}`, filter: {
|
||||
event: `${REALTIME_PRESENCE_LISTEN_EVENTS.LEAVE}`;
|
||||
}, callback: (payload: RealtimePresenceLeavePayload<T>) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.PRESENCE}`, filter: {
|
||||
event: '*';
|
||||
}, callback: (payload?: RealtimePresenceJoinPayload<T> | RealtimePresenceLeavePayload<T>) => void): RealtimeChannel;
|
||||
/**
|
||||
* Listen for Postgres database changes (insert / update / delete) streamed
|
||||
* over this channel.
|
||||
*/
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.POSTGRES_CHANGES}`, filter: RealtimePostgresChangesFilter<`${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.ALL}`>, callback: (payload: RealtimePostgresChangesPayload<T>) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.POSTGRES_CHANGES}`, filter: RealtimePostgresChangesFilter<`${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.INSERT}`>, callback: (payload: RealtimePostgresInsertPayload<T>) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.POSTGRES_CHANGES}`, filter: RealtimePostgresChangesFilter<`${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.UPDATE}`>, callback: (payload: RealtimePostgresUpdatePayload<T>) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.POSTGRES_CHANGES}`, filter: RealtimePostgresChangesFilter<`${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.DELETE}`>, callback: (payload: RealtimePostgresDeletePayload<T>) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.POSTGRES_CHANGES}`, filter: RealtimePostgresChangesFilter<`${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT}`>, callback: (payload: RealtimePostgresChangesPayload<T>) => void): RealtimeChannel;
|
||||
/**
|
||||
* Listen for broadcast messages sent on this channel.
|
||||
*
|
||||
* @param type One of "broadcast", "presence", or "postgres_changes".
|
||||
* @param filter Custom object specific to the Realtime feature detailing which payloads to receive.
|
||||
* @param callback Function to be invoked when event handler is triggered.
|
||||
*/
|
||||
on(type: `${REALTIME_LISTEN_TYPES.BROADCAST}`, filter: {
|
||||
event: string;
|
||||
}, callback: (payload: {
|
||||
type: `${REALTIME_LISTEN_TYPES.BROADCAST}`;
|
||||
event: string;
|
||||
meta?: {
|
||||
replayed?: boolean;
|
||||
id: string;
|
||||
};
|
||||
[key: string]: any;
|
||||
}) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.BROADCAST}`, filter: {
|
||||
event: string;
|
||||
}, callback: (payload: {
|
||||
type: `${REALTIME_LISTEN_TYPES.BROADCAST}`;
|
||||
event: string;
|
||||
meta?: {
|
||||
replayed?: boolean;
|
||||
id: string;
|
||||
};
|
||||
payload: T;
|
||||
}) => void): RealtimeChannel;
|
||||
on<T extends Record<string, unknown>>(type: `${REALTIME_LISTEN_TYPES.BROADCAST}`, filter: {
|
||||
event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.ALL;
|
||||
}, callback: (payload: {
|
||||
type: `${REALTIME_LISTEN_TYPES.BROADCAST}`;
|
||||
event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.ALL;
|
||||
payload: RealtimeBroadcastPayload<T>;
|
||||
}) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.BROADCAST}`, filter: {
|
||||
event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.INSERT;
|
||||
}, callback: (payload: {
|
||||
type: `${REALTIME_LISTEN_TYPES.BROADCAST}`;
|
||||
event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.INSERT;
|
||||
payload: RealtimeBroadcastInsertPayload<T>;
|
||||
}) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.BROADCAST}`, filter: {
|
||||
event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.UPDATE;
|
||||
}, callback: (payload: {
|
||||
type: `${REALTIME_LISTEN_TYPES.BROADCAST}`;
|
||||
event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.UPDATE;
|
||||
payload: RealtimeBroadcastUpdatePayload<T>;
|
||||
}) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.BROADCAST}`, filter: {
|
||||
event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.DELETE;
|
||||
}, callback: (payload: {
|
||||
type: `${REALTIME_LISTEN_TYPES.BROADCAST}`;
|
||||
event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.DELETE;
|
||||
payload: RealtimeBroadcastDeletePayload<T>;
|
||||
}) => void): RealtimeChannel;
|
||||
/**
|
||||
* Listen for `system` events on this channel.
|
||||
*
|
||||
* The payload follows the {@link RealtimeSystemPayload} shape. Opt in to the replication-ready
|
||||
* notification with `config.broadcast.replication_ready: true` when creating the channel, then
|
||||
* watch for `payload.status === 'ok'` to know the Postgres replication connection is ready.
|
||||
*
|
||||
* @example Know when the replication connection is ready
|
||||
* ```js
|
||||
* const channel = supabase.channel('room1', {
|
||||
* config: { broadcast: { replication_ready: true } },
|
||||
* })
|
||||
*
|
||||
* channel
|
||||
* .on('postgres_changes', { event: '*', schema: 'public', table: 'messages' }, (payload) => {
|
||||
* console.log('Change received!', payload)
|
||||
* })
|
||||
* .on('system', {}, (payload) => {
|
||||
* if (payload.extension === 'system' && payload.status === 'ok') {
|
||||
* console.log('Replication connection is ready:', payload.message)
|
||||
* }
|
||||
* })
|
||||
* .subscribe()
|
||||
* ```
|
||||
*/
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.SYSTEM}`, filter: {}, callback: (payload: any) => void): RealtimeChannel;
|
||||
/**
|
||||
* Sends a broadcast message explicitly via REST API.
|
||||
*
|
||||
* This method always uses the REST API endpoint regardless of WebSocket connection state.
|
||||
* Useful when you want to guarantee REST delivery or when gradually migrating from implicit REST fallback.
|
||||
*
|
||||
* Payloads that are `ArrayBuffer` or `ArrayBufferView` (e.g. `Uint8Array`) are sent as
|
||||
* `application/octet-stream`; all other payloads are JSON-encoded.
|
||||
*
|
||||
* @param event The name of the broadcast event
|
||||
* @param payload Payload to be sent (required)
|
||||
* @param opts Options including timeout
|
||||
* @returns Promise resolving to object with success status, and error details if failed
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
httpSend(event: string, payload: any, opts?: {
|
||||
timeout?: number;
|
||||
}): Promise<{
|
||||
success: true;
|
||||
} | {
|
||||
success: false;
|
||||
status: number;
|
||||
error: string;
|
||||
}>;
|
||||
/**
|
||||
* Sends a message into the channel.
|
||||
*
|
||||
* @param args Arguments to send to channel
|
||||
* @param args.type The type of event to send
|
||||
* @param args.event The name of the event being sent
|
||||
* @param args.payload Payload to be sent
|
||||
* @param opts Options to be used during the send process
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @remarks
|
||||
* - When using REST you don't need to subscribe to the channel
|
||||
* - REST calls are only available from 2.37.0 onwards
|
||||
* - If you create a channel only to send a REST broadcast, remove it from
|
||||
* the client when the send completes
|
||||
*
|
||||
* @example Send a message via websocket
|
||||
* ```js
|
||||
* const channel = supabase.channel('room1')
|
||||
*
|
||||
* channel.subscribe((status) => {
|
||||
* if (status === 'SUBSCRIBED') {
|
||||
* channel.send({
|
||||
* type: 'broadcast',
|
||||
* event: 'cursor-pos',
|
||||
* payload: { x: Math.random(), y: Math.random() },
|
||||
* })
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @exampleResponse Send a message via websocket
|
||||
* ```js
|
||||
* ok | timed out | error
|
||||
* ```
|
||||
*
|
||||
* @example Send a message via REST
|
||||
* ```js
|
||||
* const channel = supabase.channel('room1')
|
||||
*
|
||||
* try {
|
||||
* await channel.httpSend('cursor-pos', { x: Math.random(), y: Math.random() })
|
||||
* } finally {
|
||||
* await supabase.removeChannel(channel)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
send(args: {
|
||||
type: 'broadcast' | 'presence' | 'postgres_changes';
|
||||
event: string;
|
||||
payload?: any;
|
||||
[key: string]: any;
|
||||
}, opts?: {
|
||||
[key: string]: any;
|
||||
}): Promise<RealtimeChannelSendResponse>;
|
||||
/**
|
||||
* Updates the payload that will be sent the next time the channel joins (reconnects).
|
||||
* Useful for rotating access tokens or updating config without re-creating the channel.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
updateJoinPayload(payload: Record<string, any>): void;
|
||||
/**
|
||||
* Leaves the channel.
|
||||
*
|
||||
* Unsubscribes from server events, and instructs channel to terminate on server.
|
||||
* Triggers onClose() hooks.
|
||||
*
|
||||
* To receive leave acknowledgements, use the a `receive` hook to bind to the server ack, ie:
|
||||
* channel.unsubscribe().receive("ok", () => alert("left!") )
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
unsubscribe(timeout?: number): Promise<RealtimeChannelSendResponse>;
|
||||
/**
|
||||
* Destroys and stops related timers.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
teardown(): void;
|
||||
copyBindings(other: RealtimeChannel): void;
|
||||
}
|
||||
//# sourceMappingURL=RealtimeChannel.d.ts.map
|
||||
+1
File diff suppressed because one or more lines are too long
+765
@@ -0,0 +1,765 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.REALTIME_CHANNEL_STATES = exports.REALTIME_SUBSCRIBE_STATES = exports.REALTIME_LISTEN_TYPES = exports.REALTIME_POSTGRES_CHANGES_LISTEN_EVENT = exports.postgresChangesFilter = exports.RealtimePostgresFilterBuilder = void 0;
|
||||
const tslib_1 = require("tslib");
|
||||
const constants_1 = require("./lib/constants");
|
||||
const RealtimePresence_1 = tslib_1.__importDefault(require("./RealtimePresence"));
|
||||
const Transformers = tslib_1.__importStar(require("./lib/transformers"));
|
||||
const transformers_1 = require("./lib/transformers");
|
||||
const normalizeChannelError_1 = require("./lib/normalizeChannelError");
|
||||
const channelAdapter_1 = tslib_1.__importDefault(require("./phoenix/channelAdapter"));
|
||||
const RealtimePostgresFilterBuilder_1 = require("./RealtimePostgresFilterBuilder");
|
||||
var RealtimePostgresFilterBuilder_2 = require("./RealtimePostgresFilterBuilder");
|
||||
Object.defineProperty(exports, "RealtimePostgresFilterBuilder", { enumerable: true, get: function () { return RealtimePostgresFilterBuilder_2.RealtimePostgresFilterBuilder; } });
|
||||
Object.defineProperty(exports, "postgresChangesFilter", { enumerable: true, get: function () { return RealtimePostgresFilterBuilder_2.postgresChangesFilter; } });
|
||||
var REALTIME_POSTGRES_CHANGES_LISTEN_EVENT;
|
||||
(function (REALTIME_POSTGRES_CHANGES_LISTEN_EVENT) {
|
||||
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["ALL"] = "*";
|
||||
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["INSERT"] = "INSERT";
|
||||
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["UPDATE"] = "UPDATE";
|
||||
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["DELETE"] = "DELETE";
|
||||
})(REALTIME_POSTGRES_CHANGES_LISTEN_EVENT || (exports.REALTIME_POSTGRES_CHANGES_LISTEN_EVENT = REALTIME_POSTGRES_CHANGES_LISTEN_EVENT = {}));
|
||||
var REALTIME_LISTEN_TYPES;
|
||||
(function (REALTIME_LISTEN_TYPES) {
|
||||
REALTIME_LISTEN_TYPES["BROADCAST"] = "broadcast";
|
||||
REALTIME_LISTEN_TYPES["PRESENCE"] = "presence";
|
||||
REALTIME_LISTEN_TYPES["POSTGRES_CHANGES"] = "postgres_changes";
|
||||
REALTIME_LISTEN_TYPES["SYSTEM"] = "system";
|
||||
})(REALTIME_LISTEN_TYPES || (exports.REALTIME_LISTEN_TYPES = REALTIME_LISTEN_TYPES = {}));
|
||||
var REALTIME_SUBSCRIBE_STATES;
|
||||
(function (REALTIME_SUBSCRIBE_STATES) {
|
||||
REALTIME_SUBSCRIBE_STATES["SUBSCRIBED"] = "SUBSCRIBED";
|
||||
REALTIME_SUBSCRIBE_STATES["TIMED_OUT"] = "TIMED_OUT";
|
||||
REALTIME_SUBSCRIBE_STATES["CLOSED"] = "CLOSED";
|
||||
REALTIME_SUBSCRIBE_STATES["CHANNEL_ERROR"] = "CHANNEL_ERROR";
|
||||
})(REALTIME_SUBSCRIBE_STATES || (exports.REALTIME_SUBSCRIBE_STATES = REALTIME_SUBSCRIBE_STATES = {}));
|
||||
exports.REALTIME_CHANNEL_STATES = constants_1.CHANNEL_STATES;
|
||||
/** A channel is the basic building block of Realtime
|
||||
* and narrows the scope of data flow to subscribed clients.
|
||||
* You can think of a channel as a chatroom where participants are able to see who's online
|
||||
* and send and receive messages.
|
||||
*/
|
||||
class RealtimeChannel {
|
||||
get state() {
|
||||
return this.channelAdapter.state;
|
||||
}
|
||||
set state(state) {
|
||||
this.channelAdapter.state = state;
|
||||
}
|
||||
get joinedOnce() {
|
||||
return this.channelAdapter.joinedOnce;
|
||||
}
|
||||
get timeout() {
|
||||
return this.socket.timeout;
|
||||
}
|
||||
get joinPush() {
|
||||
return this.channelAdapter.joinPush;
|
||||
}
|
||||
get rejoinTimer() {
|
||||
return this.channelAdapter.rejoinTimer;
|
||||
}
|
||||
/**
|
||||
* Creates a channel that can broadcast messages, sync presence, and listen to Postgres changes.
|
||||
*
|
||||
* The topic determines which realtime stream you are subscribing to. Config options let you
|
||||
* enable acknowledgement for broadcasts, presence tracking, or private channels.
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Using supabase-js (recommended)
|
||||
* ```ts
|
||||
* import { createClient } from '@supabase/supabase-js'
|
||||
*
|
||||
* const supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key')
|
||||
* const channel = supabase.channel('room1')
|
||||
* channel
|
||||
* .on('broadcast', { event: 'cursor-pos' }, (payload) => console.log(payload))
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @example Standalone import for bundle-sensitive environments
|
||||
* ```ts
|
||||
* import RealtimeClient from '@supabase/realtime-js'
|
||||
*
|
||||
* const client = new RealtimeClient('https://xyzcompany.supabase.co/realtime/v1', {
|
||||
* params: { apikey: 'your-publishable-key' },
|
||||
* })
|
||||
* const channel = new RealtimeChannel('realtime:public:messages', { config: {} }, client)
|
||||
* ```
|
||||
*/
|
||||
constructor(
|
||||
/** Topic name can be any string. */
|
||||
topic, params = { config: {} }, socket) {
|
||||
var _a, _b;
|
||||
this.topic = topic;
|
||||
this.params = params;
|
||||
this.socket = socket;
|
||||
this.bindings = {};
|
||||
this.subTopic = topic.replace(/^realtime:/i, '');
|
||||
this.params.config = Object.assign({
|
||||
broadcast: { ack: false, self: false },
|
||||
presence: { key: '', enabled: false },
|
||||
private: false,
|
||||
}, params.config);
|
||||
this.channelAdapter = new channelAdapter_1.default(this.socket.socketAdapter, topic, this.params);
|
||||
this.presence = new RealtimePresence_1.default(this);
|
||||
this._onClose(() => {
|
||||
this.socket._remove(this);
|
||||
});
|
||||
this._updateFilterTransform();
|
||||
this.broadcastEndpointURL = (0, transformers_1.httpEndpointURL)(this.socket.socketAdapter.endPointURL());
|
||||
this.private = this.params.config.private || false;
|
||||
if (!this.private && ((_b = (_a = this.params.config) === null || _a === void 0 ? void 0 : _a.broadcast) === null || _b === void 0 ? void 0 : _b.replay)) {
|
||||
throw new Error(`tried to use replay on public channel '${this.topic}'. It must be a private channel.`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Subscribe registers your client with the server.
|
||||
*
|
||||
* The optional `callback` receives a `status` and, on failure, an `err` argument.
|
||||
* Log the full `err` so its `cause`, `name`, and any structured fields aren't hidden
|
||||
* behind `err.message`.
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Handling errors
|
||||
* ```js
|
||||
* supabase.channel('room1').subscribe((status, err) => {
|
||||
* if (status === 'CHANNEL_ERROR' || status === 'TIMED_OUT') {
|
||||
* // Log the full error: its `cause` often holds the underlying reason.
|
||||
* console.error(status, err)
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
subscribe(callback, timeout = this.timeout) {
|
||||
var _a, _b, _c;
|
||||
if (!this.socket.isConnected()) {
|
||||
this.socket.connect();
|
||||
}
|
||||
if (this.channelAdapter.isClosed()) {
|
||||
const { config: { broadcast, presence, private: isPrivate }, } = this.params;
|
||||
const postgres_changes = (_b = (_a = this.bindings.postgres_changes) === null || _a === void 0 ? void 0 : _a.map((r) => r.filter)) !== null && _b !== void 0 ? _b : [];
|
||||
const presence_enabled = (!!this.bindings[REALTIME_LISTEN_TYPES.PRESENCE] &&
|
||||
this.bindings[REALTIME_LISTEN_TYPES.PRESENCE].length > 0) ||
|
||||
((_c = this.params.config.presence) === null || _c === void 0 ? void 0 : _c.enabled) === true;
|
||||
const accessTokenPayload = {};
|
||||
const config = {
|
||||
broadcast,
|
||||
presence: Object.assign(Object.assign({}, presence), { enabled: presence_enabled }),
|
||||
postgres_changes,
|
||||
private: isPrivate,
|
||||
};
|
||||
if (this.socket.accessTokenValue) {
|
||||
accessTokenPayload.access_token = this.socket.accessTokenValue;
|
||||
}
|
||||
this._onError((reason) => {
|
||||
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, (0, normalizeChannelError_1.normalizeChannelError)(reason));
|
||||
});
|
||||
this._onClose(() => callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CLOSED));
|
||||
this.updateJoinPayload(Object.assign({ config }, accessTokenPayload));
|
||||
this._updateFilterMessage();
|
||||
this.channelAdapter
|
||||
.subscribe(timeout)
|
||||
.receive('ok', async ({ postgres_changes }) => {
|
||||
// Only refresh auth if using callback-based tokens
|
||||
if (!this.socket._isManualToken()) {
|
||||
this.socket.setAuth();
|
||||
}
|
||||
if (postgres_changes === undefined) {
|
||||
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED);
|
||||
return;
|
||||
}
|
||||
this._updatePostgresBindings(postgres_changes, callback);
|
||||
})
|
||||
.receive('error', (error) => {
|
||||
this.state = constants_1.CHANNEL_STATES.errored;
|
||||
const message = Object.values(error).join(', ') || 'error';
|
||||
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, new Error(message, { cause: error }));
|
||||
})
|
||||
.receive('timeout', () => {
|
||||
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.TIMED_OUT);
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
_updatePostgresBindings(postgres_changes, callback) {
|
||||
var _a;
|
||||
const clientPostgresBindings = this.bindings.postgres_changes;
|
||||
const bindingsLen = (_a = clientPostgresBindings === null || clientPostgresBindings === void 0 ? void 0 : clientPostgresBindings.length) !== null && _a !== void 0 ? _a : 0;
|
||||
const newPostgresBindings = [];
|
||||
for (let i = 0; i < bindingsLen; i++) {
|
||||
const clientPostgresBinding = clientPostgresBindings[i];
|
||||
const { filter: { event, schema, table, filter }, } = clientPostgresBinding;
|
||||
const serverPostgresFilter = postgres_changes && postgres_changes[i];
|
||||
if (serverPostgresFilter &&
|
||||
serverPostgresFilter.event === event &&
|
||||
RealtimeChannel.isFilterValueEqual(serverPostgresFilter.schema, schema) &&
|
||||
RealtimeChannel.isFilterValueEqual(serverPostgresFilter.table, table) &&
|
||||
RealtimeChannel.isFilterValueEqual(serverPostgresFilter.filter, filter)) {
|
||||
newPostgresBindings.push(Object.assign(Object.assign({}, clientPostgresBinding), { id: serverPostgresFilter.id }));
|
||||
}
|
||||
else {
|
||||
this.unsubscribe();
|
||||
this.state = constants_1.CHANNEL_STATES.errored;
|
||||
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, new Error('mismatch between server and client bindings for postgres changes'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.bindings.postgres_changes = newPostgresBindings;
|
||||
if (this.state != constants_1.CHANNEL_STATES.errored && callback) {
|
||||
callback(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns the current presence state for this channel.
|
||||
*
|
||||
* The shape is a map keyed by presence key (for example a user id) where each entry contains the
|
||||
* tracked metadata for that user.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
presenceState() {
|
||||
return this.presence.state;
|
||||
}
|
||||
/**
|
||||
* Sends the supplied payload to the presence tracker so other subscribers can see that this
|
||||
* client is online. Use `untrack` to stop broadcasting presence for the same key.
|
||||
*
|
||||
* Tracking makes this client visible to other subscribers immediately, regardless of this
|
||||
* channel's `config.presence.enabled` setting or whether it has a `presence` listener — that
|
||||
* flag only affects whether *this* client receives presence updates from others (and, on
|
||||
* RLS-protected channels, whether it's authorized to do so).
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
async track(payload, opts = {}) {
|
||||
return await this.send({
|
||||
type: 'presence',
|
||||
event: 'track',
|
||||
payload,
|
||||
}, opts);
|
||||
}
|
||||
/**
|
||||
* Removes the current presence state for this client.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
async untrack(opts = {}) {
|
||||
return await this.send({
|
||||
type: 'presence',
|
||||
event: 'untrack',
|
||||
}, opts);
|
||||
}
|
||||
/**
|
||||
* Listen to realtime events on this channel.
|
||||
* @category Realtime
|
||||
*
|
||||
* @remarks
|
||||
* - By default, Broadcast and Presence are enabled for all projects.
|
||||
* - By default, listening to database changes is disabled for new projects due to database performance and security concerns. You can turn it on by managing Realtime's [replication](/docs/guides/api#realtime-api-overview).
|
||||
* - You can receive the "previous" data for updates and deletes by setting the table's `REPLICA IDENTITY` to `FULL` (e.g., `ALTER TABLE your_table REPLICA IDENTITY FULL;`).
|
||||
* - Row level security is not applied to delete statements. When RLS is enabled and replica identity is set to full, only the primary key is sent to clients.
|
||||
*
|
||||
* @example Listen to broadcast messages
|
||||
* ```js
|
||||
* const channel = supabase.channel("room1")
|
||||
*
|
||||
* channel.on("broadcast", { event: "cursor-pos" }, (payload) => {
|
||||
* console.log("Cursor position received!", payload);
|
||||
* }).subscribe((status) => {
|
||||
* if (status === "SUBSCRIBED") {
|
||||
* channel.send({
|
||||
* type: "broadcast",
|
||||
* event: "cursor-pos",
|
||||
* payload: { x: Math.random(), y: Math.random() },
|
||||
* });
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @example Listen to presence sync
|
||||
* ```js
|
||||
* const channel = supabase.channel('room1')
|
||||
* channel
|
||||
* .on('presence', { event: 'sync' }, () => {
|
||||
* console.log('Synced presence state: ', channel.presenceState())
|
||||
* })
|
||||
* .subscribe(async (status) => {
|
||||
* if (status === 'SUBSCRIBED') {
|
||||
* await channel.track({ online_at: new Date().toISOString() })
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @example Listen to presence join
|
||||
* ```js
|
||||
* const channel = supabase.channel('room1')
|
||||
* channel
|
||||
* .on('presence', { event: 'join' }, ({ newPresences }) => {
|
||||
* console.log('Newly joined presences: ', newPresences)
|
||||
* })
|
||||
* .subscribe(async (status) => {
|
||||
* if (status === 'SUBSCRIBED') {
|
||||
* await channel.track({ online_at: new Date().toISOString() })
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @example Listen to presence leave
|
||||
* ```js
|
||||
* const channel = supabase.channel('room1')
|
||||
* channel
|
||||
* .on('presence', { event: 'leave' }, ({ leftPresences }) => {
|
||||
* console.log('Newly left presences: ', leftPresences)
|
||||
* })
|
||||
* .subscribe(async (status) => {
|
||||
* if (status === 'SUBSCRIBED') {
|
||||
* await channel.track({ online_at: new Date().toISOString() })
|
||||
* await channel.untrack()
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @example Listen to all database changes
|
||||
* ```js
|
||||
* supabase
|
||||
* .channel('room1')
|
||||
* .on('postgres_changes', { event: '*', schema: '*' }, payload => {
|
||||
* console.log('Change received!', payload)
|
||||
* })
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @example Listen to a specific table
|
||||
* ```js
|
||||
* supabase
|
||||
* .channel('room1')
|
||||
* .on('postgres_changes', { event: '*', schema: 'public', table: 'countries' }, payload => {
|
||||
* console.log('Change received!', payload)
|
||||
* })
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @example Listen to inserts
|
||||
* ```js
|
||||
* supabase
|
||||
* .channel('room1')
|
||||
* .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'countries' }, payload => {
|
||||
* console.log('Change received!', payload)
|
||||
* })
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @exampleDescription Listen to updates
|
||||
* By default, Supabase will send only the updated record. If you want to receive the previous values as well you can
|
||||
* enable full replication for the table you are listening to:
|
||||
*
|
||||
* ```sql
|
||||
* alter table "your_table" replica identity full;
|
||||
* ```
|
||||
*
|
||||
* @example Listen to updates
|
||||
* ```js
|
||||
* supabase
|
||||
* .channel('room1')
|
||||
* .on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'countries' }, payload => {
|
||||
* console.log('Change received!', payload)
|
||||
* })
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @exampleDescription Listen to deletes
|
||||
* By default, Supabase does not send deleted records. If you want to receive the deleted record you can
|
||||
* enable full replication for the table you are listening to:
|
||||
*
|
||||
* ```sql
|
||||
* alter table "your_table" replica identity full;
|
||||
* ```
|
||||
*
|
||||
* @example Listen to deletes
|
||||
* ```js
|
||||
* supabase
|
||||
* .channel('room1')
|
||||
* .on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'countries' }, payload => {
|
||||
* console.log('Change received!', payload)
|
||||
* })
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @exampleDescription Listen to multiple events
|
||||
* You can chain listeners if you want to listen to multiple events for each table.
|
||||
*
|
||||
* @example Listen to multiple events
|
||||
* ```js
|
||||
* supabase
|
||||
* .channel('room1')
|
||||
* .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'countries' }, handleRecordInserted)
|
||||
* .on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'countries' }, handleRecordDeleted)
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @exampleDescription Listen to row level changes
|
||||
* You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match.
|
||||
*
|
||||
* @example Listen to row level changes
|
||||
* ```js
|
||||
* supabase
|
||||
* .channel('room1')
|
||||
* .on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'countries', filter: 'id=eq.200' }, handleRecordUpdated)
|
||||
* .subscribe()
|
||||
* ```
|
||||
*/
|
||||
on(type, filter, callback) {
|
||||
const stateCheck = this.channelAdapter.isJoined() || this.channelAdapter.isJoining();
|
||||
const typeCheck = type === REALTIME_LISTEN_TYPES.PRESENCE || type === REALTIME_LISTEN_TYPES.POSTGRES_CHANGES;
|
||||
if (stateCheck && typeCheck) {
|
||||
this.socket.log('channel', `cannot add \`${type}\` callbacks for ${this.topic} after \`subscribe()\`.`);
|
||||
throw new Error(`cannot add \`${type}\` callbacks for ${this.topic} after \`subscribe()\`.`);
|
||||
}
|
||||
return this._on(type, filter, callback);
|
||||
}
|
||||
/**
|
||||
* Sends a broadcast message explicitly via REST API.
|
||||
*
|
||||
* This method always uses the REST API endpoint regardless of WebSocket connection state.
|
||||
* Useful when you want to guarantee REST delivery or when gradually migrating from implicit REST fallback.
|
||||
*
|
||||
* Payloads that are `ArrayBuffer` or `ArrayBufferView` (e.g. `Uint8Array`) are sent as
|
||||
* `application/octet-stream`; all other payloads are JSON-encoded.
|
||||
*
|
||||
* @param event The name of the broadcast event
|
||||
* @param payload Payload to be sent (required)
|
||||
* @param opts Options including timeout
|
||||
* @returns Promise resolving to object with success status, and error details if failed
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
async httpSend(event, payload, opts = {}) {
|
||||
var _a;
|
||||
if (payload === undefined || payload === null) {
|
||||
return Promise.reject(new Error('Payload is required for httpSend()'));
|
||||
}
|
||||
const isBinary = payload instanceof ArrayBuffer || ArrayBuffer.isView(payload);
|
||||
const headers = {
|
||||
apikey: this.socket.apiKey ? this.socket.apiKey : '',
|
||||
'Content-Type': isBinary ? 'application/octet-stream' : 'application/json',
|
||||
};
|
||||
if (this.socket.accessTokenValue) {
|
||||
headers['Authorization'] = `Bearer ${this.socket.accessTokenValue}`;
|
||||
}
|
||||
const url = new URL(this.broadcastEndpointURL);
|
||||
url.pathname += `/${encodeURIComponent(this.subTopic)}/events/${encodeURIComponent(event)}`;
|
||||
if (this.private) {
|
||||
url.searchParams.set('private', 'true');
|
||||
}
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: isBinary ? payload : JSON.stringify(payload),
|
||||
};
|
||||
const response = await this._fetchWithTimeout(url.toString(), options, (_a = opts.timeout) !== null && _a !== void 0 ? _a : this.timeout);
|
||||
if (response.status === 202) {
|
||||
return { success: true };
|
||||
}
|
||||
if (response.status === 404) {
|
||||
return Promise.reject(new Error('httpSend() requires Realtime server v2.97.0 or newer; the endpoint returned 404. ' +
|
||||
'Update your Supabase CLI to a recent version, or upgrade the Realtime server in your self-hosted setup. ' +
|
||||
'See https://github.com/supabase/supabase-js/blob/master/packages/core/realtime-js/migrations/httpsend-server-version.md'));
|
||||
}
|
||||
let errorMessage = response.statusText;
|
||||
try {
|
||||
const errorBody = await response.json();
|
||||
errorMessage = errorBody.error || errorBody.message || errorMessage;
|
||||
}
|
||||
catch (_b) { }
|
||||
return Promise.reject(new Error(errorMessage));
|
||||
}
|
||||
/**
|
||||
* Sends a message into the channel.
|
||||
*
|
||||
* @param args Arguments to send to channel
|
||||
* @param args.type The type of event to send
|
||||
* @param args.event The name of the event being sent
|
||||
* @param args.payload Payload to be sent
|
||||
* @param opts Options to be used during the send process
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @remarks
|
||||
* - When using REST you don't need to subscribe to the channel
|
||||
* - REST calls are only available from 2.37.0 onwards
|
||||
* - If you create a channel only to send a REST broadcast, remove it from
|
||||
* the client when the send completes
|
||||
*
|
||||
* @example Send a message via websocket
|
||||
* ```js
|
||||
* const channel = supabase.channel('room1')
|
||||
*
|
||||
* channel.subscribe((status) => {
|
||||
* if (status === 'SUBSCRIBED') {
|
||||
* channel.send({
|
||||
* type: 'broadcast',
|
||||
* event: 'cursor-pos',
|
||||
* payload: { x: Math.random(), y: Math.random() },
|
||||
* })
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @exampleResponse Send a message via websocket
|
||||
* ```js
|
||||
* ok | timed out | error
|
||||
* ```
|
||||
*
|
||||
* @example Send a message via REST
|
||||
* ```js
|
||||
* const channel = supabase.channel('room1')
|
||||
*
|
||||
* try {
|
||||
* await channel.httpSend('cursor-pos', { x: Math.random(), y: Math.random() })
|
||||
* } finally {
|
||||
* await supabase.removeChannel(channel)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
async send(args, opts = {}) {
|
||||
var _a, _b;
|
||||
if (!this.channelAdapter.canPush() && args.type === 'broadcast') {
|
||||
console.warn('Realtime send() is automatically falling back to REST API. ' +
|
||||
'This behavior will be deprecated in the future. ' +
|
||||
'Please use httpSend() explicitly for REST delivery.');
|
||||
const { event, payload: endpoint_payload } = args;
|
||||
const headers = {
|
||||
apikey: this.socket.apiKey ? this.socket.apiKey : '',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if (this.socket.accessTokenValue) {
|
||||
headers['Authorization'] = `Bearer ${this.socket.accessTokenValue}`;
|
||||
}
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
messages: [
|
||||
{
|
||||
topic: this.subTopic,
|
||||
event,
|
||||
payload: endpoint_payload,
|
||||
private: this.private,
|
||||
},
|
||||
],
|
||||
}),
|
||||
};
|
||||
try {
|
||||
const response = await this._fetchWithTimeout(this.broadcastEndpointURL, options, (_a = opts.timeout) !== null && _a !== void 0 ? _a : this.timeout);
|
||||
await ((_b = response.body) === null || _b === void 0 ? void 0 : _b.cancel());
|
||||
return response.ok ? 'ok' : 'error';
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
return 'timed out';
|
||||
}
|
||||
else {
|
||||
return 'error';
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return new Promise((resolve) => {
|
||||
var _a, _b, _c;
|
||||
const push = this.channelAdapter.push(args.type, args, opts.timeout || this.timeout);
|
||||
if (args.type === 'broadcast' && !((_c = (_b = (_a = this.params) === null || _a === void 0 ? void 0 : _a.config) === null || _b === void 0 ? void 0 : _b.broadcast) === null || _c === void 0 ? void 0 : _c.ack)) {
|
||||
resolve('ok');
|
||||
}
|
||||
push.receive('ok', () => resolve('ok'));
|
||||
push.receive('error', () => resolve('error'));
|
||||
push.receive('timeout', () => resolve('timed out'));
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Updates the payload that will be sent the next time the channel joins (reconnects).
|
||||
* Useful for rotating access tokens or updating config without re-creating the channel.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
updateJoinPayload(payload) {
|
||||
this.channelAdapter.updateJoinPayload(payload);
|
||||
}
|
||||
/**
|
||||
* Leaves the channel.
|
||||
*
|
||||
* Unsubscribes from server events, and instructs channel to terminate on server.
|
||||
* Triggers onClose() hooks.
|
||||
*
|
||||
* To receive leave acknowledgements, use the a `receive` hook to bind to the server ack, ie:
|
||||
* channel.unsubscribe().receive("ok", () => alert("left!") )
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
async unsubscribe(timeout = this.timeout) {
|
||||
return new Promise((resolve) => {
|
||||
this.channelAdapter
|
||||
.unsubscribe(timeout)
|
||||
.receive('ok', () => resolve('ok'))
|
||||
.receive('timeout', () => resolve('timed out'))
|
||||
.receive('error', () => resolve('error'));
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Destroys and stops related timers.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
teardown() {
|
||||
this.channelAdapter.teardown();
|
||||
}
|
||||
/** @internal */
|
||||
async _fetchWithTimeout(url, options, timeout) {
|
||||
const controller = new AbortController();
|
||||
const id = setTimeout(() => controller.abort(), timeout);
|
||||
const response = await this.socket.fetch(url, Object.assign(Object.assign({}, options), { signal: controller.signal }));
|
||||
clearTimeout(id);
|
||||
return response;
|
||||
}
|
||||
/** @internal */
|
||||
_on(type, filter, callback) {
|
||||
const typeLower = type.toLocaleLowerCase();
|
||||
// Serialize a postgres_changes filter builder into its string form so the
|
||||
// rest of the pipeline (join payload, server binding match) sees a string.
|
||||
// Duck-type `build()` in addition to `instanceof` so a builder constructed
|
||||
// against a duplicate copy of the package (separate module realm) still works.
|
||||
const filterValue = filter === null || filter === void 0 ? void 0 : filter.filter;
|
||||
if (filterValue instanceof RealtimePostgresFilterBuilder_1.RealtimePostgresFilterBuilder ||
|
||||
(typeof filterValue === 'object' &&
|
||||
filterValue !== null &&
|
||||
typeof filterValue.build === 'function')) {
|
||||
filter = Object.assign(Object.assign({}, filter), { filter: filterValue.build() });
|
||||
}
|
||||
const ref = this.channelAdapter.on(type, callback);
|
||||
const binding = {
|
||||
type: typeLower,
|
||||
filter: filter,
|
||||
callback: callback,
|
||||
ref: ref,
|
||||
};
|
||||
if (this.bindings[typeLower]) {
|
||||
this.bindings[typeLower].push(binding);
|
||||
}
|
||||
else {
|
||||
this.bindings[typeLower] = [binding];
|
||||
}
|
||||
this._updateFilterMessage();
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Registers a callback that will be executed when the channel closes.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_onClose(callback) {
|
||||
this.channelAdapter.onClose(callback);
|
||||
}
|
||||
/**
|
||||
* Registers a callback that will be executed when the channel encounteres an error.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_onError(callback) {
|
||||
this.channelAdapter.onError(callback);
|
||||
}
|
||||
/** @internal */
|
||||
_updateFilterMessage() {
|
||||
this.channelAdapter.updateFilterBindings((binding, payload, ref) => {
|
||||
var _a, _b, _c, _d, _e, _f, _g;
|
||||
const typeLower = binding.event.toLocaleLowerCase();
|
||||
if (this._notThisChannelEvent(typeLower, ref)) {
|
||||
return false;
|
||||
}
|
||||
const bind = (_a = this.bindings[typeLower]) === null || _a === void 0 ? void 0 : _a.find((bind) => bind.ref === binding.ref);
|
||||
if (!bind) {
|
||||
return true;
|
||||
}
|
||||
if (['broadcast', 'presence', 'postgres_changes'].includes(typeLower)) {
|
||||
if ('id' in bind) {
|
||||
const bindId = bind.id;
|
||||
const bindEvent = (_b = bind.filter) === null || _b === void 0 ? void 0 : _b.event;
|
||||
return (bindId &&
|
||||
((_c = payload.ids) === null || _c === void 0 ? void 0 : _c.includes(bindId)) &&
|
||||
(bindEvent === '*' ||
|
||||
(bindEvent === null || bindEvent === void 0 ? void 0 : bindEvent.toLocaleLowerCase()) === ((_d = payload.data) === null || _d === void 0 ? void 0 : _d.type.toLocaleLowerCase())));
|
||||
}
|
||||
else {
|
||||
const bindEvent = (_f = (_e = bind === null || bind === void 0 ? void 0 : bind.filter) === null || _e === void 0 ? void 0 : _e.event) === null || _f === void 0 ? void 0 : _f.toLocaleLowerCase();
|
||||
return bindEvent === '*' || bindEvent === ((_g = payload === null || payload === void 0 ? void 0 : payload.event) === null || _g === void 0 ? void 0 : _g.toLocaleLowerCase());
|
||||
}
|
||||
}
|
||||
else {
|
||||
return bind.type.toLocaleLowerCase() === typeLower;
|
||||
}
|
||||
});
|
||||
}
|
||||
/** @internal */
|
||||
_notThisChannelEvent(event, ref) {
|
||||
const { close, error, leave, join } = constants_1.CHANNEL_EVENTS;
|
||||
const events = [close, error, leave, join];
|
||||
return ref && events.includes(event) && ref !== this.joinPush.ref;
|
||||
}
|
||||
/** @internal */
|
||||
_updateFilterTransform() {
|
||||
this.channelAdapter.updatePayloadTransform((event, payload, ref) => {
|
||||
if (typeof payload === 'object' && 'ids' in payload) {
|
||||
const postgresChanges = payload.data;
|
||||
const { schema, table, commit_timestamp, type, errors } = postgresChanges;
|
||||
const enrichedPayload = {
|
||||
schema: schema,
|
||||
table: table,
|
||||
commit_timestamp: commit_timestamp,
|
||||
eventType: type,
|
||||
new: {},
|
||||
old: {},
|
||||
errors: errors,
|
||||
};
|
||||
return Object.assign(Object.assign({}, enrichedPayload), this._getPayloadRecords(postgresChanges));
|
||||
}
|
||||
return payload;
|
||||
});
|
||||
}
|
||||
copyBindings(other) {
|
||||
if (this.joinedOnce) {
|
||||
throw new Error('cannot copy bindings into joined channel');
|
||||
}
|
||||
for (const kind in other.bindings) {
|
||||
for (const binding of other.bindings[kind]) {
|
||||
this._on(binding.type, binding.filter, binding.callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Compares two optional filter values for equality.
|
||||
* Treats undefined, null, and empty string as equivalent empty values.
|
||||
* @internal
|
||||
*/
|
||||
static isFilterValueEqual(serverValue, clientValue) {
|
||||
const normalizedServer = serverValue !== null && serverValue !== void 0 ? serverValue : undefined;
|
||||
const normalizedClient = clientValue !== null && clientValue !== void 0 ? clientValue : undefined;
|
||||
return normalizedServer === normalizedClient;
|
||||
}
|
||||
/** @internal */
|
||||
_getPayloadRecords(payload) {
|
||||
const records = {
|
||||
new: {},
|
||||
old: {},
|
||||
};
|
||||
if (payload.type === 'INSERT' || payload.type === 'UPDATE') {
|
||||
records.new = Transformers.convertChangeData(payload.columns, payload.record);
|
||||
}
|
||||
if (payload.type === 'UPDATE' || payload.type === 'DELETE') {
|
||||
records.old = Transformers.convertChangeData(payload.columns, payload.old_record);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
}
|
||||
exports.default = RealtimeChannel;
|
||||
//# sourceMappingURL=RealtimeChannel.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+274
@@ -0,0 +1,274 @@
|
||||
import { WebSocketLike } from './lib/websocket-factory';
|
||||
import Serializer from './lib/serializer';
|
||||
import RealtimeChannel from './RealtimeChannel';
|
||||
import type { RealtimeChannelOptions } from './RealtimeChannel';
|
||||
import type { HeartbeatCallback, Encode, Decode, Timer, Vsn } from './phoenix/types';
|
||||
type Fetch = typeof fetch;
|
||||
export type LogLevel = 'info' | 'warn' | 'error' | (string & {});
|
||||
export type RealtimeMessage = {
|
||||
topic: string;
|
||||
event: string;
|
||||
payload: any;
|
||||
ref: string;
|
||||
join_ref?: string;
|
||||
};
|
||||
export type RealtimeRemoveChannelResponse = 'ok' | 'timed out' | 'error' | (string & {});
|
||||
export type HeartbeatStatus = 'sent' | 'ok' | 'error' | 'timeout' | 'disconnected' | (string & {});
|
||||
export type HeartbeatTimer = ReturnType<typeof setTimeout> | undefined;
|
||||
/**
|
||||
* Minimal WebSocket constructor interface that RealtimeClient can work with.
|
||||
* Supply a compatible implementation (native WebSocket, `ws`, etc) when running outside the browser.
|
||||
*/
|
||||
export interface WebSocketLikeConstructor {
|
||||
new (address: string | URL, subprotocols?: string | string[] | undefined): WebSocketLike;
|
||||
[key: string]: any;
|
||||
}
|
||||
export type RealtimeClientOptions = {
|
||||
transport?: WebSocketLikeConstructor;
|
||||
timeout?: number;
|
||||
heartbeatIntervalMs?: number;
|
||||
heartbeatCallback?: (status: HeartbeatStatus, latency?: number) => void;
|
||||
vsn?: string;
|
||||
logger?: (kind: string, msg: string, data?: any) => void;
|
||||
encode?: Encode<void>;
|
||||
decode?: Decode<void>;
|
||||
reconnectAfterMs?: (tries: number) => number;
|
||||
headers?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
params?: {
|
||||
[key: string]: any;
|
||||
};
|
||||
log_level?: LogLevel;
|
||||
logLevel?: LogLevel;
|
||||
fetch?: Fetch;
|
||||
worker?: boolean;
|
||||
workerUrl?: string;
|
||||
accessToken?: () => Promise<string | null>;
|
||||
disconnectOnEmptyChannelsAfterMs?: number;
|
||||
/**
|
||||
* Storage compatible object used by the underlying socket for longpoll fallback history.
|
||||
* Provide a custom implementation in environments where reading `globalThis.sessionStorage`
|
||||
* throws (sandboxed iframes, in-app webviews, "block third-party storage" privacy modes).
|
||||
* Defaults to `globalThis.sessionStorage` when accessible, otherwise an in-memory store.
|
||||
*/
|
||||
sessionStorage?: Storage;
|
||||
};
|
||||
export default class RealtimeClient {
|
||||
channels: RealtimeChannel[];
|
||||
accessTokenValue: string | null;
|
||||
accessToken: (() => Promise<string | null>) | null;
|
||||
apiKey: string | null;
|
||||
httpEndpoint: string;
|
||||
/** @deprecated headers cannot be set on websocket connections */
|
||||
headers?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
params?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
ref: number;
|
||||
logLevel?: LogLevel;
|
||||
fetch: Fetch;
|
||||
worker?: boolean;
|
||||
workerUrl?: string;
|
||||
workerRef?: Worker;
|
||||
serializer: Serializer;
|
||||
get endPoint(): string;
|
||||
get timeout(): number;
|
||||
get transport(): WebSocketLikeConstructor;
|
||||
get heartbeatCallback(): HeartbeatCallback;
|
||||
get heartbeatIntervalMs(): number;
|
||||
get heartbeatTimer(): HeartbeatTimer;
|
||||
get pendingHeartbeatRef(): string | null;
|
||||
get reconnectTimer(): Timer;
|
||||
get vsn(): Vsn;
|
||||
get encode(): Encode<void>;
|
||||
get decode(): Decode<void>;
|
||||
get reconnectAfterMs(): (tries: number) => number;
|
||||
get sendBuffer(): (() => void)[];
|
||||
get stateChangeCallbacks(): {
|
||||
open: [string, Function][];
|
||||
close: [string, Function][];
|
||||
error: [string, Function][];
|
||||
message: [string, Function][];
|
||||
};
|
||||
private _manuallySetToken;
|
||||
private _authPromise;
|
||||
private _workerHeartbeatTimer;
|
||||
private _pendingWorkerHeartbeatRef;
|
||||
private _pendingDisconnectTimer;
|
||||
private _disconnectOnEmptyChannelsAfterMs;
|
||||
/**
|
||||
* Initializes the Socket.
|
||||
*
|
||||
* @param endPoint The string WebSocket endpoint, ie, "ws://example.com/socket", "wss://example.com", "/socket" (inherited host & protocol)
|
||||
* @param options.transport The Websocket Transport, for example WebSocket. This can be a custom implementation
|
||||
* @param options.timeout The default timeout in milliseconds to trigger push timeouts.
|
||||
* @param options.params The optional params to pass when connecting.
|
||||
* @param options.headers Deprecated: headers cannot be set on websocket connections and this option will be removed in the future.
|
||||
* @param options.heartbeatIntervalMs The millisec interval to send a heartbeat message.
|
||||
* @param options.heartbeatCallback The optional function to handle heartbeat status and latency.
|
||||
* @param options.logger The optional function for specialized logging, ie: logger: (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }
|
||||
* @param options.logLevel Sets the log level for Realtime
|
||||
* @param options.encode The function to encode outgoing messages. Defaults to JSON: (payload, callback) => callback(JSON.stringify(payload))
|
||||
* @param options.decode The function to decode incoming messages. Defaults to Serializer's decode.
|
||||
* @param options.reconnectAfterMs he optional function that returns the millsec reconnect interval. Defaults to stepped backoff off.
|
||||
* @param options.worker Use Web Worker to set a side flow. Defaults to false.
|
||||
* @param options.workerUrl The URL of the worker script. Defaults to https://realtime.supabase.com/worker.js that includes a heartbeat event call to keep the connection alive.
|
||||
* @param options.vsn The protocol version to use when connecting. Supported versions are "1.0.0" and "2.0.0". Defaults to "2.0.0".
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Using supabase-js (recommended)
|
||||
* ```ts
|
||||
* import { createClient } from '@supabase/supabase-js'
|
||||
*
|
||||
* const supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key')
|
||||
* const channel = supabase.channel('room1')
|
||||
* channel
|
||||
* .on('broadcast', { event: 'cursor-pos' }, (payload) => console.log(payload))
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @example Standalone import for bundle-sensitive environments
|
||||
* ```ts
|
||||
* import RealtimeClient from '@supabase/realtime-js'
|
||||
*
|
||||
* const client = new RealtimeClient('https://xyzcompany.supabase.co/realtime/v1', {
|
||||
* params: { apikey: 'your-publishable-key' },
|
||||
* })
|
||||
* client.connect()
|
||||
* ```
|
||||
*/
|
||||
constructor(endPoint: string, options?: RealtimeClientOptions);
|
||||
/**
|
||||
* Connects the socket, unless already connected.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
connect(): void;
|
||||
/**
|
||||
* Returns the URL of the websocket.
|
||||
* @returns string The URL of the websocket.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
endpointURL(): string;
|
||||
/**
|
||||
* Disconnects the socket.
|
||||
*
|
||||
* @param code A numeric status code to send on disconnect.
|
||||
* @param reason A custom reason for the disconnect.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
disconnect(code?: number, reason?: string): Promise<"ok" | "timeout">;
|
||||
/**
|
||||
* Returns all created channels
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
getChannels(): RealtimeChannel[];
|
||||
/**
|
||||
* Unsubscribes, removes and tears down a single channel
|
||||
* @param channel A RealtimeChannel instance
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
removeChannel(channel: RealtimeChannel): Promise<RealtimeRemoveChannelResponse>;
|
||||
/**
|
||||
* Unsubscribes, removes and tears down all channels
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
removeAllChannels(): Promise<RealtimeRemoveChannelResponse[]>;
|
||||
/**
|
||||
* Logs the message.
|
||||
*
|
||||
* For customized logging, `this.logger` can be overridden in Client constructor.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
log(kind: string, msg: string, data?: any): void;
|
||||
/**
|
||||
* Returns the current state of the socket.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
connectionState(): import("./lib/constants").ConnectionState;
|
||||
/**
|
||||
* Returns `true` is the connection is open.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
isConnected(): boolean;
|
||||
/**
|
||||
* Returns `true` if the connection is currently connecting.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
isConnecting(): boolean;
|
||||
/**
|
||||
* Returns `true` if the connection is currently disconnecting.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
isDisconnecting(): boolean;
|
||||
/**
|
||||
* Creates (or reuses) a {@link RealtimeChannel} for the provided topic.
|
||||
*
|
||||
* Topics are automatically prefixed with `realtime:` to match the Realtime service.
|
||||
* If a channel with the same topic already exists it will be returned instead of creating
|
||||
* a duplicate connection.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
channel(topic: string, params?: RealtimeChannelOptions): RealtimeChannel;
|
||||
/**
|
||||
* Push out a message if the socket is connected.
|
||||
*
|
||||
* If the socket is not connected, the message gets enqueued within a local buffer, and sent out when a connection is next established.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
push(data: RealtimeMessage): void;
|
||||
/**
|
||||
* Sets the JWT access token used for channel subscription authorization and Realtime RLS.
|
||||
*
|
||||
* If param is null it will use the `accessToken` callback function or the token set on the client.
|
||||
*
|
||||
* On callback used, it will set the value of the token internal to the client.
|
||||
*
|
||||
* When a token is explicitly provided, it will be preserved across channel operations
|
||||
* (including removeChannel and resubscribe). The `accessToken` callback will not be
|
||||
* invoked until `setAuth()` is called without arguments.
|
||||
*
|
||||
* @param token A JWT string to override the token set on the client.
|
||||
*
|
||||
* @example Setting the authorization header
|
||||
* // Use a manual token (preserved across resubscribes, ignores accessToken callback)
|
||||
* client.realtime.setAuth('my-custom-jwt')
|
||||
*
|
||||
* // Switch back to using the accessToken callback
|
||||
* client.realtime.setAuth()
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
setAuth(token?: string | null): Promise<void>;
|
||||
/**
|
||||
* Sends a heartbeat message if the socket is connected.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
sendHeartbeat(): Promise<void>;
|
||||
/**
|
||||
* Sets a callback that receives lifecycle events for internal heartbeat messages.
|
||||
* Useful for instrumenting connection health (e.g. sent/ok/timeout).
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
onHeartbeat(callback: HeartbeatCallback): void;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=RealtimeClient.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RealtimeClient.d.ts","sourceRoot":"","sources":["../../src/RealtimeClient.ts"],"names":[],"mappings":"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"}
|
||||
+685
@@ -0,0 +1,685 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const tslib_1 = require("tslib");
|
||||
const websocket_factory_1 = tslib_1.__importDefault(require("./lib/websocket-factory"));
|
||||
const constants_1 = require("./lib/constants");
|
||||
const serializer_1 = tslib_1.__importDefault(require("./lib/serializer"));
|
||||
const transformers_1 = require("./lib/transformers");
|
||||
const RealtimeChannel_1 = tslib_1.__importDefault(require("./RealtimeChannel"));
|
||||
const socketAdapter_1 = tslib_1.__importDefault(require("./phoenix/socketAdapter"));
|
||||
// Connection-related constants
|
||||
const CONNECTION_TIMEOUTS = {
|
||||
HEARTBEAT_INTERVAL: 25000,
|
||||
RECONNECT_DELAY: 10,
|
||||
HEARTBEAT_TIMEOUT_FALLBACK: 100,
|
||||
};
|
||||
const RECONNECT_INTERVALS = [1000, 2000, 5000, 10000];
|
||||
const DEFAULT_RECONNECT_FALLBACK = 10000;
|
||||
function createMemorySessionStorage() {
|
||||
const store = new Map();
|
||||
return {
|
||||
get length() {
|
||||
return store.size;
|
||||
},
|
||||
clear() {
|
||||
store.clear();
|
||||
},
|
||||
getItem(key) {
|
||||
return store.has(key) ? store.get(key) : null;
|
||||
},
|
||||
key(index) {
|
||||
var _a;
|
||||
return (_a = Array.from(store.keys())[index]) !== null && _a !== void 0 ? _a : null;
|
||||
},
|
||||
removeItem(key) {
|
||||
store.delete(key);
|
||||
},
|
||||
setItem(key, value) {
|
||||
store.set(key, String(value));
|
||||
},
|
||||
};
|
||||
}
|
||||
function resolveSessionStorage() {
|
||||
try {
|
||||
if (typeof globalThis !== 'undefined' && globalThis.sessionStorage) {
|
||||
return globalThis.sessionStorage;
|
||||
}
|
||||
}
|
||||
catch (_a) {
|
||||
// Property access on `sessionStorage` itself throws in restricted-storage browsers.
|
||||
}
|
||||
return createMemorySessionStorage();
|
||||
}
|
||||
const WORKER_SCRIPT = `
|
||||
addEventListener("message", (e) => {
|
||||
if (e.data.event === "start") {
|
||||
setInterval(() => postMessage({ event: "keepAlive" }), e.data.interval);
|
||||
}
|
||||
});`;
|
||||
class RealtimeClient {
|
||||
get endPoint() {
|
||||
return this.socketAdapter.endPoint;
|
||||
}
|
||||
get timeout() {
|
||||
return this.socketAdapter.timeout;
|
||||
}
|
||||
get transport() {
|
||||
return this.socketAdapter.transport;
|
||||
}
|
||||
get heartbeatCallback() {
|
||||
return this.socketAdapter.heartbeatCallback;
|
||||
}
|
||||
get heartbeatIntervalMs() {
|
||||
return this.socketAdapter.heartbeatIntervalMs;
|
||||
}
|
||||
get heartbeatTimer() {
|
||||
if (this.worker) {
|
||||
return this._workerHeartbeatTimer;
|
||||
}
|
||||
return this.socketAdapter.heartbeatTimer;
|
||||
}
|
||||
get pendingHeartbeatRef() {
|
||||
if (this.worker) {
|
||||
return this._pendingWorkerHeartbeatRef;
|
||||
}
|
||||
return this.socketAdapter.pendingHeartbeatRef;
|
||||
}
|
||||
get reconnectTimer() {
|
||||
return this.socketAdapter.reconnectTimer;
|
||||
}
|
||||
get vsn() {
|
||||
return this.socketAdapter.vsn;
|
||||
}
|
||||
get encode() {
|
||||
return this.socketAdapter.encode;
|
||||
}
|
||||
get decode() {
|
||||
return this.socketAdapter.decode;
|
||||
}
|
||||
get reconnectAfterMs() {
|
||||
return this.socketAdapter.reconnectAfterMs;
|
||||
}
|
||||
get sendBuffer() {
|
||||
return this.socketAdapter.sendBuffer;
|
||||
}
|
||||
get stateChangeCallbacks() {
|
||||
return this.socketAdapter.stateChangeCallbacks;
|
||||
}
|
||||
/**
|
||||
* Initializes the Socket.
|
||||
*
|
||||
* @param endPoint The string WebSocket endpoint, ie, "ws://example.com/socket", "wss://example.com", "/socket" (inherited host & protocol)
|
||||
* @param options.transport The Websocket Transport, for example WebSocket. This can be a custom implementation
|
||||
* @param options.timeout The default timeout in milliseconds to trigger push timeouts.
|
||||
* @param options.params The optional params to pass when connecting.
|
||||
* @param options.headers Deprecated: headers cannot be set on websocket connections and this option will be removed in the future.
|
||||
* @param options.heartbeatIntervalMs The millisec interval to send a heartbeat message.
|
||||
* @param options.heartbeatCallback The optional function to handle heartbeat status and latency.
|
||||
* @param options.logger The optional function for specialized logging, ie: logger: (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }
|
||||
* @param options.logLevel Sets the log level for Realtime
|
||||
* @param options.encode The function to encode outgoing messages. Defaults to JSON: (payload, callback) => callback(JSON.stringify(payload))
|
||||
* @param options.decode The function to decode incoming messages. Defaults to Serializer's decode.
|
||||
* @param options.reconnectAfterMs he optional function that returns the millsec reconnect interval. Defaults to stepped backoff off.
|
||||
* @param options.worker Use Web Worker to set a side flow. Defaults to false.
|
||||
* @param options.workerUrl The URL of the worker script. Defaults to https://realtime.supabase.com/worker.js that includes a heartbeat event call to keep the connection alive.
|
||||
* @param options.vsn The protocol version to use when connecting. Supported versions are "1.0.0" and "2.0.0". Defaults to "2.0.0".
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Using supabase-js (recommended)
|
||||
* ```ts
|
||||
* import { createClient } from '@supabase/supabase-js'
|
||||
*
|
||||
* const supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key')
|
||||
* const channel = supabase.channel('room1')
|
||||
* channel
|
||||
* .on('broadcast', { event: 'cursor-pos' }, (payload) => console.log(payload))
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @example Standalone import for bundle-sensitive environments
|
||||
* ```ts
|
||||
* import RealtimeClient from '@supabase/realtime-js'
|
||||
*
|
||||
* const client = new RealtimeClient('https://xyzcompany.supabase.co/realtime/v1', {
|
||||
* params: { apikey: 'your-publishable-key' },
|
||||
* })
|
||||
* client.connect()
|
||||
* ```
|
||||
*/
|
||||
constructor(endPoint, options) {
|
||||
var _a;
|
||||
this.channels = new Array();
|
||||
this.accessTokenValue = null;
|
||||
this.accessToken = null;
|
||||
this.apiKey = null;
|
||||
this.httpEndpoint = '';
|
||||
/** @deprecated headers cannot be set on websocket connections */
|
||||
this.headers = {};
|
||||
this.params = {};
|
||||
this.ref = 0;
|
||||
this.serializer = new serializer_1.default();
|
||||
this._manuallySetToken = false;
|
||||
this._authPromise = null;
|
||||
this._workerHeartbeatTimer = undefined;
|
||||
this._pendingWorkerHeartbeatRef = null;
|
||||
this._pendingDisconnectTimer = null;
|
||||
this._disconnectOnEmptyChannelsAfterMs = 0;
|
||||
/**
|
||||
* Use either custom fetch, if provided, or default fetch to make HTTP requests
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
this._resolveFetch = (customFetch) => {
|
||||
if (customFetch) {
|
||||
return (...args) => customFetch(...args);
|
||||
}
|
||||
return (...args) => fetch(...args);
|
||||
};
|
||||
// Validate required parameters
|
||||
if (!((_a = options === null || options === void 0 ? void 0 : options.params) === null || _a === void 0 ? void 0 : _a.apikey)) {
|
||||
throw new Error('API key is required to connect to Realtime');
|
||||
}
|
||||
this.apiKey = options.params.apikey;
|
||||
const socketAdapterOptions = this._initializeOptions(options);
|
||||
this.socketAdapter = new socketAdapter_1.default(endPoint, socketAdapterOptions);
|
||||
this.httpEndpoint = (0, transformers_1.httpEndpointURL)(endPoint);
|
||||
this.fetch = this._resolveFetch(options === null || options === void 0 ? void 0 : options.fetch);
|
||||
}
|
||||
/**
|
||||
* Connects the socket, unless already connected.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
connect() {
|
||||
// Skip if already connecting, disconnecting, or connected
|
||||
if (this.isConnecting() || this.isDisconnecting() || this.isConnected()) {
|
||||
return;
|
||||
}
|
||||
// Trigger auth if needed and not already in progress
|
||||
// This ensures auth is called for standalone RealtimeClient usage
|
||||
// while avoiding race conditions with SupabaseClient's immediate setAuth call
|
||||
if (this.accessToken && !this._authPromise) {
|
||||
this._setAuthSafely('connect');
|
||||
}
|
||||
this._setupConnectionHandlers();
|
||||
try {
|
||||
this.socketAdapter.connect();
|
||||
}
|
||||
catch (error) {
|
||||
const errorMessage = error.message;
|
||||
throw new Error(`WebSocket not available: ${errorMessage}`);
|
||||
}
|
||||
this._handleNodeJsRaceCondition();
|
||||
}
|
||||
/**
|
||||
* Returns the URL of the websocket.
|
||||
* @returns string The URL of the websocket.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
endpointURL() {
|
||||
return this.socketAdapter.endPointURL();
|
||||
}
|
||||
/**
|
||||
* Disconnects the socket.
|
||||
*
|
||||
* @param code A numeric status code to send on disconnect.
|
||||
* @param reason A custom reason for the disconnect.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
async disconnect(code, reason) {
|
||||
this._cancelPendingDisconnect();
|
||||
if (this.isDisconnecting()) {
|
||||
return 'ok';
|
||||
}
|
||||
return await this.socketAdapter.disconnect(() => {
|
||||
clearInterval(this._workerHeartbeatTimer);
|
||||
this._terminateWorker();
|
||||
}, code, reason);
|
||||
}
|
||||
/**
|
||||
* Returns all created channels
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
getChannels() {
|
||||
return this.channels;
|
||||
}
|
||||
/**
|
||||
* Unsubscribes, removes and tears down a single channel
|
||||
* @param channel A RealtimeChannel instance
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
async removeChannel(channel) {
|
||||
const status = await channel.unsubscribe();
|
||||
if (status === 'ok') {
|
||||
channel.teardown();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
/**
|
||||
* Unsubscribes, removes and tears down all channels
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
async removeAllChannels() {
|
||||
const promises = this.channels.map(async (channel) => {
|
||||
const result = await channel.unsubscribe();
|
||||
channel.teardown();
|
||||
return result;
|
||||
});
|
||||
const result = await Promise.all(promises);
|
||||
await this.disconnect();
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Logs the message.
|
||||
*
|
||||
* For customized logging, `this.logger` can be overridden in Client constructor.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
log(kind, msg, data) {
|
||||
this.socketAdapter.log(kind, msg, data);
|
||||
}
|
||||
/**
|
||||
* Returns the current state of the socket.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
connectionState() {
|
||||
return this.socketAdapter.connectionState() || constants_1.CONNECTION_STATE.closed;
|
||||
}
|
||||
/**
|
||||
* Returns `true` is the connection is open.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
isConnected() {
|
||||
return this.socketAdapter.isConnected();
|
||||
}
|
||||
/**
|
||||
* Returns `true` if the connection is currently connecting.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
isConnecting() {
|
||||
return this.socketAdapter.isConnecting();
|
||||
}
|
||||
/**
|
||||
* Returns `true` if the connection is currently disconnecting.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
isDisconnecting() {
|
||||
return this.socketAdapter.isDisconnecting();
|
||||
}
|
||||
/**
|
||||
* Creates (or reuses) a {@link RealtimeChannel} for the provided topic.
|
||||
*
|
||||
* Topics are automatically prefixed with `realtime:` to match the Realtime service.
|
||||
* If a channel with the same topic already exists it will be returned instead of creating
|
||||
* a duplicate connection.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
channel(topic, params = { config: {} }) {
|
||||
const realtimeTopic = `realtime:${topic}`;
|
||||
const exists = this.getChannels().find((c) => c.topic === realtimeTopic);
|
||||
if (!exists) {
|
||||
const chan = new RealtimeChannel_1.default(`realtime:${topic}`, params, this);
|
||||
this._cancelPendingDisconnect();
|
||||
this.channels.push(chan);
|
||||
return chan;
|
||||
}
|
||||
else {
|
||||
return exists;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Push out a message if the socket is connected.
|
||||
*
|
||||
* If the socket is not connected, the message gets enqueued within a local buffer, and sent out when a connection is next established.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
push(data) {
|
||||
this.socketAdapter.push(data);
|
||||
}
|
||||
/**
|
||||
* Sets the JWT access token used for channel subscription authorization and Realtime RLS.
|
||||
*
|
||||
* If param is null it will use the `accessToken` callback function or the token set on the client.
|
||||
*
|
||||
* On callback used, it will set the value of the token internal to the client.
|
||||
*
|
||||
* When a token is explicitly provided, it will be preserved across channel operations
|
||||
* (including removeChannel and resubscribe). The `accessToken` callback will not be
|
||||
* invoked until `setAuth()` is called without arguments.
|
||||
*
|
||||
* @param token A JWT string to override the token set on the client.
|
||||
*
|
||||
* @example Setting the authorization header
|
||||
* // Use a manual token (preserved across resubscribes, ignores accessToken callback)
|
||||
* client.realtime.setAuth('my-custom-jwt')
|
||||
*
|
||||
* // Switch back to using the accessToken callback
|
||||
* client.realtime.setAuth()
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
async setAuth(token = null) {
|
||||
this._authPromise = this._performAuth(token);
|
||||
try {
|
||||
await this._authPromise;
|
||||
}
|
||||
finally {
|
||||
this._authPromise = null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns true if the current access token was explicitly set via setAuth(token),
|
||||
* false if it was obtained via the accessToken callback.
|
||||
* @internal
|
||||
*/
|
||||
_isManualToken() {
|
||||
return this._manuallySetToken;
|
||||
}
|
||||
/**
|
||||
* Sends a heartbeat message if the socket is connected.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
async sendHeartbeat() {
|
||||
this.socketAdapter.sendHeartbeat();
|
||||
}
|
||||
/**
|
||||
* Sets a callback that receives lifecycle events for internal heartbeat messages.
|
||||
* Useful for instrumenting connection health (e.g. sent/ok/timeout).
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
onHeartbeat(callback) {
|
||||
this.socketAdapter.heartbeatCallback = this._wrapHeartbeatCallback(callback);
|
||||
}
|
||||
/**
|
||||
* Return the next message ref, accounting for overflows
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_makeRef() {
|
||||
return this.socketAdapter.makeRef();
|
||||
}
|
||||
/**
|
||||
* Removes a channel from RealtimeClient
|
||||
*
|
||||
* @param channel An open subscription.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_remove(channel) {
|
||||
this.channels = this.channels.filter((c) => c.topic !== channel.topic);
|
||||
if (this.channels.length === 0) {
|
||||
this.log('transport', 'no channels remaining, scheduling disconnect');
|
||||
this._schedulePendingDisconnect();
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
_schedulePendingDisconnect() {
|
||||
this._cancelPendingDisconnect();
|
||||
if (this._disconnectOnEmptyChannelsAfterMs === 0) {
|
||||
this.log('transport', 'disconnecting immediately - no channels');
|
||||
this.disconnect();
|
||||
return;
|
||||
}
|
||||
this._pendingDisconnectTimer = setTimeout(() => {
|
||||
this._pendingDisconnectTimer = null;
|
||||
if (this.channels.length === 0) {
|
||||
this.log('transport', 'deferred disconnect fired - no channels, disconnecting');
|
||||
this.disconnect();
|
||||
}
|
||||
}, this._disconnectOnEmptyChannelsAfterMs);
|
||||
this.log('transport', `deferred disconnect scheduled in ${this._disconnectOnEmptyChannelsAfterMs}ms`);
|
||||
}
|
||||
/** @internal */
|
||||
_cancelPendingDisconnect() {
|
||||
if (this._pendingDisconnectTimer !== null) {
|
||||
this.log('transport', 'pending disconnect cancelled - channel activity detected');
|
||||
clearTimeout(this._pendingDisconnectTimer);
|
||||
this._pendingDisconnectTimer = null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Perform the actual auth operation
|
||||
* @internal
|
||||
*/
|
||||
async _performAuth(token = null) {
|
||||
let tokenToSend;
|
||||
let isManualToken = false;
|
||||
if (token) {
|
||||
tokenToSend = token;
|
||||
// Track if this is a manually-provided token
|
||||
isManualToken = true;
|
||||
}
|
||||
else if (this.accessToken) {
|
||||
// Call the accessToken callback to get fresh token
|
||||
try {
|
||||
tokenToSend = await this.accessToken();
|
||||
}
|
||||
catch (e) {
|
||||
this.log('error', 'Error fetching access token from callback', e);
|
||||
// Fall back to cached value if callback fails
|
||||
tokenToSend = this.accessTokenValue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
tokenToSend = this.accessTokenValue;
|
||||
}
|
||||
// Track whether this token was manually set or fetched via callback
|
||||
if (isManualToken) {
|
||||
this._manuallySetToken = true;
|
||||
}
|
||||
else if (this.accessToken) {
|
||||
// If we used the callback, clear the manual flag
|
||||
this._manuallySetToken = false;
|
||||
}
|
||||
if (this.accessTokenValue != tokenToSend) {
|
||||
this.accessTokenValue = tokenToSend;
|
||||
this.channels.forEach((channel) => {
|
||||
const payload = {
|
||||
access_token: tokenToSend,
|
||||
version: constants_1.DEFAULT_VERSION,
|
||||
};
|
||||
tokenToSend && channel.updateJoinPayload(payload);
|
||||
if (channel.joinedOnce && channel.channelAdapter.isJoined()) {
|
||||
channel.channelAdapter.push(constants_1.CHANNEL_EVENTS.access_token, {
|
||||
access_token: tokenToSend,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Wait for any in-flight auth operations to complete
|
||||
* @internal
|
||||
*/
|
||||
async _waitForAuthIfNeeded() {
|
||||
if (this._authPromise) {
|
||||
await this._authPromise;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Safely call setAuth with standardized error handling
|
||||
* @internal
|
||||
*/
|
||||
_setAuthSafely(context = 'general') {
|
||||
// Only refresh auth if using callback-based tokens
|
||||
if (!this._isManualToken()) {
|
||||
this.setAuth().catch((e) => {
|
||||
this.log('error', `Error setting auth in ${context}`, e);
|
||||
});
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
_setupConnectionHandlers() {
|
||||
this.socketAdapter.onOpen(() => {
|
||||
const authPromise = this._authPromise ||
|
||||
(this.accessToken && !this.accessTokenValue ? this.setAuth() : Promise.resolve());
|
||||
authPromise.catch((e) => {
|
||||
this.log('error', 'error waiting for auth on connect', e);
|
||||
});
|
||||
if (this.worker && !this.workerRef) {
|
||||
this._startWorkerHeartbeat();
|
||||
}
|
||||
});
|
||||
this.socketAdapter.onClose(() => {
|
||||
if (this.worker && this.workerRef) {
|
||||
this._terminateWorker();
|
||||
}
|
||||
});
|
||||
this.socketAdapter.onMessage((message) => {
|
||||
if (message.ref && message.ref === this._pendingWorkerHeartbeatRef) {
|
||||
this._pendingWorkerHeartbeatRef = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
/** @internal */
|
||||
_handleNodeJsRaceCondition() {
|
||||
if (this.socketAdapter.isConnected()) {
|
||||
// hack: ensure onConnOpen is called
|
||||
this.socketAdapter.getSocket().onConnOpen();
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
_wrapHeartbeatCallback(heartbeatCallback) {
|
||||
return (status, latency) => {
|
||||
if (status === 'disconnected')
|
||||
return;
|
||||
if (status == 'sent')
|
||||
this._setAuthSafely();
|
||||
if (heartbeatCallback)
|
||||
heartbeatCallback(status, latency);
|
||||
};
|
||||
}
|
||||
/** @internal */
|
||||
_startWorkerHeartbeat() {
|
||||
if (this.workerUrl) {
|
||||
this.log('worker', `starting worker for from ${this.workerUrl}`);
|
||||
}
|
||||
else {
|
||||
this.log('worker', `starting default worker`);
|
||||
}
|
||||
const objectUrl = this._workerObjectUrl(this.workerUrl);
|
||||
this.workerRef = new Worker(objectUrl);
|
||||
this.workerRef.onerror = (error) => {
|
||||
this.log('worker', 'worker error', error.message);
|
||||
this._terminateWorker();
|
||||
this.disconnect();
|
||||
};
|
||||
this.workerRef.onmessage = (event) => {
|
||||
if (event.data.event === 'keepAlive') {
|
||||
this.sendHeartbeat();
|
||||
}
|
||||
};
|
||||
this.workerRef.postMessage({
|
||||
event: 'start',
|
||||
interval: this.heartbeatIntervalMs,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Terminate the Web Worker and clear the reference
|
||||
* @internal
|
||||
*/
|
||||
_terminateWorker() {
|
||||
if (this.workerRef) {
|
||||
this.log('worker', 'terminating worker');
|
||||
this.workerRef.terminate();
|
||||
this.workerRef = undefined;
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
_workerObjectUrl(url) {
|
||||
let result_url;
|
||||
if (url) {
|
||||
result_url = url;
|
||||
}
|
||||
else {
|
||||
const blob = new Blob([WORKER_SCRIPT], { type: 'application/javascript' });
|
||||
result_url = URL.createObjectURL(blob);
|
||||
}
|
||||
return result_url;
|
||||
}
|
||||
/**
|
||||
* Initialize socket options with defaults
|
||||
* @internal
|
||||
*/
|
||||
_initializeOptions(options) {
|
||||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
||||
this.worker = (_a = options === null || options === void 0 ? void 0 : options.worker) !== null && _a !== void 0 ? _a : false;
|
||||
this.accessToken = (_b = options === null || options === void 0 ? void 0 : options.accessToken) !== null && _b !== void 0 ? _b : null;
|
||||
const result = {};
|
||||
result.timeout = (_c = options === null || options === void 0 ? void 0 : options.timeout) !== null && _c !== void 0 ? _c : constants_1.DEFAULT_TIMEOUT;
|
||||
result.heartbeatIntervalMs =
|
||||
(_d = options === null || options === void 0 ? void 0 : options.heartbeatIntervalMs) !== null && _d !== void 0 ? _d : CONNECTION_TIMEOUTS.HEARTBEAT_INTERVAL;
|
||||
this._disconnectOnEmptyChannelsAfterMs =
|
||||
(_e = options === null || options === void 0 ? void 0 : options.disconnectOnEmptyChannelsAfterMs) !== null && _e !== void 0 ? _e : 2 * ((_f = options === null || options === void 0 ? void 0 : options.heartbeatIntervalMs) !== null && _f !== void 0 ? _f : CONNECTION_TIMEOUTS.HEARTBEAT_INTERVAL);
|
||||
// @ts-ignore - mismatch between phoenix and supabase
|
||||
result.transport = (_g = options === null || options === void 0 ? void 0 : options.transport) !== null && _g !== void 0 ? _g : websocket_factory_1.default.getWebSocketConstructor();
|
||||
result.params = options === null || options === void 0 ? void 0 : options.params;
|
||||
result.logger = options === null || options === void 0 ? void 0 : options.logger;
|
||||
result.heartbeatCallback = this._wrapHeartbeatCallback(options === null || options === void 0 ? void 0 : options.heartbeatCallback);
|
||||
result.sessionStorage = (_h = options === null || options === void 0 ? void 0 : options.sessionStorage) !== null && _h !== void 0 ? _h : resolveSessionStorage();
|
||||
result.reconnectAfterMs =
|
||||
(_j = options === null || options === void 0 ? void 0 : options.reconnectAfterMs) !== null && _j !== void 0 ? _j : ((tries) => {
|
||||
return RECONNECT_INTERVALS[tries - 1] || DEFAULT_RECONNECT_FALLBACK;
|
||||
});
|
||||
let defaultEncode;
|
||||
let defaultDecode;
|
||||
const vsn = (_k = options === null || options === void 0 ? void 0 : options.vsn) !== null && _k !== void 0 ? _k : constants_1.DEFAULT_VSN;
|
||||
switch (vsn) {
|
||||
case constants_1.VSN_1_0_0:
|
||||
defaultEncode = (payload, callback) => {
|
||||
return callback(JSON.stringify(payload));
|
||||
};
|
||||
defaultDecode = (payload, callback) => {
|
||||
return callback(JSON.parse(payload));
|
||||
};
|
||||
break;
|
||||
case constants_1.VSN_2_0_0:
|
||||
defaultEncode = this.serializer.encode.bind(this.serializer);
|
||||
defaultDecode = this.serializer.decode.bind(this.serializer);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported serializer version: ${result.vsn}`);
|
||||
}
|
||||
result.vsn = vsn;
|
||||
result.encode = (_l = options === null || options === void 0 ? void 0 : options.encode) !== null && _l !== void 0 ? _l : defaultEncode;
|
||||
result.decode = (_m = options === null || options === void 0 ? void 0 : options.decode) !== null && _m !== void 0 ? _m : defaultDecode;
|
||||
result.beforeReconnect = this._reconnectAuth.bind(this);
|
||||
if ((options === null || options === void 0 ? void 0 : options.logLevel) || (options === null || options === void 0 ? void 0 : options.log_level)) {
|
||||
this.logLevel = options.logLevel || options.log_level;
|
||||
result.params = Object.assign(Object.assign({}, result.params), { log_level: this.logLevel });
|
||||
}
|
||||
// Handle worker setup
|
||||
if (this.worker) {
|
||||
if (typeof window !== 'undefined' && !window.Worker) {
|
||||
throw new Error('Web Worker is not supported');
|
||||
}
|
||||
this.workerUrl = options === null || options === void 0 ? void 0 : options.workerUrl;
|
||||
result.autoSendHeartbeat = !this.worker;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/** @internal */
|
||||
async _reconnectAuth() {
|
||||
await this._waitForAuthIfNeeded();
|
||||
if (!this.isConnected()) {
|
||||
this.connect();
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.default = RealtimeClient;
|
||||
//# sourceMappingURL=RealtimeClient.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+132
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Comparison operators accepted in a Postgres Changes `filter` string.
|
||||
*
|
||||
* These mirror the PostgREST operator surface and are evaluated server-side.
|
||||
* Any operator can be negated via {@link RealtimePostgresFilterBuilder.not}
|
||||
* (the `not.` prefix).
|
||||
*
|
||||
* This is the subset of PostgREST's `FilterOperator` (see `postgrest-js`) that
|
||||
* Realtime Postgres Changes supports. Containment, range and full-text search
|
||||
* operators (`cs`, `cd`, `ov`, range ops, `fts`, …) are intentionally not
|
||||
* included because the Realtime server does not evaluate them.
|
||||
*
|
||||
* - `eq`, `neq`, `lt`, `lte`, `gt`, `gte` — comparison
|
||||
* - `in` — membership: `status=in.(active,pending)`
|
||||
* - `like`, `ilike` — pattern match (case-sensitive / insensitive): `title=like.%foo%`
|
||||
* - `is` — `IS` check against `null` / `true` / `false` / `unknown`: `deleted_at=is.null`
|
||||
* - `match`, `imatch` — POSIX regex match (`~` / `~*`)
|
||||
* - `isdistinct` — NULL-safe inequality (`IS DISTINCT FROM`)
|
||||
*/
|
||||
export type RealtimePostgresChangesFilterOperator = 'eq' | 'neq' | 'lt' | 'lte' | 'gt' | 'gte' | 'in' | 'like' | 'ilike' | 'is' | 'match' | 'imatch' | 'isdistinct';
|
||||
/** Scalar value accepted by a single filter operator. */
|
||||
export type RealtimeFilterValue = string | number | boolean | null;
|
||||
/** Value accepted by the `is` operator. */
|
||||
export type RealtimeIsFilterValue = null | boolean | 'null' | 'true' | 'false' | 'unknown';
|
||||
/**
|
||||
* Fluent builder for Postgres Changes `filter` strings.
|
||||
*
|
||||
* Each method appends a single `column=operator.value` condition. Multiple
|
||||
* conditions are combined with commas, which the Realtime server applies as an
|
||||
* `AND`. Pass an instance straight to `channel.on('postgres_changes', …)` — the
|
||||
* SDK serializes it to a string automatically — or call {@link build} to obtain
|
||||
* the string yourself.
|
||||
*
|
||||
* The builder mirrors the `postgrest-js` filter API (`eq`, `neq`, `in`, `like`,
|
||||
* `not`, …) for the operators that Realtime supports. Values containing reserved
|
||||
* characters (`,`, `(`, `)`, `"`, `\`) — or surrounding whitespace — are
|
||||
* automatically double-quoted and escaped the same way PostgREST does, so they
|
||||
* survive the server's filter parser; all other values are sent verbatim.
|
||||
*
|
||||
* The filter is snapshotted when passed to `channel.on(...)`; mutating the
|
||||
* builder afterwards does not affect an existing subscription. An empty builder
|
||||
* serializes to `''`, which the server treats as "no filter".
|
||||
*
|
||||
* @example
|
||||
* channel.on('postgres_changes', {
|
||||
* event: '*',
|
||||
* schema: 'public',
|
||||
* table: 'users',
|
||||
* filter: postgresChangesFilter().eq('id', 1).lt('age', 30), // → 'id=eq.1,age=lt.30'
|
||||
* }, (payload) => { ... })
|
||||
*/
|
||||
export declare class RealtimePostgresFilterBuilder {
|
||||
private readonly filters;
|
||||
private add;
|
||||
/** Match rows where `column` equals `value` (`column=eq.value`). */
|
||||
eq(column: string, value: RealtimeFilterValue): this;
|
||||
/** Match rows where `column` does not equal `value` (`column=neq.value`). */
|
||||
neq(column: string, value: RealtimeFilterValue): this;
|
||||
/** Match rows where `column` is greater than `value` (`column=gt.value`). */
|
||||
gt(column: string, value: RealtimeFilterValue): this;
|
||||
/** Match rows where `column` is greater than or equal to `value` (`column=gte.value`). */
|
||||
gte(column: string, value: RealtimeFilterValue): this;
|
||||
/** Match rows where `column` is less than `value` (`column=lt.value`). */
|
||||
lt(column: string, value: RealtimeFilterValue): this;
|
||||
/** Match rows where `column` is less than or equal to `value` (`column=lte.value`). */
|
||||
lte(column: string, value: RealtimeFilterValue): this;
|
||||
/**
|
||||
* Match rows where `column` is one of `values` (`column=in.(a,b,c)`).
|
||||
* Requires at least one value; duplicates are removed. An element containing a
|
||||
* reserved character is double-quoted (`in.("a,b",c)`), so commas inside an
|
||||
* element are preserved. `null` is intentionally not accepted (`IN (null)`
|
||||
* never matches in SQL) — use `is`/`not('col','is',null)` for null checks.
|
||||
*/
|
||||
in(column: string, values: ReadonlyArray<string | number | boolean>): this;
|
||||
/** Match rows where `column` matches the case-sensitive `pattern` (`column=like.pattern`). */
|
||||
like(column: string, pattern: string): this;
|
||||
/** Match rows where `column` matches the case-insensitive `pattern` (`column=ilike.pattern`). */
|
||||
ilike(column: string, pattern: string): this;
|
||||
/** Match rows where `column` matches the POSIX regex `pattern` (`column=match.pattern`). */
|
||||
match(column: string, pattern: string): this;
|
||||
/** Match rows where `column` matches the case-insensitive POSIX regex `pattern` (`column=imatch.pattern`). */
|
||||
imatch(column: string, pattern: string): this;
|
||||
/**
|
||||
* Match rows where `column` `IS` the given value (`column=is.null`).
|
||||
* Accepts `null`, a boolean, or the keywords `'null' | 'true' | 'false' | 'unknown'`.
|
||||
*/
|
||||
is(column: string, value: RealtimeIsFilterValue): this;
|
||||
/** Match rows where `column` is distinct from `value` (`column=isdistinct.value`). NULL-safe inequality. */
|
||||
isDistinct(column: string, value: RealtimeFilterValue): this;
|
||||
/**
|
||||
* Negate any operator with the `not.` prefix (`column=not.operator.value`).
|
||||
* `in` takes an array, `is` takes an `IS` keyword/boolean/null, and every
|
||||
* other operator takes a scalar value.
|
||||
*
|
||||
* @example
|
||||
* postgresChangesFilter().not('status', 'in', ['draft', 'archived'])
|
||||
* // → status=not.in.(draft,archived)
|
||||
* postgresChangesFilter().not('deleted_at', 'is', null)
|
||||
* // → deleted_at=not.is.null
|
||||
*/
|
||||
not(column: string, operator: 'in', value: ReadonlyArray<string | number | boolean>): this;
|
||||
not(column: string, operator: 'is', value: RealtimeIsFilterValue): this;
|
||||
not(column: string, operator: Exclude<RealtimePostgresChangesFilterOperator, 'in' | 'is'>, value: RealtimeFilterValue): this;
|
||||
/**
|
||||
* Serialize all conditions into the comma-separated (AND) filter string.
|
||||
*
|
||||
* Conditions are joined by commas, which the server applies as `AND`. A scalar
|
||||
* value (or single `in` element) that contains a reserved character — `,`,
|
||||
* `(`, `)`, `"`, `\` — or surrounding whitespace is double-quoted and escaped
|
||||
* the way PostgREST does, so commas inside a value are preserved rather than
|
||||
* read as a condition boundary.
|
||||
*/
|
||||
build(): string;
|
||||
/** Alias for {@link build}; lets the builder be used wherever a string is expected. */
|
||||
toString(): string;
|
||||
}
|
||||
/**
|
||||
* Create a {@link RealtimePostgresFilterBuilder} for composing a Postgres
|
||||
* Changes `filter`. Conditions are combined with `AND`.
|
||||
*
|
||||
* @example
|
||||
* import { postgresChangesFilter } from '@supabase/realtime-js'
|
||||
*
|
||||
* channel.on('postgres_changes', {
|
||||
* event: 'UPDATE',
|
||||
* schema: 'public',
|
||||
* table: 'orders',
|
||||
* filter: postgresChangesFilter().gt('amount', 100).eq('status', 'open'),
|
||||
* }, (payload) => { ... })
|
||||
*/
|
||||
export declare const postgresChangesFilter: () => RealtimePostgresFilterBuilder;
|
||||
//# sourceMappingURL=RealtimePostgresFilterBuilder.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RealtimePostgresFilterBuilder.d.ts","sourceRoot":"","sources":["../../src/RealtimePostgresFilterBuilder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,qCAAqC,GAC7C,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,MAAM,GACN,OAAO,GACP,IAAI,GACJ,OAAO,GACP,QAAQ,GACR,YAAY,CAAA;AAEhB,yDAAyD;AACzD,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAA;AAElE,2CAA2C;AAC3C,MAAM,MAAM,qBAAqB,GAAG,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;AAuC1F;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,6BAA6B;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAEvC,OAAO,CAAC,GAAG;IAWX,oEAAoE;IACpE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAIpD,6EAA6E;IAC7E,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAIrD,6EAA6E;IAC7E,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAIpD,0FAA0F;IAC1F,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAIrD,0EAA0E;IAC1E,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAIpD,uFAAuF;IACvF,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAIrD;;;;;;OAMG;IACH,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI;IAI1E,8FAA8F;IAC9F,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3C,iGAAiG;IACjG,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAI5C,4FAA4F;IAC5F,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAI5C,8GAA8G;IAC9G,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAI7C;;;OAGG;IACH,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,qBAAqB,GAAG,IAAI;IAItD,4GAA4G;IAC5G,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAI5D;;;;;;;;;;OAUG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI;IAC1F,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,qBAAqB,GAAG,IAAI;IACvE,GAAG,CACD,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,OAAO,CAAC,qCAAqC,EAAE,IAAI,GAAG,IAAI,CAAC,EACrE,KAAK,EAAE,mBAAmB,GACzB,IAAI;IASP;;;;;;;;OAQG;IACH,KAAK,IAAI,MAAM;IAIf,uFAAuF;IACvF,QAAQ,IAAI,MAAM;CAGnB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qBAAqB,QAAO,6BACJ,CAAA"}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.postgresChangesFilter = exports.RealtimePostgresFilterBuilder = void 0;
|
||||
// Reserved characters that force PostgREST-style quoting: `[,()]` (which the
|
||||
// server reads as condition/list delimiters) plus `"`/`\` (escaped inside quotes).
|
||||
const PostgrestReservedCharsRegexp = /[,()"\\]/;
|
||||
const needsQuoting = (value) => PostgrestReservedCharsRegexp.test(value) || value !== value.trim();
|
||||
const quote = (value) => `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
||||
const serializeScalar = (value) => {
|
||||
const serialized = value === null ? 'null' : String(value);
|
||||
return needsQuoting(serialized) ? quote(serialized) : serialized;
|
||||
};
|
||||
const serializeIsValue = (value) => value === null ? 'null' : String(value);
|
||||
// Builds the `operator.value` portion of a filter (everything after `column=`).
|
||||
const serialize = (operator, value) => {
|
||||
if (operator === 'in') {
|
||||
const values = Array.isArray(value) ? value : [value];
|
||||
if (values.length === 0) {
|
||||
throw new Error('Realtime `in` filter requires at least one value.');
|
||||
}
|
||||
const items = Array.from(new Set(values))
|
||||
.map((v) => serializeScalar(v))
|
||||
.join(',');
|
||||
return `in.(${items})`;
|
||||
}
|
||||
if (operator === 'is') {
|
||||
return `is.${serializeIsValue(value)}`;
|
||||
}
|
||||
return `${operator}.${serializeScalar(value)}`;
|
||||
};
|
||||
/**
|
||||
* Fluent builder for Postgres Changes `filter` strings.
|
||||
*
|
||||
* Each method appends a single `column=operator.value` condition. Multiple
|
||||
* conditions are combined with commas, which the Realtime server applies as an
|
||||
* `AND`. Pass an instance straight to `channel.on('postgres_changes', …)` — the
|
||||
* SDK serializes it to a string automatically — or call {@link build} to obtain
|
||||
* the string yourself.
|
||||
*
|
||||
* The builder mirrors the `postgrest-js` filter API (`eq`, `neq`, `in`, `like`,
|
||||
* `not`, …) for the operators that Realtime supports. Values containing reserved
|
||||
* characters (`,`, `(`, `)`, `"`, `\`) — or surrounding whitespace — are
|
||||
* automatically double-quoted and escaped the same way PostgREST does, so they
|
||||
* survive the server's filter parser; all other values are sent verbatim.
|
||||
*
|
||||
* The filter is snapshotted when passed to `channel.on(...)`; mutating the
|
||||
* builder afterwards does not affect an existing subscription. An empty builder
|
||||
* serializes to `''`, which the server treats as "no filter".
|
||||
*
|
||||
* @example
|
||||
* channel.on('postgres_changes', {
|
||||
* event: '*',
|
||||
* schema: 'public',
|
||||
* table: 'users',
|
||||
* filter: postgresChangesFilter().eq('id', 1).lt('age', 30), // → 'id=eq.1,age=lt.30'
|
||||
* }, (payload) => { ... })
|
||||
*/
|
||||
class RealtimePostgresFilterBuilder {
|
||||
constructor() {
|
||||
this.filters = [];
|
||||
}
|
||||
add(column, operator, value, negate = false) {
|
||||
const prefix = negate ? 'not.' : '';
|
||||
this.filters.push(`${column}=${prefix}${serialize(operator, value)}`);
|
||||
return this;
|
||||
}
|
||||
/** Match rows where `column` equals `value` (`column=eq.value`). */
|
||||
eq(column, value) {
|
||||
return this.add(column, 'eq', value);
|
||||
}
|
||||
/** Match rows where `column` does not equal `value` (`column=neq.value`). */
|
||||
neq(column, value) {
|
||||
return this.add(column, 'neq', value);
|
||||
}
|
||||
/** Match rows where `column` is greater than `value` (`column=gt.value`). */
|
||||
gt(column, value) {
|
||||
return this.add(column, 'gt', value);
|
||||
}
|
||||
/** Match rows where `column` is greater than or equal to `value` (`column=gte.value`). */
|
||||
gte(column, value) {
|
||||
return this.add(column, 'gte', value);
|
||||
}
|
||||
/** Match rows where `column` is less than `value` (`column=lt.value`). */
|
||||
lt(column, value) {
|
||||
return this.add(column, 'lt', value);
|
||||
}
|
||||
/** Match rows where `column` is less than or equal to `value` (`column=lte.value`). */
|
||||
lte(column, value) {
|
||||
return this.add(column, 'lte', value);
|
||||
}
|
||||
/**
|
||||
* Match rows where `column` is one of `values` (`column=in.(a,b,c)`).
|
||||
* Requires at least one value; duplicates are removed. An element containing a
|
||||
* reserved character is double-quoted (`in.("a,b",c)`), so commas inside an
|
||||
* element are preserved. `null` is intentionally not accepted (`IN (null)`
|
||||
* never matches in SQL) — use `is`/`not('col','is',null)` for null checks.
|
||||
*/
|
||||
in(column, values) {
|
||||
return this.add(column, 'in', values);
|
||||
}
|
||||
/** Match rows where `column` matches the case-sensitive `pattern` (`column=like.pattern`). */
|
||||
like(column, pattern) {
|
||||
return this.add(column, 'like', pattern);
|
||||
}
|
||||
/** Match rows where `column` matches the case-insensitive `pattern` (`column=ilike.pattern`). */
|
||||
ilike(column, pattern) {
|
||||
return this.add(column, 'ilike', pattern);
|
||||
}
|
||||
/** Match rows where `column` matches the POSIX regex `pattern` (`column=match.pattern`). */
|
||||
match(column, pattern) {
|
||||
return this.add(column, 'match', pattern);
|
||||
}
|
||||
/** Match rows where `column` matches the case-insensitive POSIX regex `pattern` (`column=imatch.pattern`). */
|
||||
imatch(column, pattern) {
|
||||
return this.add(column, 'imatch', pattern);
|
||||
}
|
||||
/**
|
||||
* Match rows where `column` `IS` the given value (`column=is.null`).
|
||||
* Accepts `null`, a boolean, or the keywords `'null' | 'true' | 'false' | 'unknown'`.
|
||||
*/
|
||||
is(column, value) {
|
||||
return this.add(column, 'is', value);
|
||||
}
|
||||
/** Match rows where `column` is distinct from `value` (`column=isdistinct.value`). NULL-safe inequality. */
|
||||
isDistinct(column, value) {
|
||||
return this.add(column, 'isdistinct', value);
|
||||
}
|
||||
not(column, operator, value) {
|
||||
return this.add(column, operator, value, true);
|
||||
}
|
||||
/**
|
||||
* Serialize all conditions into the comma-separated (AND) filter string.
|
||||
*
|
||||
* Conditions are joined by commas, which the server applies as `AND`. A scalar
|
||||
* value (or single `in` element) that contains a reserved character — `,`,
|
||||
* `(`, `)`, `"`, `\` — or surrounding whitespace is double-quoted and escaped
|
||||
* the way PostgREST does, so commas inside a value are preserved rather than
|
||||
* read as a condition boundary.
|
||||
*/
|
||||
build() {
|
||||
return this.filters.join(',');
|
||||
}
|
||||
/** Alias for {@link build}; lets the builder be used wherever a string is expected. */
|
||||
toString() {
|
||||
return this.build();
|
||||
}
|
||||
}
|
||||
exports.RealtimePostgresFilterBuilder = RealtimePostgresFilterBuilder;
|
||||
/**
|
||||
* Create a {@link RealtimePostgresFilterBuilder} for composing a Postgres
|
||||
* Changes `filter`. Conditions are combined with `AND`.
|
||||
*
|
||||
* @example
|
||||
* import { postgresChangesFilter } from '@supabase/realtime-js'
|
||||
*
|
||||
* channel.on('postgres_changes', {
|
||||
* event: 'UPDATE',
|
||||
* schema: 'public',
|
||||
* table: 'orders',
|
||||
* filter: postgresChangesFilter().gt('amount', 100).eq('status', 'open'),
|
||||
* }, (payload) => { ... })
|
||||
*/
|
||||
const postgresChangesFilter = () => new RealtimePostgresFilterBuilder();
|
||||
exports.postgresChangesFilter = postgresChangesFilter;
|
||||
//# sourceMappingURL=RealtimePostgresFilterBuilder.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RealtimePostgresFilterBuilder.js","sourceRoot":"","sources":["../../src/RealtimePostgresFilterBuilder.ts"],"names":[],"mappings":"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"}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import type RealtimeChannel from './RealtimeChannel';
|
||||
export type Presence<T extends {
|
||||
[key: string]: any;
|
||||
} = {}> = {
|
||||
presence_ref: string;
|
||||
} & T;
|
||||
export type RealtimePresenceState<T extends {
|
||||
[key: string]: any;
|
||||
} = {}> = {
|
||||
[key: string]: Presence<T>[];
|
||||
};
|
||||
export type RealtimePresenceJoinPayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = {
|
||||
event: `${REALTIME_PRESENCE_LISTEN_EVENTS.JOIN}`;
|
||||
key: string;
|
||||
currentPresences: Presence<T>[];
|
||||
newPresences: Presence<T>[];
|
||||
};
|
||||
export type RealtimePresenceLeavePayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = {
|
||||
event: `${REALTIME_PRESENCE_LISTEN_EVENTS.LEAVE}`;
|
||||
key: string;
|
||||
currentPresences: Presence<T>[];
|
||||
leftPresences: Presence<T>[];
|
||||
};
|
||||
export declare enum REALTIME_PRESENCE_LISTEN_EVENTS {
|
||||
SYNC = "sync",
|
||||
JOIN = "join",
|
||||
LEAVE = "leave"
|
||||
}
|
||||
export type RealtimePresenceOptions = {
|
||||
events?: {
|
||||
state: string;
|
||||
diff: string;
|
||||
};
|
||||
};
|
||||
export default class RealtimePresence {
|
||||
channel: RealtimeChannel;
|
||||
get state(): RealtimePresenceState;
|
||||
private presenceAdapter;
|
||||
/**
|
||||
* Creates a Presence helper that keeps the local presence state in sync with the server.
|
||||
*
|
||||
* @param channel - The realtime channel to bind to.
|
||||
* @param opts - Optional custom event names, e.g. `{ events: { state: 'state', diff: 'diff' } }`.
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Example for a presence channel
|
||||
* ```ts
|
||||
* const presence = new RealtimePresence(channel)
|
||||
*
|
||||
* channel.on('presence', ({ event, key }) => {
|
||||
* console.log(`Presence ${event} on ${key}`)
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
constructor(channel: RealtimeChannel, opts?: RealtimePresenceOptions);
|
||||
}
|
||||
//# sourceMappingURL=RealtimePresence.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RealtimePresence.d.ts","sourceRoot":"","sources":["../../src/RealtimePresence.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,eAAe,MAAM,mBAAmB,CAAA;AAGpD,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,GAAG,EAAE,IAAI;IAC5D,YAAY,EAAE,MAAM,CAAA;CACrB,GAAG,CAAC,CAAA;AAEL,MAAM,MAAM,qBAAqB,CAAC,CAAC,SAAS;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,GAAG,EAAE,IAAI;IACzE,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,2BAA2B,CAAC,CAAC,SAAS;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,IAAI;IAC1E,KAAK,EAAE,GAAG,+BAA+B,CAAC,IAAI,EAAE,CAAA;IAChD,GAAG,EAAE,MAAM,CAAA;IACX,gBAAgB,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;IAC/B,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;CAC5B,CAAA;AAED,MAAM,MAAM,4BAA4B,CAAC,CAAC,SAAS;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,IAAI;IAC3E,KAAK,EAAE,GAAG,+BAA+B,CAAC,KAAK,EAAE,CAAA;IACjD,GAAG,EAAE,MAAM,CAAA;IACX,gBAAgB,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;IAC/B,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;CAC7B,CAAA;AAED,oBAAY,+BAA+B;IACzC,IAAI,SAAS;IACb,IAAI,SAAS;IACb,KAAK,UAAU;CAChB;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CACzC,CAAA;AAED,MAAM,CAAC,OAAO,OAAO,gBAAgB;IAyB1B,OAAO,EAAE,eAAe;IAxBjC,IAAI,KAAK,0BAER;IAED,OAAO,CAAC,eAAe,CAAiB;IAExC;;;;;;;;;;;;;;;;OAgBG;gBAEM,OAAO,EAAE,eAAe,EAC/B,IAAI,CAAC,EAAE,uBAAuB;CAIjC"}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
"use strict";
|
||||
/*
|
||||
This file draws heavily from https://github.com/phoenixframework/phoenix/blob/d344ec0a732ab4ee204215b31de69cf4be72e3bf/assets/js/phoenix/presence.js
|
||||
License: https://github.com/phoenixframework/phoenix/blob/d344ec0a732ab4ee204215b31de69cf4be72e3bf/LICENSE.md
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.REALTIME_PRESENCE_LISTEN_EVENTS = void 0;
|
||||
const tslib_1 = require("tslib");
|
||||
const presenceAdapter_1 = tslib_1.__importDefault(require("./phoenix/presenceAdapter"));
|
||||
var REALTIME_PRESENCE_LISTEN_EVENTS;
|
||||
(function (REALTIME_PRESENCE_LISTEN_EVENTS) {
|
||||
REALTIME_PRESENCE_LISTEN_EVENTS["SYNC"] = "sync";
|
||||
REALTIME_PRESENCE_LISTEN_EVENTS["JOIN"] = "join";
|
||||
REALTIME_PRESENCE_LISTEN_EVENTS["LEAVE"] = "leave";
|
||||
})(REALTIME_PRESENCE_LISTEN_EVENTS || (exports.REALTIME_PRESENCE_LISTEN_EVENTS = REALTIME_PRESENCE_LISTEN_EVENTS = {}));
|
||||
class RealtimePresence {
|
||||
get state() {
|
||||
return this.presenceAdapter.state;
|
||||
}
|
||||
/**
|
||||
* Creates a Presence helper that keeps the local presence state in sync with the server.
|
||||
*
|
||||
* @param channel - The realtime channel to bind to.
|
||||
* @param opts - Optional custom event names, e.g. `{ events: { state: 'state', diff: 'diff' } }`.
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Example for a presence channel
|
||||
* ```ts
|
||||
* const presence = new RealtimePresence(channel)
|
||||
*
|
||||
* channel.on('presence', ({ event, key }) => {
|
||||
* console.log(`Presence ${event} on ${key}`)
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
constructor(channel, opts) {
|
||||
this.channel = channel;
|
||||
this.presenceAdapter = new presenceAdapter_1.default(this.channel.channelAdapter, opts);
|
||||
}
|
||||
}
|
||||
exports.default = RealtimePresence;
|
||||
//# sourceMappingURL=RealtimePresence.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RealtimePresence.js","sourceRoot":"","sources":["../../src/RealtimePresence.ts"],"names":[],"mappings":";AAAA;;;EAGE;;;;AAGF,wFAAuD;AAwBvD,IAAY,+BAIX;AAJD,WAAY,+BAA+B;IACzC,gDAAa,CAAA;IACb,gDAAa,CAAA;IACb,kDAAe,CAAA;AACjB,CAAC,EAJW,+BAA+B,+CAA/B,+BAA+B,QAI1C;AAMD,MAAqB,gBAAgB;IACnC,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,eAAe,CAAC,KAAK,CAAA;IACnC,CAAC;IAID;;;;;;;;;;;;;;;;OAgBG;IACH,YACS,OAAwB,EAC/B,IAA8B;QADvB,YAAO,GAAP,OAAO,CAAiB;QAG/B,IAAI,CAAC,eAAe,GAAG,IAAI,yBAAe,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC,CAAA;IAC/E,CAAC;CACF;AA9BD,mCA8BC"}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import RealtimeClient, { RealtimeClientOptions, RealtimeMessage, RealtimeRemoveChannelResponse, WebSocketLikeConstructor } from './RealtimeClient';
|
||||
import RealtimeChannel, { RealtimeChannelOptions, RealtimeChannelSendResponse, RealtimePostgresChangesFilter, RealtimePostgresChangesFilterOperator, RealtimePostgresFilterBuilder, postgresChangesFilter, RealtimePostgresChangesPayload, RealtimePostgresInsertPayload, RealtimePostgresUpdatePayload, RealtimePostgresDeletePayload, RealtimeSystemPayload, REALTIME_LISTEN_TYPES, REALTIME_POSTGRES_CHANGES_LISTEN_EVENT, REALTIME_SUBSCRIBE_STATES, REALTIME_CHANNEL_STATES } from './RealtimeChannel';
|
||||
import RealtimePresence, { RealtimePresenceState, RealtimePresenceJoinPayload, RealtimePresenceLeavePayload, REALTIME_PRESENCE_LISTEN_EVENTS } from './RealtimePresence';
|
||||
import WebSocketFactory, { WebSocketLike } from './lib/websocket-factory';
|
||||
export { RealtimePresence, RealtimeChannel, RealtimeChannelOptions, RealtimeChannelSendResponse, RealtimeClient, RealtimeClientOptions, RealtimeMessage, RealtimePostgresChangesFilter, RealtimePostgresChangesFilterOperator, RealtimePostgresFilterBuilder, postgresChangesFilter, RealtimePostgresChangesPayload, RealtimePostgresInsertPayload, RealtimePostgresUpdatePayload, RealtimePostgresDeletePayload, RealtimeSystemPayload, RealtimePresenceJoinPayload, RealtimePresenceLeavePayload, RealtimePresenceState, RealtimeRemoveChannelResponse, REALTIME_LISTEN_TYPES, REALTIME_POSTGRES_CHANGES_LISTEN_EVENT, REALTIME_PRESENCE_LISTEN_EVENTS, REALTIME_SUBSCRIBE_STATES, REALTIME_CHANNEL_STATES, WebSocketFactory, WebSocketLike, WebSocketLikeConstructor, };
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,EAAE,EACrB,qBAAqB,EACrB,eAAe,EACf,6BAA6B,EAC7B,wBAAwB,EACzB,MAAM,kBAAkB,CAAA;AACzB,OAAO,eAAe,EAAE,EACtB,sBAAsB,EACtB,2BAA2B,EAC3B,6BAA6B,EAC7B,qCAAqC,EACrC,6BAA6B,EAC7B,qBAAqB,EACrB,8BAA8B,EAC9B,6BAA6B,EAC7B,6BAA6B,EAC7B,6BAA6B,EAC7B,qBAAqB,EACrB,qBAAqB,EACrB,sCAAsC,EACtC,yBAAyB,EACzB,uBAAuB,EACxB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,gBAAgB,EAAE,EACvB,qBAAqB,EACrB,2BAA2B,EAC3B,4BAA4B,EAC5B,+BAA+B,EAChC,MAAM,oBAAoB,CAAA;AAC3B,OAAO,gBAAgB,EAAE,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAEzE,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,2BAA2B,EAC3B,cAAc,EACd,qBAAqB,EACrB,eAAe,EACf,6BAA6B,EAC7B,qCAAqC,EACrC,6BAA6B,EAC7B,qBAAqB,EACrB,8BAA8B,EAC9B,6BAA6B,EAC7B,6BAA6B,EAC7B,6BAA6B,EAC7B,qBAAqB,EACrB,2BAA2B,EAC3B,4BAA4B,EAC5B,qBAAqB,EACrB,6BAA6B,EAC7B,qBAAqB,EACrB,sCAAsC,EACtC,+BAA+B,EAC/B,yBAAyB,EACzB,uBAAuB,EACvB,gBAAgB,EAChB,aAAa,EACb,wBAAwB,GACzB,CAAA"}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.WebSocketFactory = exports.REALTIME_CHANNEL_STATES = exports.REALTIME_SUBSCRIBE_STATES = exports.REALTIME_PRESENCE_LISTEN_EVENTS = exports.REALTIME_POSTGRES_CHANGES_LISTEN_EVENT = exports.REALTIME_LISTEN_TYPES = exports.postgresChangesFilter = exports.RealtimePostgresFilterBuilder = exports.RealtimeClient = exports.RealtimeChannel = exports.RealtimePresence = void 0;
|
||||
const tslib_1 = require("tslib");
|
||||
const RealtimeClient_1 = tslib_1.__importDefault(require("./RealtimeClient"));
|
||||
exports.RealtimeClient = RealtimeClient_1.default;
|
||||
const RealtimeChannel_1 = tslib_1.__importStar(require("./RealtimeChannel"));
|
||||
exports.RealtimeChannel = RealtimeChannel_1.default;
|
||||
Object.defineProperty(exports, "RealtimePostgresFilterBuilder", { enumerable: true, get: function () { return RealtimeChannel_1.RealtimePostgresFilterBuilder; } });
|
||||
Object.defineProperty(exports, "postgresChangesFilter", { enumerable: true, get: function () { return RealtimeChannel_1.postgresChangesFilter; } });
|
||||
Object.defineProperty(exports, "REALTIME_LISTEN_TYPES", { enumerable: true, get: function () { return RealtimeChannel_1.REALTIME_LISTEN_TYPES; } });
|
||||
Object.defineProperty(exports, "REALTIME_POSTGRES_CHANGES_LISTEN_EVENT", { enumerable: true, get: function () { return RealtimeChannel_1.REALTIME_POSTGRES_CHANGES_LISTEN_EVENT; } });
|
||||
Object.defineProperty(exports, "REALTIME_SUBSCRIBE_STATES", { enumerable: true, get: function () { return RealtimeChannel_1.REALTIME_SUBSCRIBE_STATES; } });
|
||||
Object.defineProperty(exports, "REALTIME_CHANNEL_STATES", { enumerable: true, get: function () { return RealtimeChannel_1.REALTIME_CHANNEL_STATES; } });
|
||||
const RealtimePresence_1 = tslib_1.__importStar(require("./RealtimePresence"));
|
||||
exports.RealtimePresence = RealtimePresence_1.default;
|
||||
Object.defineProperty(exports, "REALTIME_PRESENCE_LISTEN_EVENTS", { enumerable: true, get: function () { return RealtimePresence_1.REALTIME_PRESENCE_LISTEN_EVENTS; } });
|
||||
const websocket_factory_1 = tslib_1.__importDefault(require("./lib/websocket-factory"));
|
||||
exports.WebSocketFactory = websocket_factory_1.default;
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;AAAA,8EAKyB;AA+BvB,yBApCK,wBAAc,CAoCL;AA9BhB,6EAgB0B;AAWxB,0BA3BK,yBAAe,CA2BL;AAQf,8GA9BA,+CAA6B,OA8BA;AAC7B,sGA9BA,uCAAqB,OA8BA;AAUrB,sGAlCA,uCAAqB,OAkCA;AACrB,uHAlCA,wDAAsC,OAkCA;AAEtC,0GAnCA,2CAAyB,OAmCA;AACzB,wGAnCA,yCAAuB,OAmCA;AAjCzB,+EAK2B;AAIzB,2BATK,0BAAgB,CASL;AAsBhB,gHA3BA,kDAA+B,OA2BA;AAzBjC,wFAAyE;AA4BvE,2BA5BK,2BAAgB,CA4BL"}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import type { SocketState, ChannelState, ChannelEvent as PhoenixChannelEvent, Transport, Vsn } from '../phoenix/types';
|
||||
export type { SocketState, ChannelState, Transport };
|
||||
export declare const DEFAULT_VERSION = "realtime-js/2.110.8";
|
||||
export declare const VSN_1_0_0: Vsn;
|
||||
export declare const VSN_2_0_0: Vsn;
|
||||
export declare const DEFAULT_VSN: Vsn;
|
||||
export declare const VERSION = "2.110.8";
|
||||
export declare const DEFAULT_TIMEOUT = 10000;
|
||||
export declare const WS_CLOSE_NORMAL = 1000;
|
||||
export declare const MAX_PUSH_BUFFER_SIZE = 100;
|
||||
export declare const SOCKET_STATES: {
|
||||
readonly connecting: 0;
|
||||
readonly open: 1;
|
||||
readonly closing: 2;
|
||||
readonly closed: 3;
|
||||
};
|
||||
export declare const CHANNEL_STATES: {
|
||||
readonly closed: "closed";
|
||||
readonly errored: "errored";
|
||||
readonly joined: "joined";
|
||||
readonly joining: "joining";
|
||||
readonly leaving: "leaving";
|
||||
};
|
||||
export type ChannelEvent = PhoenixChannelEvent | 'access_token';
|
||||
export declare const CHANNEL_EVENTS: {
|
||||
readonly close: "phx_close";
|
||||
readonly error: "phx_error";
|
||||
readonly join: "phx_join";
|
||||
readonly reply: "phx_reply";
|
||||
readonly leave: "phx_leave";
|
||||
readonly access_token: "access_token";
|
||||
};
|
||||
export declare const TRANSPORTS: {
|
||||
readonly websocket: "websocket";
|
||||
};
|
||||
export type ConnectionState = 'connecting' | 'open' | 'closing' | 'closed' | (string & {});
|
||||
export declare const CONNECTION_STATE: {
|
||||
readonly connecting: "connecting";
|
||||
readonly open: "open";
|
||||
readonly closing: "closing";
|
||||
readonly closed: "closed";
|
||||
};
|
||||
//# sourceMappingURL=constants.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/lib/constants.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,YAAY,IAAI,mBAAmB,EACnC,SAAS,EACT,GAAG,EACJ,MAAM,kBAAkB,CAAA;AAEzB,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,CAAA;AAEpD,eAAO,MAAM,eAAe,wBAA2B,CAAA;AAEvD,eAAO,MAAM,SAAS,EAAE,GAAa,CAAA;AACrC,eAAO,MAAM,SAAS,EAAE,GAAa,CAAA;AACrC,eAAO,MAAM,WAAW,EAAE,GAAe,CAAA;AAEzC,eAAO,MAAM,OAAO,YAAU,CAAA;AAE9B,eAAO,MAAM,eAAe,QAAQ,CAAA;AAEpC,eAAO,MAAM,eAAe,OAAO,CAAA;AACnC,eAAO,MAAM,oBAAoB,MAAM,CAAA;AAEvC,eAAO,MAAM,aAAa;;;;;CAKhB,CAAA;AAEV,eAAO,MAAM,cAAc;;;;;;CAMjB,CAAA;AAEV,MAAM,MAAM,YAAY,GAAG,mBAAmB,GAAG,cAAc,CAAA;AAE/D,eAAO,MAAM,cAAc;;;;;;;CAOjB,CAAA;AAEV,eAAO,MAAM,UAAU;;CAEb,CAAA;AAEV,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAE1F,eAAO,MAAM,gBAAgB;;;;;CAKnB,CAAA"}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.CONNECTION_STATE = exports.TRANSPORTS = exports.CHANNEL_EVENTS = exports.CHANNEL_STATES = exports.SOCKET_STATES = exports.MAX_PUSH_BUFFER_SIZE = exports.WS_CLOSE_NORMAL = exports.DEFAULT_TIMEOUT = exports.VERSION = exports.DEFAULT_VSN = exports.VSN_2_0_0 = exports.VSN_1_0_0 = exports.DEFAULT_VERSION = void 0;
|
||||
const version_1 = require("./version");
|
||||
exports.DEFAULT_VERSION = `realtime-js/${version_1.version}`;
|
||||
exports.VSN_1_0_0 = '1.0.0';
|
||||
exports.VSN_2_0_0 = '2.0.0';
|
||||
exports.DEFAULT_VSN = exports.VSN_2_0_0;
|
||||
exports.VERSION = version_1.version;
|
||||
exports.DEFAULT_TIMEOUT = 10000;
|
||||
exports.WS_CLOSE_NORMAL = 1000;
|
||||
exports.MAX_PUSH_BUFFER_SIZE = 100;
|
||||
exports.SOCKET_STATES = {
|
||||
connecting: 0,
|
||||
open: 1,
|
||||
closing: 2,
|
||||
closed: 3,
|
||||
};
|
||||
exports.CHANNEL_STATES = {
|
||||
closed: 'closed',
|
||||
errored: 'errored',
|
||||
joined: 'joined',
|
||||
joining: 'joining',
|
||||
leaving: 'leaving',
|
||||
};
|
||||
exports.CHANNEL_EVENTS = {
|
||||
close: 'phx_close',
|
||||
error: 'phx_error',
|
||||
join: 'phx_join',
|
||||
reply: 'phx_reply',
|
||||
leave: 'phx_leave',
|
||||
access_token: 'access_token',
|
||||
};
|
||||
exports.TRANSPORTS = {
|
||||
websocket: 'websocket',
|
||||
};
|
||||
exports.CONNECTION_STATE = {
|
||||
connecting: 'connecting',
|
||||
open: 'open',
|
||||
closing: 'closing',
|
||||
closed: 'closed',
|
||||
};
|
||||
//# sourceMappingURL=constants.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/lib/constants.ts"],"names":[],"mappings":";;;AAAA,uCAAmC;AAWtB,QAAA,eAAe,GAAG,eAAe,iBAAO,EAAE,CAAA;AAE1C,QAAA,SAAS,GAAQ,OAAO,CAAA;AACxB,QAAA,SAAS,GAAQ,OAAO,CAAA;AACxB,QAAA,WAAW,GAAQ,iBAAS,CAAA;AAE5B,QAAA,OAAO,GAAG,iBAAO,CAAA;AAEjB,QAAA,eAAe,GAAG,KAAK,CAAA;AAEvB,QAAA,eAAe,GAAG,IAAI,CAAA;AACtB,QAAA,oBAAoB,GAAG,GAAG,CAAA;AAE1B,QAAA,aAAa,GAAG;IAC3B,UAAU,EAAE,CAAC;IACb,IAAI,EAAE,CAAC;IACP,OAAO,EAAE,CAAC;IACV,MAAM,EAAE,CAAC;CACD,CAAA;AAEG,QAAA,cAAc,GAAG;IAC5B,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;CACV,CAAA;AAIG,QAAA,cAAc,GAAG;IAC5B,KAAK,EAAE,WAAW;IAClB,KAAK,EAAE,WAAW;IAClB,IAAI,EAAE,UAAU;IAChB,KAAK,EAAE,WAAW;IAClB,KAAK,EAAE,WAAW;IAClB,YAAY,EAAE,cAAc;CACpB,CAAA;AAEG,QAAA,UAAU,GAAG;IACxB,SAAS,EAAE,WAAW;CACd,CAAA;AAIG,QAAA,gBAAgB,GAAG;IAC9B,UAAU,EAAE,YAAY;IACxB,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;CACR,CAAA"}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Normalize the various shapes a channel error reason can take into a real `Error`.
|
||||
*
|
||||
* Transport-level channel errors arrive as a `CloseEvent`, a transport `Event`, an `Error`,
|
||||
* a string, or `undefined` depending on which path in the underlying socket fired. Server-reply
|
||||
* errors arrive as a payload object. This helper produces a consistent `Error` for every case
|
||||
* and preserves the original via `cause` so callers can still inspect the raw event.
|
||||
*/
|
||||
export declare function normalizeChannelError(reason: unknown): Error;
|
||||
//# sourceMappingURL=normalizeChannelError.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"normalizeChannelError.d.ts","sourceRoot":"","sources":["../../../src/lib/normalizeChannelError.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,KAAK,CAqB5D"}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.normalizeChannelError = normalizeChannelError;
|
||||
/**
|
||||
* Normalize the various shapes a channel error reason can take into a real `Error`.
|
||||
*
|
||||
* Transport-level channel errors arrive as a `CloseEvent`, a transport `Event`, an `Error`,
|
||||
* a string, or `undefined` depending on which path in the underlying socket fired. Server-reply
|
||||
* errors arrive as a payload object. This helper produces a consistent `Error` for every case
|
||||
* and preserves the original via `cause` so callers can still inspect the raw event.
|
||||
*/
|
||||
function normalizeChannelError(reason) {
|
||||
if (reason instanceof Error) {
|
||||
return reason;
|
||||
}
|
||||
if (typeof reason === 'string') {
|
||||
return new Error(reason);
|
||||
}
|
||||
if (reason && typeof reason === 'object') {
|
||||
const obj = reason;
|
||||
if (typeof obj.code === 'number') {
|
||||
const detail = typeof obj.reason === 'string' && obj.reason ? ` (${obj.reason})` : '';
|
||||
return new Error(`socket closed: ${obj.code}${detail}`, { cause: reason });
|
||||
}
|
||||
return new Error('channel error: transport failure', { cause: reason });
|
||||
}
|
||||
return new Error('channel error: connection lost');
|
||||
}
|
||||
//# sourceMappingURL=normalizeChannelError.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"normalizeChannelError.js","sourceRoot":"","sources":["../../../src/lib/normalizeChannelError.ts"],"names":[],"mappings":";;AAQA,sDAqBC;AA7BD;;;;;;;GAOG;AACH,SAAgB,qBAAqB,CAAC,MAAe;IACnD,IAAI,MAAM,YAAY,KAAK,EAAE,CAAC;QAC5B,OAAO,MAAM,CAAA;IACf,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC,CAAA;IAC1B,CAAC;IAED,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QACzC,MAAM,GAAG,GAAG,MAAiC,CAAA;QAE7C,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;YACrF,OAAO,IAAI,KAAK,CAAC,kBAAkB,GAAG,CAAC,IAAI,GAAG,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QAC5E,CAAC;QAED,OAAO,IAAI,KAAK,CAAC,kCAAkC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;IACzE,CAAC;IAED,OAAO,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAA;AACpD,CAAC"}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
export type Msg<T> = {
|
||||
join_ref?: string | null;
|
||||
ref?: string | null;
|
||||
topic: string;
|
||||
event: string;
|
||||
payload: T;
|
||||
};
|
||||
export default class Serializer {
|
||||
HEADER_LENGTH: number;
|
||||
USER_BROADCAST_PUSH_META_LENGTH: number;
|
||||
KINDS: {
|
||||
userBroadcastPush: number;
|
||||
userBroadcast: number;
|
||||
};
|
||||
BINARY_ENCODING: number;
|
||||
JSON_ENCODING: number;
|
||||
BROADCAST_EVENT: string;
|
||||
allowedMetadataKeys: string[];
|
||||
constructor(allowedMetadataKeys?: string[] | null);
|
||||
encode(msg: Msg<{
|
||||
[key: string]: any;
|
||||
}>, callback: (result: ArrayBuffer | string) => any): any;
|
||||
private _binaryEncodeUserBroadcastPush;
|
||||
private _encodeBinaryUserBroadcastPush;
|
||||
private _encodeJsonUserBroadcastPush;
|
||||
private _encodeUserBroadcastPush;
|
||||
decode(rawPayload: ArrayBuffer | string, callback: Function): any;
|
||||
private _binaryDecode;
|
||||
private _decodeUserBroadcast;
|
||||
private _isArrayBuffer;
|
||||
private _pick;
|
||||
}
|
||||
//# sourceMappingURL=serializer.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../../../src/lib/serializer.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI;IACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,CAAC,CAAA;CACX,CAAA;AAED,MAAM,CAAC,OAAO,OAAO,UAAU;IAC7B,aAAa,SAAI;IACjB,+BAA+B,SAAI;IACnC,KAAK;;;MAA6C;IAClD,eAAe,SAAI;IACnB,aAAa,SAAI;IACjB,eAAe,SAAc;IAE7B,mBAAmB,EAAE,MAAM,EAAE,CAAK;gBAEtB,mBAAmB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI;IAIjD,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,KAAK,GAAG;IAexF,OAAO,CAAC,8BAA8B;IAQtC,OAAO,CAAC,8BAA8B;IAKtC,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,wBAAwB;IA6EhC,MAAM,CAAC,UAAU,EAAE,WAAW,GAAG,MAAM,EAAE,QAAQ,EAAE,QAAQ;IAe3D,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,oBAAoB;IA0C5B,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,KAAK;CAMd"}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
class Serializer {
|
||||
constructor(allowedMetadataKeys) {
|
||||
this.HEADER_LENGTH = 1;
|
||||
this.USER_BROADCAST_PUSH_META_LENGTH = 6;
|
||||
this.KINDS = { userBroadcastPush: 3, userBroadcast: 4 };
|
||||
this.BINARY_ENCODING = 0;
|
||||
this.JSON_ENCODING = 1;
|
||||
this.BROADCAST_EVENT = 'broadcast';
|
||||
this.allowedMetadataKeys = [];
|
||||
this.allowedMetadataKeys = allowedMetadataKeys !== null && allowedMetadataKeys !== void 0 ? allowedMetadataKeys : [];
|
||||
}
|
||||
encode(msg, callback) {
|
||||
if (msg.event === this.BROADCAST_EVENT &&
|
||||
!(msg.payload instanceof ArrayBuffer) &&
|
||||
typeof msg.payload.event === 'string') {
|
||||
return callback(this._binaryEncodeUserBroadcastPush(msg));
|
||||
}
|
||||
let payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload];
|
||||
return callback(JSON.stringify(payload));
|
||||
}
|
||||
_binaryEncodeUserBroadcastPush(message) {
|
||||
var _a;
|
||||
if (this._isArrayBuffer((_a = message.payload) === null || _a === void 0 ? void 0 : _a.payload)) {
|
||||
return this._encodeBinaryUserBroadcastPush(message);
|
||||
}
|
||||
else {
|
||||
return this._encodeJsonUserBroadcastPush(message);
|
||||
}
|
||||
}
|
||||
_encodeBinaryUserBroadcastPush(message) {
|
||||
var _a, _b;
|
||||
const userPayload = (_b = (_a = message.payload) === null || _a === void 0 ? void 0 : _a.payload) !== null && _b !== void 0 ? _b : new ArrayBuffer(0);
|
||||
return this._encodeUserBroadcastPush(message, this.BINARY_ENCODING, userPayload);
|
||||
}
|
||||
_encodeJsonUserBroadcastPush(message) {
|
||||
var _a, _b;
|
||||
const userPayload = (_b = (_a = message.payload) === null || _a === void 0 ? void 0 : _a.payload) !== null && _b !== void 0 ? _b : {};
|
||||
const encoder = new TextEncoder();
|
||||
const encodedUserPayload = encoder.encode(JSON.stringify(userPayload)).buffer;
|
||||
return this._encodeUserBroadcastPush(message, this.JSON_ENCODING, encodedUserPayload);
|
||||
}
|
||||
_encodeUserBroadcastPush(message, encodingType, encodedPayload) {
|
||||
var _a, _b;
|
||||
// Encode each header field as UTF-8. The length prefixes are byte counts and
|
||||
// the decode side uses TextDecoder (UTF-8), so measuring with String.length
|
||||
// and writing with charCodeAt would corrupt any multi-byte character (e.g.
|
||||
// accents or emoji) and desynchronize the buffer.
|
||||
const encoder = new TextEncoder();
|
||||
const topic = encoder.encode(message.topic);
|
||||
const ref = encoder.encode((_a = message.ref) !== null && _a !== void 0 ? _a : '');
|
||||
const joinRef = encoder.encode((_b = message.join_ref) !== null && _b !== void 0 ? _b : '');
|
||||
const userEvent = encoder.encode(message.payload.event);
|
||||
// Filter metadata based on allowed keys
|
||||
const rest = this.allowedMetadataKeys
|
||||
? this._pick(message.payload, this.allowedMetadataKeys)
|
||||
: {};
|
||||
const metadata = encoder.encode(Object.keys(rest).length === 0 ? '' : JSON.stringify(rest));
|
||||
// Validate byte lengths don't exceed uint8 max value (255)
|
||||
if (joinRef.length > 255) {
|
||||
throw new Error(`joinRef length ${joinRef.length} exceeds maximum of 255`);
|
||||
}
|
||||
if (ref.length > 255) {
|
||||
throw new Error(`ref length ${ref.length} exceeds maximum of 255`);
|
||||
}
|
||||
if (topic.length > 255) {
|
||||
throw new Error(`topic length ${topic.length} exceeds maximum of 255`);
|
||||
}
|
||||
if (userEvent.length > 255) {
|
||||
throw new Error(`userEvent length ${userEvent.length} exceeds maximum of 255`);
|
||||
}
|
||||
if (metadata.length > 255) {
|
||||
throw new Error(`metadata length ${metadata.length} exceeds maximum of 255`);
|
||||
}
|
||||
const metaLength = this.USER_BROADCAST_PUSH_META_LENGTH +
|
||||
joinRef.length +
|
||||
ref.length +
|
||||
topic.length +
|
||||
userEvent.length +
|
||||
metadata.length;
|
||||
const header = new ArrayBuffer(this.HEADER_LENGTH + metaLength);
|
||||
const view = new DataView(header);
|
||||
const bytes = new Uint8Array(header);
|
||||
let offset = 0;
|
||||
view.setUint8(offset++, this.KINDS.userBroadcastPush); // kind
|
||||
view.setUint8(offset++, joinRef.length);
|
||||
view.setUint8(offset++, ref.length);
|
||||
view.setUint8(offset++, topic.length);
|
||||
view.setUint8(offset++, userEvent.length);
|
||||
view.setUint8(offset++, metadata.length);
|
||||
view.setUint8(offset++, encodingType);
|
||||
bytes.set(joinRef, offset);
|
||||
offset += joinRef.length;
|
||||
bytes.set(ref, offset);
|
||||
offset += ref.length;
|
||||
bytes.set(topic, offset);
|
||||
offset += topic.length;
|
||||
bytes.set(userEvent, offset);
|
||||
offset += userEvent.length;
|
||||
bytes.set(metadata, offset);
|
||||
offset += metadata.length;
|
||||
var combined = new Uint8Array(header.byteLength + encodedPayload.byteLength);
|
||||
combined.set(new Uint8Array(header), 0);
|
||||
combined.set(new Uint8Array(encodedPayload), header.byteLength);
|
||||
return combined.buffer;
|
||||
}
|
||||
decode(rawPayload, callback) {
|
||||
if (this._isArrayBuffer(rawPayload)) {
|
||||
let result = this._binaryDecode(rawPayload);
|
||||
return callback(result);
|
||||
}
|
||||
if (typeof rawPayload === 'string') {
|
||||
const jsonPayload = JSON.parse(rawPayload);
|
||||
const [join_ref, ref, topic, event, payload] = jsonPayload;
|
||||
return callback({ join_ref, ref, topic, event, payload });
|
||||
}
|
||||
return callback({});
|
||||
}
|
||||
_binaryDecode(buffer) {
|
||||
const view = new DataView(buffer);
|
||||
const kind = view.getUint8(0);
|
||||
const decoder = new TextDecoder();
|
||||
switch (kind) {
|
||||
case this.KINDS.userBroadcast:
|
||||
return this._decodeUserBroadcast(buffer, view, decoder);
|
||||
}
|
||||
}
|
||||
_decodeUserBroadcast(buffer, view, decoder) {
|
||||
const topicSize = view.getUint8(1);
|
||||
const userEventSize = view.getUint8(2);
|
||||
const metadataSize = view.getUint8(3);
|
||||
const payloadEncoding = view.getUint8(4);
|
||||
let offset = this.HEADER_LENGTH + 4;
|
||||
const topic = decoder.decode(buffer.slice(offset, offset + topicSize));
|
||||
offset = offset + topicSize;
|
||||
const userEvent = decoder.decode(buffer.slice(offset, offset + userEventSize));
|
||||
offset = offset + userEventSize;
|
||||
const metadata = decoder.decode(buffer.slice(offset, offset + metadataSize));
|
||||
offset = offset + metadataSize;
|
||||
const payload = buffer.slice(offset, buffer.byteLength);
|
||||
const parsedPayload = payloadEncoding === this.JSON_ENCODING ? JSON.parse(decoder.decode(payload)) : payload;
|
||||
const data = {
|
||||
type: this.BROADCAST_EVENT,
|
||||
event: userEvent,
|
||||
payload: parsedPayload,
|
||||
};
|
||||
// Metadata is optional and always JSON encoded
|
||||
if (metadataSize > 0) {
|
||||
data['meta'] = JSON.parse(metadata);
|
||||
}
|
||||
return { join_ref: null, ref: null, topic: topic, event: this.BROADCAST_EVENT, payload: data };
|
||||
}
|
||||
_isArrayBuffer(buffer) {
|
||||
var _a;
|
||||
return buffer instanceof ArrayBuffer || ((_a = buffer === null || buffer === void 0 ? void 0 : buffer.constructor) === null || _a === void 0 ? void 0 : _a.name) === 'ArrayBuffer';
|
||||
}
|
||||
_pick(obj, keys) {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return {};
|
||||
}
|
||||
return Object.fromEntries(Object.entries(obj).filter(([key]) => keys.includes(key)));
|
||||
}
|
||||
}
|
||||
exports.default = Serializer;
|
||||
//# sourceMappingURL=serializer.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+109
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Helpers to convert the change Payload into native JS types.
|
||||
*/
|
||||
export declare enum PostgresTypes {
|
||||
abstime = "abstime",
|
||||
bool = "bool",
|
||||
date = "date",
|
||||
daterange = "daterange",
|
||||
float4 = "float4",
|
||||
float8 = "float8",
|
||||
int2 = "int2",
|
||||
int4 = "int4",
|
||||
int4range = "int4range",
|
||||
int8 = "int8",
|
||||
int8range = "int8range",
|
||||
json = "json",
|
||||
jsonb = "jsonb",
|
||||
money = "money",
|
||||
numeric = "numeric",
|
||||
oid = "oid",
|
||||
reltime = "reltime",
|
||||
text = "text",
|
||||
time = "time",
|
||||
timestamp = "timestamp",
|
||||
timestamptz = "timestamptz",
|
||||
timetz = "timetz",
|
||||
tsrange = "tsrange",
|
||||
tstzrange = "tstzrange"
|
||||
}
|
||||
type Columns = {
|
||||
name: string;
|
||||
type: string;
|
||||
flags?: string[];
|
||||
type_modifier?: number;
|
||||
}[];
|
||||
type BaseValue = null | string | number | boolean;
|
||||
type RecordValue = BaseValue | BaseValue[];
|
||||
type Record = {
|
||||
[key: string]: RecordValue;
|
||||
};
|
||||
/**
|
||||
* Takes an array of columns and an object of string values then converts each string value
|
||||
* to its mapped type.
|
||||
*
|
||||
* @param {{name: String, type: String}[]} columns
|
||||
* @param {Object} record
|
||||
* @param {Object} options The map of various options that can be applied to the mapper
|
||||
* @param {Array} options.skipTypes The array of types that should not be converted
|
||||
*
|
||||
* @example convertChangeData([{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age:'33'}, {})
|
||||
* //=>{ first_name: 'Paul', age: 33 }
|
||||
*/
|
||||
export declare const convertChangeData: (columns: Columns, record: Record | null, options?: {
|
||||
skipTypes?: string[];
|
||||
}) => Record;
|
||||
/**
|
||||
* Converts the value of an individual column.
|
||||
*
|
||||
* @param {String} columnName The column that you want to convert
|
||||
* @param {{name: String, type: String}[]} columns All of the columns
|
||||
* @param {Object} record The map of string values
|
||||
* @param {Array} skipTypes An array of types that should not be converted
|
||||
* @return {object} Useless information
|
||||
*
|
||||
* @example convertColumn('age', [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age: '33'}, [])
|
||||
* //=> 33
|
||||
* @example convertColumn('age', [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age: '33'}, ['int4'])
|
||||
* //=> "33"
|
||||
*/
|
||||
export declare const convertColumn: (columnName: string, columns: Columns, record: Record, skipTypes: string[]) => RecordValue;
|
||||
/**
|
||||
* If the value of the cell is `null`, returns null.
|
||||
* Otherwise converts the string value to the correct type.
|
||||
* @param {String} type A postgres column type
|
||||
* @param {String} value The cell value
|
||||
*
|
||||
* @example convertCell('bool', 't')
|
||||
* //=> true
|
||||
* @example convertCell('int8', '10')
|
||||
* //=> 10
|
||||
* @example convertCell('_int4', '{1,2,3,4}')
|
||||
* //=> [1,2,3,4]
|
||||
*/
|
||||
export declare const convertCell: (type: string, value: RecordValue) => RecordValue;
|
||||
export declare const toBoolean: (value: RecordValue) => RecordValue;
|
||||
export declare const toNumber: (value: RecordValue) => RecordValue;
|
||||
export declare const toJson: (value: RecordValue) => RecordValue;
|
||||
/**
|
||||
* Converts a Postgres Array into a native JS array
|
||||
*
|
||||
* @example toArray('{}', 'int4')
|
||||
* //=> []
|
||||
* @example toArray('{"[2021-01-01,2021-12-31)","(2021-01-01,2021-12-32]"}', 'daterange')
|
||||
* //=> ['[2021-01-01,2021-12-31)', '(2021-01-01,2021-12-32]']
|
||||
* @example toArray([1,2,3,4], 'int4')
|
||||
* //=> [1,2,3,4]
|
||||
*/
|
||||
export declare const toArray: (value: RecordValue, type: string) => RecordValue;
|
||||
/**
|
||||
* Fixes timestamp to be ISO-8601. Swaps the space between the date and time for a 'T'
|
||||
* See https://github.com/supabase/supabase/issues/18
|
||||
*
|
||||
* @example toTimestampString('2019-09-10 00:00:00')
|
||||
* //=> '2019-09-10T00:00:00'
|
||||
*/
|
||||
export declare const toTimestampString: (value: RecordValue) => RecordValue;
|
||||
export declare const httpEndpointURL: (socketUrl: string) => string;
|
||||
export {};
|
||||
//# sourceMappingURL=transformers.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"transformers.d.ts","sourceRoot":"","sources":["../../../src/lib/transformers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,oBAAY,aAAa;IACvB,OAAO,YAAY;IACnB,IAAI,SAAS;IACb,IAAI,SAAS;IACb,SAAS,cAAc;IACvB,MAAM,WAAW;IACjB,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,IAAI,SAAS;IACb,SAAS,cAAc;IACvB,IAAI,SAAS;IACb,SAAS,cAAc;IACvB,IAAI,SAAS;IACb,KAAK,UAAU;IACf,KAAK,UAAU;IACf,OAAO,YAAY;IACnB,GAAG,QAAQ;IACX,OAAO,YAAY;IACnB,IAAI,SAAS;IACb,IAAI,SAAS;IACb,SAAS,cAAc;IACvB,WAAW,gBAAgB;IAC3B,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,SAAS,cAAc;CACxB;AAED,KAAK,OAAO,GAAG;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,EAAE,CAAA;AAEH,KAAK,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AACjD,KAAK,WAAW,GAAG,SAAS,GAAG,SAAS,EAAE,CAAA;AAE1C,KAAK,MAAM,GAAG;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAAA;CAC3B,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,GAC5B,SAAS,OAAO,EAChB,QAAQ,MAAM,GAAG,IAAI,EACrB,UAAS;IAAE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,KACrC,MAWF,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa,GACxB,YAAY,MAAM,EAClB,SAAS,OAAO,EAChB,QAAQ,MAAM,EACd,WAAW,MAAM,EAAE,KAClB,WAUF,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,EAAE,OAAO,WAAW,KAAG,WA0C9D,CAAA;AAKD,eAAO,MAAM,SAAS,GAAI,OAAO,WAAW,KAAG,WAS9C,CAAA;AACD,eAAO,MAAM,QAAQ,GAAI,OAAO,WAAW,KAAG,WAQ7C,CAAA;AACD,eAAO,MAAM,MAAM,GAAI,OAAO,WAAW,KAAG,WAS3C,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,OAAO,GAAI,OAAO,WAAW,EAAE,MAAM,MAAM,KAAG,WA0B1D,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,WAAW,KAAG,WAMtD,CAAA;AAED,eAAO,MAAM,eAAe,GAAI,WAAW,MAAM,KAAG,MAkBnD,CAAA"}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Helpers to convert the change Payload into native JS types.
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.httpEndpointURL = exports.toTimestampString = exports.toArray = exports.toJson = exports.toNumber = exports.toBoolean = exports.convertCell = exports.convertColumn = exports.convertChangeData = exports.PostgresTypes = void 0;
|
||||
// Adapted from epgsql (src/epgsql_binary.erl), this module licensed under
|
||||
// 3-clause BSD found here: https://raw.githubusercontent.com/epgsql/epgsql/devel/LICENSE
|
||||
var PostgresTypes;
|
||||
(function (PostgresTypes) {
|
||||
PostgresTypes["abstime"] = "abstime";
|
||||
PostgresTypes["bool"] = "bool";
|
||||
PostgresTypes["date"] = "date";
|
||||
PostgresTypes["daterange"] = "daterange";
|
||||
PostgresTypes["float4"] = "float4";
|
||||
PostgresTypes["float8"] = "float8";
|
||||
PostgresTypes["int2"] = "int2";
|
||||
PostgresTypes["int4"] = "int4";
|
||||
PostgresTypes["int4range"] = "int4range";
|
||||
PostgresTypes["int8"] = "int8";
|
||||
PostgresTypes["int8range"] = "int8range";
|
||||
PostgresTypes["json"] = "json";
|
||||
PostgresTypes["jsonb"] = "jsonb";
|
||||
PostgresTypes["money"] = "money";
|
||||
PostgresTypes["numeric"] = "numeric";
|
||||
PostgresTypes["oid"] = "oid";
|
||||
PostgresTypes["reltime"] = "reltime";
|
||||
PostgresTypes["text"] = "text";
|
||||
PostgresTypes["time"] = "time";
|
||||
PostgresTypes["timestamp"] = "timestamp";
|
||||
PostgresTypes["timestamptz"] = "timestamptz";
|
||||
PostgresTypes["timetz"] = "timetz";
|
||||
PostgresTypes["tsrange"] = "tsrange";
|
||||
PostgresTypes["tstzrange"] = "tstzrange";
|
||||
})(PostgresTypes || (exports.PostgresTypes = PostgresTypes = {}));
|
||||
/**
|
||||
* Takes an array of columns and an object of string values then converts each string value
|
||||
* to its mapped type.
|
||||
*
|
||||
* @param {{name: String, type: String}[]} columns
|
||||
* @param {Object} record
|
||||
* @param {Object} options The map of various options that can be applied to the mapper
|
||||
* @param {Array} options.skipTypes The array of types that should not be converted
|
||||
*
|
||||
* @example convertChangeData([{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age:'33'}, {})
|
||||
* //=>{ first_name: 'Paul', age: 33 }
|
||||
*/
|
||||
const convertChangeData = (columns, record, options = {}) => {
|
||||
var _a;
|
||||
const skipTypes = (_a = options.skipTypes) !== null && _a !== void 0 ? _a : [];
|
||||
if (!record) {
|
||||
return {};
|
||||
}
|
||||
return Object.keys(record).reduce((acc, rec_key) => {
|
||||
acc[rec_key] = (0, exports.convertColumn)(rec_key, columns, record, skipTypes);
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
exports.convertChangeData = convertChangeData;
|
||||
/**
|
||||
* Converts the value of an individual column.
|
||||
*
|
||||
* @param {String} columnName The column that you want to convert
|
||||
* @param {{name: String, type: String}[]} columns All of the columns
|
||||
* @param {Object} record The map of string values
|
||||
* @param {Array} skipTypes An array of types that should not be converted
|
||||
* @return {object} Useless information
|
||||
*
|
||||
* @example convertColumn('age', [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age: '33'}, [])
|
||||
* //=> 33
|
||||
* @example convertColumn('age', [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age: '33'}, ['int4'])
|
||||
* //=> "33"
|
||||
*/
|
||||
const convertColumn = (columnName, columns, record, skipTypes) => {
|
||||
const column = columns.find((x) => x.name === columnName);
|
||||
const colType = column === null || column === void 0 ? void 0 : column.type;
|
||||
const value = record[columnName];
|
||||
if (colType && !skipTypes.includes(colType)) {
|
||||
return (0, exports.convertCell)(colType, value);
|
||||
}
|
||||
return noop(value);
|
||||
};
|
||||
exports.convertColumn = convertColumn;
|
||||
/**
|
||||
* If the value of the cell is `null`, returns null.
|
||||
* Otherwise converts the string value to the correct type.
|
||||
* @param {String} type A postgres column type
|
||||
* @param {String} value The cell value
|
||||
*
|
||||
* @example convertCell('bool', 't')
|
||||
* //=> true
|
||||
* @example convertCell('int8', '10')
|
||||
* //=> 10
|
||||
* @example convertCell('_int4', '{1,2,3,4}')
|
||||
* //=> [1,2,3,4]
|
||||
*/
|
||||
const convertCell = (type, value) => {
|
||||
// if data type is an array
|
||||
if (type.charAt(0) === '_') {
|
||||
const dataType = type.slice(1, type.length);
|
||||
return (0, exports.toArray)(value, dataType);
|
||||
}
|
||||
// If not null, convert to correct type.
|
||||
switch (type) {
|
||||
case PostgresTypes.bool:
|
||||
return (0, exports.toBoolean)(value);
|
||||
case PostgresTypes.float4:
|
||||
case PostgresTypes.float8:
|
||||
case PostgresTypes.int2:
|
||||
case PostgresTypes.int4:
|
||||
case PostgresTypes.int8:
|
||||
case PostgresTypes.numeric:
|
||||
case PostgresTypes.oid:
|
||||
return (0, exports.toNumber)(value);
|
||||
case PostgresTypes.json:
|
||||
case PostgresTypes.jsonb:
|
||||
return (0, exports.toJson)(value);
|
||||
case PostgresTypes.timestamp:
|
||||
return (0, exports.toTimestampString)(value); // Format to be consistent with PostgREST
|
||||
case PostgresTypes.abstime: // To allow users to cast it based on Timezone
|
||||
case PostgresTypes.date: // To allow users to cast it based on Timezone
|
||||
case PostgresTypes.daterange:
|
||||
case PostgresTypes.int4range:
|
||||
case PostgresTypes.int8range:
|
||||
case PostgresTypes.money:
|
||||
case PostgresTypes.reltime: // To allow users to cast it based on Timezone
|
||||
case PostgresTypes.text:
|
||||
case PostgresTypes.time: // To allow users to cast it based on Timezone
|
||||
case PostgresTypes.timestamptz: // To allow users to cast it based on Timezone
|
||||
case PostgresTypes.timetz: // To allow users to cast it based on Timezone
|
||||
case PostgresTypes.tsrange:
|
||||
case PostgresTypes.tstzrange:
|
||||
return noop(value);
|
||||
default:
|
||||
// Return the value for remaining types
|
||||
return noop(value);
|
||||
}
|
||||
};
|
||||
exports.convertCell = convertCell;
|
||||
const noop = (value) => {
|
||||
return value;
|
||||
};
|
||||
const toBoolean = (value) => {
|
||||
switch (value) {
|
||||
case 't':
|
||||
return true;
|
||||
case 'f':
|
||||
return false;
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
};
|
||||
exports.toBoolean = toBoolean;
|
||||
const toNumber = (value) => {
|
||||
if (typeof value === 'string') {
|
||||
const parsedValue = parseFloat(value);
|
||||
if (!Number.isNaN(parsedValue)) {
|
||||
return parsedValue;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
exports.toNumber = toNumber;
|
||||
const toJson = (value) => {
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
catch (_a) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
exports.toJson = toJson;
|
||||
/**
|
||||
* Converts a Postgres Array into a native JS array
|
||||
*
|
||||
* @example toArray('{}', 'int4')
|
||||
* //=> []
|
||||
* @example toArray('{"[2021-01-01,2021-12-31)","(2021-01-01,2021-12-32]"}', 'daterange')
|
||||
* //=> ['[2021-01-01,2021-12-31)', '(2021-01-01,2021-12-32]']
|
||||
* @example toArray([1,2,3,4], 'int4')
|
||||
* //=> [1,2,3,4]
|
||||
*/
|
||||
const toArray = (value, type) => {
|
||||
if (typeof value !== 'string') {
|
||||
return value;
|
||||
}
|
||||
const lastIdx = value.length - 1;
|
||||
const closeBrace = value[lastIdx];
|
||||
const openBrace = value[0];
|
||||
// Confirm value is a Postgres array by checking curly brackets
|
||||
if (openBrace === '{' && closeBrace === '}') {
|
||||
let arr;
|
||||
const valTrim = value.slice(1, lastIdx);
|
||||
// TODO: find a better solution to separate Postgres array data
|
||||
try {
|
||||
arr = JSON.parse('[' + valTrim + ']');
|
||||
}
|
||||
catch (_) {
|
||||
// WARNING: splitting on comma does not cover all edge cases
|
||||
arr = valTrim ? valTrim.split(',') : [];
|
||||
}
|
||||
return arr.map((val) => (0, exports.convertCell)(type, val));
|
||||
}
|
||||
return value;
|
||||
};
|
||||
exports.toArray = toArray;
|
||||
/**
|
||||
* Fixes timestamp to be ISO-8601. Swaps the space between the date and time for a 'T'
|
||||
* See https://github.com/supabase/supabase/issues/18
|
||||
*
|
||||
* @example toTimestampString('2019-09-10 00:00:00')
|
||||
* //=> '2019-09-10T00:00:00'
|
||||
*/
|
||||
const toTimestampString = (value) => {
|
||||
if (typeof value === 'string') {
|
||||
return value.replace(' ', 'T');
|
||||
}
|
||||
return value;
|
||||
};
|
||||
exports.toTimestampString = toTimestampString;
|
||||
const httpEndpointURL = (socketUrl) => {
|
||||
const wsUrl = new URL(socketUrl);
|
||||
wsUrl.protocol = wsUrl.protocol.replace(/^ws/i, 'http');
|
||||
wsUrl.pathname = wsUrl.pathname
|
||||
.replace(/\/+$/, '') // remove all trailing slashes
|
||||
.replace(/\/socket\/websocket$/i, '') // remove the socket/websocket path
|
||||
.replace(/\/socket$/i, '') // remove the socket path
|
||||
.replace(/\/websocket$/i, ''); // remove the websocket path
|
||||
if (wsUrl.pathname === '' || wsUrl.pathname === '/') {
|
||||
wsUrl.pathname = '/api/broadcast';
|
||||
}
|
||||
else {
|
||||
wsUrl.pathname = wsUrl.pathname + '/api/broadcast';
|
||||
}
|
||||
return wsUrl.href;
|
||||
};
|
||||
exports.httpEndpointURL = httpEndpointURL;
|
||||
//# sourceMappingURL=transformers.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+2
@@ -0,0 +1,2 @@
|
||||
export declare const version = "2.110.8";
|
||||
//# sourceMappingURL=version.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../../src/lib/version.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,OAAO,YAAY,CAAA"}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.version = void 0;
|
||||
// Generated automatically during releases by scripts/update-version-files.ts
|
||||
// This file provides runtime access to the package version for:
|
||||
// - HTTP request headers (e.g., X-Client-Info header for API requests)
|
||||
// - Debugging and support (identifying which version is running)
|
||||
// - Telemetry and logging (version reporting in errors/analytics)
|
||||
// - Ensuring build artifacts match the published package version
|
||||
exports.version = '2.110.8';
|
||||
//# sourceMappingURL=version.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../../src/lib/version.ts"],"names":[],"mappings":";;;AAAA,6EAA6E;AAC7E,gEAAgE;AAChE,uEAAuE;AACvE,iEAAiE;AACjE,kEAAkE;AAClE,iEAAiE;AACpD,QAAA,OAAO,GAAG,SAAS,CAAA"}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
export interface WebSocketLike {
|
||||
readonly CONNECTING: number;
|
||||
readonly OPEN: number;
|
||||
readonly CLOSING: number;
|
||||
readonly CLOSED: number;
|
||||
readonly readyState: number;
|
||||
readonly url: string;
|
||||
readonly protocol: string;
|
||||
/**
|
||||
* Closes the socket, optionally providing a close code and reason.
|
||||
*/
|
||||
close(code?: number, reason?: string): void;
|
||||
/**
|
||||
* Sends data through the socket using the underlying implementation.
|
||||
*/
|
||||
send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void;
|
||||
onopen: ((this: any, ev: Event) => any) | null;
|
||||
onmessage: ((this: any, ev: MessageEvent) => any) | null;
|
||||
onclose: ((this: any, ev: CloseEvent) => any) | null;
|
||||
onerror: ((this: any, ev: Event) => any) | null;
|
||||
/**
|
||||
* Registers an event listener on the socket (compatible with browser WebSocket API).
|
||||
*/
|
||||
addEventListener(type: string, listener: EventListener): void;
|
||||
/**
|
||||
* Removes a previously registered event listener.
|
||||
*/
|
||||
removeEventListener(type: string, listener: EventListener): void;
|
||||
binaryType?: string;
|
||||
bufferedAmount?: number;
|
||||
extensions?: string;
|
||||
dispatchEvent?: (event: Event) => boolean;
|
||||
}
|
||||
export interface WebSocketEnvironment {
|
||||
type: 'native' | 'cloudflare' | 'unsupported';
|
||||
/** WebSocket constructor for this environment, if available. */
|
||||
wsConstructor?: typeof WebSocket;
|
||||
error?: string;
|
||||
workaround?: string;
|
||||
}
|
||||
/**
|
||||
* Utilities for creating WebSocket instances across runtimes.
|
||||
*/
|
||||
export declare class WebSocketFactory {
|
||||
/**
|
||||
* Static-only utility – prevent instantiation.
|
||||
*/
|
||||
private constructor();
|
||||
private static detectEnvironment;
|
||||
/**
|
||||
* Returns the best available WebSocket constructor for the current runtime.
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Example with error handling
|
||||
* ```ts
|
||||
* try {
|
||||
* const WS = WebSocketFactory.getWebSocketConstructor()
|
||||
* const socket = new WS('wss://example.com/socket')
|
||||
* } catch (error) {
|
||||
* console.error('WebSocket not available in this environment.', error)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
static getWebSocketConstructor(): typeof WebSocket;
|
||||
/**
|
||||
* Detects whether the runtime can establish WebSocket connections.
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Example in a Node.js script
|
||||
* ```ts
|
||||
* if (!WebSocketFactory.isWebSocketSupported()) {
|
||||
* console.error('WebSockets are required for this script.')
|
||||
* process.exitCode = 1
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
static isWebSocketSupported(): boolean;
|
||||
}
|
||||
export default WebSocketFactory;
|
||||
//# sourceMappingURL=websocket-factory.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"websocket-factory.d.ts","sourceRoot":"","sources":["../../../src/lib/websocket-factory.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IAEzB;;OAEG;IACH,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3C;;OAEG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,GAAG,eAAe,GAAG,IAAI,CAAA;IAEnE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,KAAK,GAAG,CAAC,GAAG,IAAI,CAAA;IAC9C,SAAS,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,YAAY,KAAK,GAAG,CAAC,GAAG,IAAI,CAAA;IACxD,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,UAAU,KAAK,GAAG,CAAC,GAAG,IAAI,CAAA;IACpD,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,KAAK,KAAK,GAAG,CAAC,GAAG,IAAI,CAAA;IAE/C;;OAEG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAA;IAC7D;;OAEG;IACH,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAA;IAGhE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAA;CAC1C;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,QAAQ,GAAG,YAAY,GAAG,aAAa,CAAA;IAC7C,gEAAgE;IAChE,aAAa,CAAC,EAAE,OAAO,SAAS,CAAA;IAChC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAYD;;GAEG;AACH,qBAAa,gBAAgB;IAC3B;;OAEG;IACH,OAAO;IACP,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAqEhC;;;;;;;;;;;;;;OAcG;WACW,uBAAuB,IAAI,OAAO,SAAS;IAYzD;;;;;;;;;;;;OAYG;WACW,oBAAoB,IAAI,OAAO;CAQ9C;AAED,eAAe,gBAAgB,CAAA"}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.WebSocketFactory = void 0;
|
||||
/**
|
||||
* Utilities for creating WebSocket instances across runtimes.
|
||||
*/
|
||||
class WebSocketFactory {
|
||||
/**
|
||||
* Static-only utility – prevent instantiation.
|
||||
*/
|
||||
constructor() { }
|
||||
static detectEnvironment() {
|
||||
var _a;
|
||||
if (typeof WebSocket !== 'undefined') {
|
||||
return { type: 'native', wsConstructor: WebSocket };
|
||||
}
|
||||
const gt = globalThis;
|
||||
if (typeof globalThis !== 'undefined' && typeof gt.WebSocket !== 'undefined') {
|
||||
return { type: 'native', wsConstructor: gt.WebSocket };
|
||||
}
|
||||
const gl = typeof global !== 'undefined' ? global : undefined;
|
||||
if (gl && typeof gl.WebSocket !== 'undefined') {
|
||||
return { type: 'native', wsConstructor: gl.WebSocket };
|
||||
}
|
||||
if (typeof globalThis !== 'undefined' &&
|
||||
typeof gt.WebSocketPair !== 'undefined' &&
|
||||
typeof globalThis.WebSocket === 'undefined') {
|
||||
return {
|
||||
type: 'cloudflare',
|
||||
error: 'Cloudflare Workers detected. WebSocket clients are not supported in Cloudflare Workers.',
|
||||
workaround: 'Use Cloudflare Workers WebSocket API for server-side WebSocket handling, or deploy to a different runtime.',
|
||||
};
|
||||
}
|
||||
if ((typeof globalThis !== 'undefined' && gt.EdgeRuntime) ||
|
||||
(typeof navigator !== 'undefined' && ((_a = navigator.userAgent) === null || _a === void 0 ? void 0 : _a.includes('Vercel-Edge')))) {
|
||||
return {
|
||||
type: 'unsupported',
|
||||
error: 'Edge runtime detected (Vercel Edge/Netlify Edge). WebSockets are not supported in edge functions.',
|
||||
workaround: 'Use serverless functions or a different deployment target for WebSocket functionality.',
|
||||
};
|
||||
}
|
||||
// Use dynamic property access to avoid Next.js Edge Runtime static analysis warnings
|
||||
const _process = globalThis['process'];
|
||||
if (_process) {
|
||||
const processVersions = _process['versions'];
|
||||
if (processVersions && processVersions['node']) {
|
||||
// Reaching here means an earlier check did not find a native WebSocket,
|
||||
// so this Node.js process is missing the global WebSocket (Node.js 22+).
|
||||
return {
|
||||
type: 'unsupported',
|
||||
error: 'Node.js detected but native WebSocket not found.',
|
||||
workaround: 'Ensure you are running Node.js 22+ or provide a WebSocket implementation via the transport option.',
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'unsupported',
|
||||
error: 'Unknown JavaScript runtime without WebSocket support.',
|
||||
workaround: "Ensure you're running in a supported environment (browser, Node.js, Deno) or provide a custom WebSocket implementation.",
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Returns the best available WebSocket constructor for the current runtime.
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Example with error handling
|
||||
* ```ts
|
||||
* try {
|
||||
* const WS = WebSocketFactory.getWebSocketConstructor()
|
||||
* const socket = new WS('wss://example.com/socket')
|
||||
* } catch (error) {
|
||||
* console.error('WebSocket not available in this environment.', error)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
static getWebSocketConstructor() {
|
||||
const env = this.detectEnvironment();
|
||||
if (env.wsConstructor) {
|
||||
return env.wsConstructor;
|
||||
}
|
||||
let errorMessage = env.error || 'WebSocket not supported in this environment.';
|
||||
if (env.workaround) {
|
||||
errorMessage += `\n\nSuggested solution: ${env.workaround}`;
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
/**
|
||||
* Detects whether the runtime can establish WebSocket connections.
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Example in a Node.js script
|
||||
* ```ts
|
||||
* if (!WebSocketFactory.isWebSocketSupported()) {
|
||||
* console.error('WebSockets are required for this script.')
|
||||
* process.exitCode = 1
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
static isWebSocketSupported() {
|
||||
try {
|
||||
const env = this.detectEnvironment();
|
||||
return env.type === 'native';
|
||||
}
|
||||
catch (_a) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.WebSocketFactory = WebSocketFactory;
|
||||
exports.default = WebSocketFactory;
|
||||
//# sourceMappingURL=websocket-factory.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"websocket-factory.js","sourceRoot":"","sources":["../../../src/lib/websocket-factory.ts"],"names":[],"mappings":"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"}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import type { RealtimeChannelOptions } from '../RealtimeChannel';
|
||||
import SocketAdapter from './socketAdapter';
|
||||
import type { ChannelBindingCallback, ChannelOnMessage, ChannelOnErrorCallback, ChannelFilterBindings, ChannelState, Push, Timer } from './types';
|
||||
export default class ChannelAdapter {
|
||||
private channel;
|
||||
private socket;
|
||||
constructor(socket: SocketAdapter, topic: string, params: RealtimeChannelOptions);
|
||||
get state(): ChannelState;
|
||||
set state(state: ChannelState);
|
||||
get joinedOnce(): boolean;
|
||||
get joinPush(): Push;
|
||||
get rejoinTimer(): Timer;
|
||||
on(event: string, callback: ChannelBindingCallback): number;
|
||||
off(event: string, refNumber?: number): void;
|
||||
subscribe(timeout?: number): Push;
|
||||
unsubscribe(timeout?: number): Push;
|
||||
teardown(): void;
|
||||
onClose(callback: ChannelBindingCallback): void;
|
||||
onError(callback: ChannelOnErrorCallback): number;
|
||||
push(event: string, payload: {
|
||||
[key: string]: any;
|
||||
}, timeout?: number): Push;
|
||||
updateJoinPayload(payload: Record<string, any>): void;
|
||||
canPush(): boolean;
|
||||
isJoined(): boolean;
|
||||
isJoining(): boolean;
|
||||
isClosed(): boolean;
|
||||
isLeaving(): boolean;
|
||||
updateFilterBindings(filterBindings: ChannelFilterBindings): void;
|
||||
updatePayloadTransform(callback: ChannelOnMessage): void;
|
||||
}
|
||||
//# sourceMappingURL=channelAdapter.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"channelAdapter.d.ts","sourceRoot":"","sources":["../../../src/phoenix/channelAdapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAChE,OAAO,aAAa,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,EACV,sBAAsB,EACtB,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EAErB,YAAY,EACZ,IAAI,EACJ,KAAK,EACN,MAAM,SAAS,CAAA;AAEhB,MAAM,CAAC,OAAO,OAAO,cAAc;IACjC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAe;gBAEjB,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB;IAMhF,IAAI,KAAK,IAAI,YAAY,CAExB;IAED,IAAI,KAAK,CAAC,KAAK,EAAE,YAAY,EAE5B;IAED,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,IAAI,QAAQ,IAAI,IAAI,CAEnB;IAED,IAAI,WAAW,IAAI,KAAK,CAEvB;IAED,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,sBAAsB,GAAG,MAAM;IAI3D,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;IAIrC,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAIjC,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAInC,QAAQ;IAIR,OAAO,CAAC,QAAQ,EAAE,sBAAsB;IAIxC,OAAO,CAAC,QAAQ,EAAE,sBAAsB,GAAG,MAAM;IAIjD,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAuB5E,iBAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;IAK9C,OAAO;IAIP,QAAQ;IAIR,SAAS;IAIT,QAAQ;IAIR,SAAS;IAIT,oBAAoB,CAAC,cAAc,EAAE,qBAAqB;IAI1D,sBAAsB,CAAC,QAAQ,EAAE,gBAAgB;CAUlD"}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const constants_1 = require("../lib/constants");
|
||||
class ChannelAdapter {
|
||||
constructor(socket, topic, params) {
|
||||
const phoenixParams = phoenixChannelParams(params);
|
||||
this.channel = socket.getSocket().channel(topic, phoenixParams);
|
||||
this.socket = socket;
|
||||
}
|
||||
get state() {
|
||||
return this.channel.state;
|
||||
}
|
||||
set state(state) {
|
||||
this.channel.state = state;
|
||||
}
|
||||
get joinedOnce() {
|
||||
return this.channel.joinedOnce;
|
||||
}
|
||||
get joinPush() {
|
||||
return this.channel.joinPush;
|
||||
}
|
||||
get rejoinTimer() {
|
||||
return this.channel.rejoinTimer;
|
||||
}
|
||||
on(event, callback) {
|
||||
return this.channel.on(event, callback);
|
||||
}
|
||||
off(event, refNumber) {
|
||||
this.channel.off(event, refNumber);
|
||||
}
|
||||
subscribe(timeout) {
|
||||
return this.channel.join(timeout);
|
||||
}
|
||||
unsubscribe(timeout) {
|
||||
return this.channel.leave(timeout);
|
||||
}
|
||||
teardown() {
|
||||
this.channel.teardown();
|
||||
}
|
||||
onClose(callback) {
|
||||
this.channel.onClose(callback);
|
||||
}
|
||||
onError(callback) {
|
||||
return this.channel.onError(callback);
|
||||
}
|
||||
push(event, payload, timeout) {
|
||||
let push;
|
||||
try {
|
||||
push = this.channel.push(event, payload, timeout);
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`tried to push '${event}' to '${this.channel.topic}' before joining. Use channel.subscribe() before pushing events`);
|
||||
}
|
||||
if (this.channel.pushBuffer.length > constants_1.MAX_PUSH_BUFFER_SIZE) {
|
||||
const removedPush = this.channel.pushBuffer.shift();
|
||||
removedPush.cancelTimeout();
|
||||
this.socket.log('channel', `discarded push due to buffer overflow: ${removedPush.event}`, removedPush.payload());
|
||||
}
|
||||
return push;
|
||||
}
|
||||
updateJoinPayload(payload) {
|
||||
const oldPayload = this.channel.joinPush.payload();
|
||||
this.channel.joinPush.payload = () => (Object.assign(Object.assign({}, oldPayload), payload));
|
||||
}
|
||||
canPush() {
|
||||
return this.socket.isConnected() && this.state === constants_1.CHANNEL_STATES.joined;
|
||||
}
|
||||
isJoined() {
|
||||
return this.state === constants_1.CHANNEL_STATES.joined;
|
||||
}
|
||||
isJoining() {
|
||||
return this.state === constants_1.CHANNEL_STATES.joining;
|
||||
}
|
||||
isClosed() {
|
||||
return this.state === constants_1.CHANNEL_STATES.closed;
|
||||
}
|
||||
isLeaving() {
|
||||
return this.state === constants_1.CHANNEL_STATES.leaving;
|
||||
}
|
||||
updateFilterBindings(filterBindings) {
|
||||
this.channel.filterBindings = filterBindings;
|
||||
}
|
||||
updatePayloadTransform(callback) {
|
||||
this.channel.onMessage = callback;
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
getChannel() {
|
||||
return this.channel;
|
||||
}
|
||||
}
|
||||
exports.default = ChannelAdapter;
|
||||
function phoenixChannelParams(options) {
|
||||
return {
|
||||
config: Object.assign({
|
||||
broadcast: { ack: false, self: false },
|
||||
presence: { key: '', enabled: false },
|
||||
private: false,
|
||||
}, options.config),
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=channelAdapter.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"channelAdapter.js","sourceRoot":"","sources":["../../../src/phoenix/channelAdapter.ts"],"names":[],"mappings":"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"}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import type { PresenceState, PresenceStates } from './types';
|
||||
import type { RealtimePresenceOptions, RealtimePresenceState } from '../RealtimePresence';
|
||||
import ChannelAdapter from './channelAdapter';
|
||||
export default class PresenceAdapter {
|
||||
private presence;
|
||||
constructor(channel: ChannelAdapter, opts?: RealtimePresenceOptions);
|
||||
get state(): RealtimePresenceState;
|
||||
/**
|
||||
* @private
|
||||
* Remove 'metas' key
|
||||
* Change 'phx_ref' to 'presence_ref'
|
||||
* Remove 'phx_ref' and 'phx_ref_prev'
|
||||
*
|
||||
* @example Transform state
|
||||
* // returns {
|
||||
* abc123: [
|
||||
* { presence_ref: '2', user_id: 1 },
|
||||
* { presence_ref: '3', user_id: 2 }
|
||||
* ]
|
||||
* }
|
||||
* RealtimePresence.transformState({
|
||||
* abc123: {
|
||||
* metas: [
|
||||
* { phx_ref: '2', phx_ref_prev: '1' user_id: 1 },
|
||||
* { phx_ref: '3', user_id: 2 }
|
||||
* ]
|
||||
* }
|
||||
* })
|
||||
*
|
||||
*/
|
||||
static transformState(state: PresenceStates): RealtimePresenceState;
|
||||
static onJoinPayload(key: string, currentPresence: PresenceState, newPresence: PresenceState): {
|
||||
event: string;
|
||||
key: string;
|
||||
currentPresences: {
|
||||
presence_ref: string;
|
||||
}[];
|
||||
newPresences: {
|
||||
presence_ref: string;
|
||||
}[];
|
||||
};
|
||||
static onLeavePayload(key: string, currentPresence: PresenceState, leftPresence: PresenceState): {
|
||||
event: string;
|
||||
key: string;
|
||||
currentPresences: {
|
||||
presence_ref: string;
|
||||
}[];
|
||||
leftPresences: {
|
||||
presence_ref: string;
|
||||
}[];
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=presenceAdapter.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"presenceAdapter.d.ts","sourceRoot":"","sources":["../../../src/phoenix/presenceAdapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAC5D,OAAO,KAAK,EACV,uBAAuB,EACvB,qBAAqB,EAEtB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,cAAc,MAAM,kBAAkB,CAAA;AAE7C,MAAM,CAAC,OAAO,OAAO,eAAe;IAClC,OAAO,CAAC,QAAQ,CAAU;gBAEd,OAAO,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,uBAAuB;IAmBnE,IAAI,KAAK,0BAER;IAED;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,qBAAqB;IAWnE,MAAM,CAAC,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa;;;;;;;;;;IAY5F,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa;;;;;;;;;;CAW/F"}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const phoenix_1 = require("@supabase/phoenix");
|
||||
class PresenceAdapter {
|
||||
constructor(channel, opts) {
|
||||
const phoenixOptions = phoenixPresenceOptions(opts);
|
||||
this.presence = new phoenix_1.Presence(channel.getChannel(), phoenixOptions);
|
||||
this.presence.onJoin((key, currentPresence, newPresence) => {
|
||||
const onJoinPayload = PresenceAdapter.onJoinPayload(key, currentPresence, newPresence);
|
||||
channel.getChannel().trigger('presence', onJoinPayload);
|
||||
});
|
||||
this.presence.onLeave((key, currentPresence, leftPresence) => {
|
||||
const onLeavePayload = PresenceAdapter.onLeavePayload(key, currentPresence, leftPresence);
|
||||
channel.getChannel().trigger('presence', onLeavePayload);
|
||||
});
|
||||
this.presence.onSync(() => {
|
||||
channel.getChannel().trigger('presence', { event: 'sync' });
|
||||
});
|
||||
}
|
||||
get state() {
|
||||
return PresenceAdapter.transformState(this.presence.state);
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* Remove 'metas' key
|
||||
* Change 'phx_ref' to 'presence_ref'
|
||||
* Remove 'phx_ref' and 'phx_ref_prev'
|
||||
*
|
||||
* @example Transform state
|
||||
* // returns {
|
||||
* abc123: [
|
||||
* { presence_ref: '2', user_id: 1 },
|
||||
* { presence_ref: '3', user_id: 2 }
|
||||
* ]
|
||||
* }
|
||||
* RealtimePresence.transformState({
|
||||
* abc123: {
|
||||
* metas: [
|
||||
* { phx_ref: '2', phx_ref_prev: '1' user_id: 1 },
|
||||
* { phx_ref: '3', user_id: 2 }
|
||||
* ]
|
||||
* }
|
||||
* })
|
||||
*
|
||||
*/
|
||||
static transformState(state) {
|
||||
state = cloneState(state);
|
||||
return Object.getOwnPropertyNames(state).reduce((newState, key) => {
|
||||
const presences = state[key];
|
||||
newState[key] = transformState(presences);
|
||||
return newState;
|
||||
}, {});
|
||||
}
|
||||
static onJoinPayload(key, currentPresence, newPresence) {
|
||||
const currentPresences = parseCurrentPresences(currentPresence);
|
||||
const newPresences = transformState(newPresence);
|
||||
return {
|
||||
event: 'join',
|
||||
key,
|
||||
currentPresences,
|
||||
newPresences,
|
||||
};
|
||||
}
|
||||
static onLeavePayload(key, currentPresence, leftPresence) {
|
||||
const currentPresences = parseCurrentPresences(currentPresence);
|
||||
const leftPresences = transformState(leftPresence);
|
||||
return {
|
||||
event: 'leave',
|
||||
key,
|
||||
currentPresences,
|
||||
leftPresences,
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.default = PresenceAdapter;
|
||||
function transformState(presences) {
|
||||
return presences.metas.map((presence) => {
|
||||
presence['presence_ref'] = presence['phx_ref'];
|
||||
delete presence['phx_ref'];
|
||||
delete presence['phx_ref_prev'];
|
||||
return presence;
|
||||
});
|
||||
}
|
||||
function cloneState(state) {
|
||||
return JSON.parse(JSON.stringify(state));
|
||||
}
|
||||
function phoenixPresenceOptions(opts) {
|
||||
return (opts === null || opts === void 0 ? void 0 : opts.events) && { events: opts.events };
|
||||
}
|
||||
function parseCurrentPresences(currentPresences) {
|
||||
return (currentPresences === null || currentPresences === void 0 ? void 0 : currentPresences.metas) ? transformState(currentPresences) : [];
|
||||
}
|
||||
//# sourceMappingURL=presenceAdapter.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"presenceAdapter.js","sourceRoot":"","sources":["../../../src/phoenix/presenceAdapter.ts"],"names":[],"mappings":"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"}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import type { Message, SocketOnClose, SocketOnMessage, SocketOnOpen, SocketOnError, SocketOptions, SocketStateChangeCallbacks, Vsn, Encode, Decode, HeartbeatCallback, Timer } from './types';
|
||||
import { ConnectionState } from '../lib/constants';
|
||||
import type { HeartbeatTimer, WebSocketLikeConstructor } from '../RealtimeClient';
|
||||
export default class SocketAdapter {
|
||||
private socket;
|
||||
constructor(endPoint: string, options: SocketOptions);
|
||||
get timeout(): number;
|
||||
get endPoint(): string;
|
||||
get transport(): WebSocketLikeConstructor;
|
||||
get heartbeatIntervalMs(): number;
|
||||
get heartbeatCallback(): HeartbeatCallback;
|
||||
set heartbeatCallback(callback: HeartbeatCallback);
|
||||
get heartbeatTimer(): HeartbeatTimer;
|
||||
get pendingHeartbeatRef(): string | null;
|
||||
get reconnectTimer(): Timer;
|
||||
get vsn(): Vsn;
|
||||
get encode(): Encode<void>;
|
||||
get decode(): Decode<void>;
|
||||
get reconnectAfterMs(): (tries: number) => number;
|
||||
get sendBuffer(): (() => void)[];
|
||||
get stateChangeCallbacks(): SocketStateChangeCallbacks;
|
||||
connect(): void;
|
||||
disconnect(callback: () => void, code?: number, reason?: string, timeout?: number): Promise<'ok' | 'timeout'>;
|
||||
push(data: Message<Record<string, unknown>>): void;
|
||||
log(kind: string, msg: string, data?: any): void;
|
||||
makeRef(): string;
|
||||
onOpen(callback: SocketOnOpen): void;
|
||||
onClose(callback: SocketOnClose): void;
|
||||
onError(callback: SocketOnError): void;
|
||||
onMessage(callback: SocketOnMessage): void;
|
||||
isConnected(): boolean;
|
||||
isConnecting(): boolean;
|
||||
isDisconnecting(): boolean;
|
||||
connectionState(): ConnectionState;
|
||||
endPointURL(): string;
|
||||
sendHeartbeat(): void;
|
||||
}
|
||||
//# sourceMappingURL=socketAdapter.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"socketAdapter.d.ts","sourceRoot":"","sources":["../../../src/phoenix/socketAdapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,OAAO,EACP,aAAa,EACb,eAAe,EACf,YAAY,EACZ,aAAa,EACb,aAAa,EACb,0BAA0B,EAC1B,GAAG,EACH,MAAM,EACN,MAAM,EACN,iBAAiB,EACjB,KAAK,EACN,MAAM,SAAS,CAAA;AAChB,OAAO,EAAoB,eAAe,EAAE,MAAM,kBAAkB,CAAA;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAA;AAEjF,MAAM,CAAC,OAAO,OAAO,aAAa;IAChC,OAAO,CAAC,MAAM,CAAQ;gBAEV,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa;IAIpD,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,SAAS,IAAI,wBAAwB,CAExC;IAED,IAAI,mBAAmB,IAAI,MAAM,CAEhC;IAED,IAAI,iBAAiB,IAAI,iBAAiB,CAEzC;IAED,IAAI,iBAAiB,CAAC,QAAQ,EAAE,iBAAiB,EAEhD;IAED,IAAI,cAAc,IAAI,cAAc,CAEnC;IAED,IAAI,mBAAmB,IAAI,MAAM,GAAG,IAAI,CAEvC;IAED,IAAI,cAAc,IAAI,KAAK,CAE1B;IAED,IAAI,GAAG,IAAI,GAAG,CAEb;IAED,IAAI,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,CAEzB;IAED,IAAI,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,CAEzB;IAED,IAAI,gBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAEhD;IAED,IAAI,UAAU,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,CAE/B;IAED,IAAI,oBAAoB,IAAI,0BAA0B,CAErD;IAED,OAAO;IAIP,UAAU,CACR,QAAQ,EAAE,MAAM,IAAI,EACpB,IAAI,CAAC,EAAE,MAAM,EACb,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,GAAE,MAAc,GACtB,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC;IAc5B,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAI3C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG;IAIzC,OAAO,IAAI,MAAM;IAIjB,MAAM,CAAC,QAAQ,EAAE,YAAY;IAI7B,OAAO,CAAC,QAAQ,EAAE,aAAa;IAI/B,OAAO,CAAC,QAAQ,EAAE,aAAa;IAI/B,SAAS,CAAC,QAAQ,EAAE,eAAe;IAInC,WAAW;IAIX,YAAY;IAIZ,eAAe;IAIf,eAAe,IAAI,eAAe;IAKlC,WAAW,IAAI,MAAM;IAIrB,aAAa;CAUd"}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const phoenix_1 = require("@supabase/phoenix");
|
||||
const constants_1 = require("../lib/constants");
|
||||
class SocketAdapter {
|
||||
constructor(endPoint, options) {
|
||||
this.socket = new phoenix_1.Socket(endPoint, options);
|
||||
}
|
||||
get timeout() {
|
||||
return this.socket.timeout;
|
||||
}
|
||||
get endPoint() {
|
||||
return this.socket.endPoint;
|
||||
}
|
||||
get transport() {
|
||||
return this.socket.transport;
|
||||
}
|
||||
get heartbeatIntervalMs() {
|
||||
return this.socket.heartbeatIntervalMs;
|
||||
}
|
||||
get heartbeatCallback() {
|
||||
return this.socket.heartbeatCallback;
|
||||
}
|
||||
set heartbeatCallback(callback) {
|
||||
this.socket.heartbeatCallback = callback;
|
||||
}
|
||||
get heartbeatTimer() {
|
||||
return this.socket.heartbeatTimer;
|
||||
}
|
||||
get pendingHeartbeatRef() {
|
||||
return this.socket.pendingHeartbeatRef;
|
||||
}
|
||||
get reconnectTimer() {
|
||||
return this.socket.reconnectTimer;
|
||||
}
|
||||
get vsn() {
|
||||
return this.socket.vsn;
|
||||
}
|
||||
get encode() {
|
||||
return this.socket.encode;
|
||||
}
|
||||
get decode() {
|
||||
return this.socket.decode;
|
||||
}
|
||||
get reconnectAfterMs() {
|
||||
return this.socket.reconnectAfterMs;
|
||||
}
|
||||
get sendBuffer() {
|
||||
return this.socket.sendBuffer;
|
||||
}
|
||||
get stateChangeCallbacks() {
|
||||
return this.socket.stateChangeCallbacks;
|
||||
}
|
||||
connect() {
|
||||
this.socket.connect();
|
||||
}
|
||||
disconnect(callback, code, reason, timeout = 10000) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => resolve('timeout'), timeout);
|
||||
this.socket.disconnect(() => {
|
||||
callback();
|
||||
resolve('ok');
|
||||
}, code, reason);
|
||||
});
|
||||
}
|
||||
push(data) {
|
||||
this.socket.push(data);
|
||||
}
|
||||
log(kind, msg, data) {
|
||||
this.socket.log(kind, msg, data);
|
||||
}
|
||||
makeRef() {
|
||||
return this.socket.makeRef();
|
||||
}
|
||||
onOpen(callback) {
|
||||
this.socket.onOpen(callback);
|
||||
}
|
||||
onClose(callback) {
|
||||
this.socket.onClose(callback);
|
||||
}
|
||||
onError(callback) {
|
||||
this.socket.onError(callback);
|
||||
}
|
||||
onMessage(callback) {
|
||||
this.socket.onMessage(callback);
|
||||
}
|
||||
isConnected() {
|
||||
return this.socket.isConnected();
|
||||
}
|
||||
isConnecting() {
|
||||
return this.socket.connectionState() == constants_1.CONNECTION_STATE.connecting;
|
||||
}
|
||||
isDisconnecting() {
|
||||
return this.socket.connectionState() == constants_1.CONNECTION_STATE.closing;
|
||||
}
|
||||
connectionState() {
|
||||
// @ts-ignore - requires better typing and exposing type in phoenix
|
||||
return this.socket.connectionState();
|
||||
}
|
||||
endPointURL() {
|
||||
return this.socket.endPointURL();
|
||||
}
|
||||
sendHeartbeat() {
|
||||
this.socket.sendHeartbeat();
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
getSocket() {
|
||||
return this.socket;
|
||||
}
|
||||
}
|
||||
exports.default = SocketAdapter;
|
||||
//# sourceMappingURL=socketAdapter.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"socketAdapter.js","sourceRoot":"","sources":["../../../src/phoenix/socketAdapter.ts"],"names":[],"mappings":"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"}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export type { Socket, SocketOptions, SocketState, SocketOnOpen, SocketOnError, SocketOnMessage, SocketOnClose, SocketStateChangeCallbacks, Channel, ChannelState, ChannelEvent, ChannelBindingCallback, ChannelFilterBindings, ChannelOnMessage, ChannelOnErrorCallback, PresenceState, Message, Params, Transport, Timer, Vsn, Encode, Decode, HeartbeatCallback, HeartbeatStatus, } from '@supabase/phoenix';
|
||||
import type { Channel, PresenceState } from '@supabase/phoenix';
|
||||
export type Push = ReturnType<Channel['push']>;
|
||||
export type PresenceStates = Record<string, PresenceState>;
|
||||
//# sourceMappingURL=types.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/phoenix/types.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,MAAM,EACN,aAAa,EACb,WAAW,EACX,YAAY,EACZ,aAAa,EACb,eAAe,EACf,aAAa,EACb,0BAA0B,EAC1B,OAAO,EACP,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,sBAAsB,EACtB,aAAa,EACb,OAAO,EACP,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,MAAM,EACN,MAAM,EACN,iBAAiB,EACjB,eAAe,GAChB,MAAM,mBAAmB,CAAA;AAE1B,OAAO,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AAE/D,MAAM,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;AAC9C,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA"}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=types.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/phoenix/types.ts"],"names":[],"mappings":""}
|
||||
+575
@@ -0,0 +1,575 @@
|
||||
import type { ChannelState } from './lib/constants';
|
||||
import type RealtimeClient from './RealtimeClient';
|
||||
import RealtimePresence, { REALTIME_PRESENCE_LISTEN_EVENTS } from './RealtimePresence';
|
||||
import type { RealtimePresenceJoinPayload, RealtimePresenceLeavePayload, RealtimePresenceState } from './RealtimePresence';
|
||||
import { ChannelBindingCallback } from './phoenix/types';
|
||||
import type { Timer } from './phoenix/types';
|
||||
import { RealtimePostgresFilterBuilder } from './RealtimePostgresFilterBuilder';
|
||||
export type { RealtimePostgresChangesFilterOperator } from './RealtimePostgresFilterBuilder';
|
||||
export { RealtimePostgresFilterBuilder, postgresChangesFilter, } from './RealtimePostgresFilterBuilder';
|
||||
type ReplayOption = {
|
||||
since: number;
|
||||
limit?: number;
|
||||
};
|
||||
export type RealtimeChannelOptions = {
|
||||
config: {
|
||||
/**
|
||||
* self option enables client to receive message it broadcast
|
||||
* ack option instructs server to acknowledge that broadcast message was received
|
||||
* replay option instructs server to replay broadcast messages
|
||||
* replication_ready option instructs the server to emit a `system` event once the
|
||||
* Postgres replication connection backing this channel is established and ready to
|
||||
* stream changes. Listen for it with `channel.on('system', {}, (payload) => ...)`;
|
||||
* the payload's `status` is `'ok'` (`message: 'Replication connection established'`)
|
||||
* on success or `'error'` if the connection is not ready in time.
|
||||
*/
|
||||
broadcast?: {
|
||||
self?: boolean;
|
||||
ack?: boolean;
|
||||
replay?: ReplayOption;
|
||||
replication_ready?: boolean;
|
||||
};
|
||||
/**
|
||||
* key option is used to track presence payload across clients
|
||||
*
|
||||
* enabled controls whether this client receives presence state and updates from other
|
||||
* clients — set it to true (or add an `.on('presence', ...)` listener, which enables it
|
||||
* automatically) if you want to see who else is present. Without it, this client's
|
||||
* `presenceState()` stays empty and no `presence` events fire for you, because the
|
||||
* underlying presence state machine buffers incoming updates until it has received an
|
||||
* initial snapshot, which is only requested when this flag is set.
|
||||
*
|
||||
* It does not gate the other direction: calling `track()` always makes this client
|
||||
* visible to other subscribers that have presence enabled, regardless of this client's
|
||||
* own `enabled` setting. On RLS-protected (private) channels, receiving presence updates
|
||||
* additionally requires the `presence.read` policy to authorize this client.
|
||||
*/
|
||||
presence?: {
|
||||
key?: string;
|
||||
enabled?: boolean;
|
||||
};
|
||||
/**
|
||||
* defines if the channel is private or not and if RLS policies will be used to check data
|
||||
*/
|
||||
private?: boolean;
|
||||
};
|
||||
};
|
||||
type RealtimeChangesPayloadBase = {
|
||||
schema: string;
|
||||
table: string;
|
||||
};
|
||||
type RealtimeBroadcastChangesPayloadBase = RealtimeChangesPayloadBase & {
|
||||
id: string;
|
||||
};
|
||||
export type RealtimeBroadcastInsertPayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = RealtimeBroadcastChangesPayloadBase & {
|
||||
operation: `${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.INSERT}`;
|
||||
record: T;
|
||||
old_record: null;
|
||||
};
|
||||
export type RealtimeBroadcastUpdatePayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = RealtimeBroadcastChangesPayloadBase & {
|
||||
operation: `${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.UPDATE}`;
|
||||
record: T;
|
||||
old_record: T;
|
||||
};
|
||||
export type RealtimeBroadcastDeletePayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = RealtimeBroadcastChangesPayloadBase & {
|
||||
operation: `${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.DELETE}`;
|
||||
record: null;
|
||||
old_record: T;
|
||||
};
|
||||
export type RealtimeBroadcastPayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = RealtimeBroadcastInsertPayload<T> | RealtimeBroadcastUpdatePayload<T> | RealtimeBroadcastDeletePayload<T>;
|
||||
type RealtimePostgresChangesPayloadBase = {
|
||||
schema: string;
|
||||
table: string;
|
||||
commit_timestamp: string;
|
||||
errors: string[];
|
||||
};
|
||||
export type RealtimePostgresInsertPayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = RealtimePostgresChangesPayloadBase & {
|
||||
eventType: `${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.INSERT}`;
|
||||
new: T;
|
||||
old: {};
|
||||
};
|
||||
export type RealtimePostgresUpdatePayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = RealtimePostgresChangesPayloadBase & {
|
||||
eventType: `${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.UPDATE}`;
|
||||
new: T;
|
||||
old: Partial<T>;
|
||||
};
|
||||
export type RealtimePostgresDeletePayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = RealtimePostgresChangesPayloadBase & {
|
||||
eventType: `${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.DELETE}`;
|
||||
new: {};
|
||||
old: Partial<T>;
|
||||
};
|
||||
export type RealtimePostgresChangesPayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = RealtimePostgresInsertPayload<T> | RealtimePostgresUpdatePayload<T> | RealtimePostgresDeletePayload<T>;
|
||||
export type RealtimePostgresChangesFilter<T extends `${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT}`> = {
|
||||
/**
|
||||
* The type of database change to listen to.
|
||||
*/
|
||||
event: T;
|
||||
/**
|
||||
* The database schema to listen to.
|
||||
*/
|
||||
schema: string;
|
||||
/**
|
||||
* The database table to listen to.
|
||||
*/
|
||||
table?: string;
|
||||
/**
|
||||
* Receive database changes only when the filter is matched.
|
||||
*
|
||||
* A filter is a `column=operator.value` expression, e.g. `id=eq.1` or
|
||||
* `title=like.%foo%`. See {@link RealtimePostgresChangesFilterOperator} for
|
||||
* the available operators.
|
||||
*
|
||||
* Multiple filters can be combined with commas; they are applied as an `AND`
|
||||
* condition: `filter: 'id=gt.0,id=lt.100'`.
|
||||
*
|
||||
* Any operator can be negated with the `not.` prefix: `filter: 'status=not.in.(draft,archived)'`.
|
||||
*
|
||||
* The server splits conditions on commas outside quotes/parentheses. To
|
||||
* include a reserved character (`,`, `(`, `)`) in a value, wrap it in double
|
||||
* quotes PostgREST-style: `name=eq."a,b"`. The {@link RealtimePostgresFilterBuilder}
|
||||
* does this quoting for you.
|
||||
*
|
||||
* Instead of a raw string you can pass a {@link RealtimePostgresFilterBuilder}
|
||||
* (via `postgresChangesFilter()`) for a type-checked, ergonomic way to compose filters; the
|
||||
* SDK serializes it to a string automatically.
|
||||
*/
|
||||
filter?: string | RealtimePostgresFilterBuilder;
|
||||
/**
|
||||
* Restrict the change payload to a subset of columns instead of receiving the
|
||||
* full row. Reduces payload size (helpful for large `bytea`/`jsonb` columns)
|
||||
* and the data transferred per event.
|
||||
*
|
||||
* The listed columns must be selectable by the subscribing role.
|
||||
*
|
||||
* @example
|
||||
* channel.on('postgres_changes', {
|
||||
* event: '*',
|
||||
* schema: 'public',
|
||||
* table: 'users',
|
||||
* select: ['id', 'first_name'],
|
||||
* }, (payload) => {
|
||||
* // payload.new only contains { id, first_name }
|
||||
* })
|
||||
*/
|
||||
select?: string[];
|
||||
};
|
||||
export type RealtimeChannelSendResponse = 'ok' | 'timed out' | 'error' | (string & {});
|
||||
/**
|
||||
* Payload of a `system` event emitted by the server.
|
||||
*
|
||||
* Most notably, when a channel is created with `config.broadcast.replication_ready: true`,
|
||||
* the server sends one of these once the Postgres replication connection is ready
|
||||
* (`status: 'ok'`) or fails to become ready in time (`status: 'error'`).
|
||||
*/
|
||||
export type RealtimeSystemPayload = {
|
||||
/** The extension that produced the message, e.g. `'system'` or `'postgres_changes'`. */
|
||||
extension: 'system' | 'postgres_changes' | (string & {});
|
||||
/** `'ok'` on success, `'error'` on failure. */
|
||||
status: 'ok' | 'error' | (string & {});
|
||||
/** Human-readable description, e.g. `'Replication connection established'`. */
|
||||
message: string;
|
||||
/** The channel (sub)topic the message refers to. */
|
||||
channel: string;
|
||||
};
|
||||
export declare enum REALTIME_POSTGRES_CHANGES_LISTEN_EVENT {
|
||||
ALL = "*",
|
||||
INSERT = "INSERT",
|
||||
UPDATE = "UPDATE",
|
||||
DELETE = "DELETE"
|
||||
}
|
||||
export declare enum REALTIME_LISTEN_TYPES {
|
||||
BROADCAST = "broadcast",
|
||||
PRESENCE = "presence",
|
||||
POSTGRES_CHANGES = "postgres_changes",
|
||||
SYSTEM = "system"
|
||||
}
|
||||
export declare enum REALTIME_SUBSCRIBE_STATES {
|
||||
SUBSCRIBED = "SUBSCRIBED",
|
||||
TIMED_OUT = "TIMED_OUT",
|
||||
CLOSED = "CLOSED",
|
||||
CHANNEL_ERROR = "CHANNEL_ERROR"
|
||||
}
|
||||
export declare const REALTIME_CHANNEL_STATES: {
|
||||
readonly closed: "closed";
|
||||
readonly errored: "errored";
|
||||
readonly joined: "joined";
|
||||
readonly joining: "joining";
|
||||
readonly leaving: "leaving";
|
||||
};
|
||||
type Binding = {
|
||||
type: string;
|
||||
filter: {
|
||||
[key: string]: any;
|
||||
};
|
||||
callback: ChannelBindingCallback;
|
||||
ref: number;
|
||||
id?: string;
|
||||
};
|
||||
/** A channel is the basic building block of Realtime
|
||||
* and narrows the scope of data flow to subscribed clients.
|
||||
* You can think of a channel as a chatroom where participants are able to see who's online
|
||||
* and send and receive messages.
|
||||
*/
|
||||
export default class RealtimeChannel {
|
||||
/** Topic name can be any string. */
|
||||
topic: string;
|
||||
params: RealtimeChannelOptions;
|
||||
socket: RealtimeClient;
|
||||
bindings: Record<string, Binding[]>;
|
||||
subTopic: string;
|
||||
broadcastEndpointURL: string;
|
||||
private: boolean;
|
||||
presence: RealtimePresence;
|
||||
get state(): ChannelState;
|
||||
set state(state: ChannelState);
|
||||
get joinedOnce(): boolean;
|
||||
get timeout(): number;
|
||||
get joinPush(): import("@supabase/phoenix").Push;
|
||||
get rejoinTimer(): Timer;
|
||||
/**
|
||||
* Creates a channel that can broadcast messages, sync presence, and listen to Postgres changes.
|
||||
*
|
||||
* The topic determines which realtime stream you are subscribing to. Config options let you
|
||||
* enable acknowledgement for broadcasts, presence tracking, or private channels.
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Using supabase-js (recommended)
|
||||
* ```ts
|
||||
* import { createClient } from '@supabase/supabase-js'
|
||||
*
|
||||
* const supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key')
|
||||
* const channel = supabase.channel('room1')
|
||||
* channel
|
||||
* .on('broadcast', { event: 'cursor-pos' }, (payload) => console.log(payload))
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @example Standalone import for bundle-sensitive environments
|
||||
* ```ts
|
||||
* import RealtimeClient from '@supabase/realtime-js'
|
||||
*
|
||||
* const client = new RealtimeClient('https://xyzcompany.supabase.co/realtime/v1', {
|
||||
* params: { apikey: 'your-publishable-key' },
|
||||
* })
|
||||
* const channel = new RealtimeChannel('realtime:public:messages', { config: {} }, client)
|
||||
* ```
|
||||
*/
|
||||
constructor(
|
||||
/** Topic name can be any string. */
|
||||
topic: string, params: RealtimeChannelOptions | undefined, socket: RealtimeClient);
|
||||
/**
|
||||
* Subscribe registers your client with the server.
|
||||
*
|
||||
* The optional `callback` receives a `status` and, on failure, an `err` argument.
|
||||
* Log the full `err` so its `cause`, `name`, and any structured fields aren't hidden
|
||||
* behind `err.message`.
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Handling errors
|
||||
* ```js
|
||||
* supabase.channel('room1').subscribe((status, err) => {
|
||||
* if (status === 'CHANNEL_ERROR' || status === 'TIMED_OUT') {
|
||||
* // Log the full error: its `cause` often holds the underlying reason.
|
||||
* console.error(status, err)
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
subscribe(callback?: (status: REALTIME_SUBSCRIBE_STATES, err?: Error) => void, timeout?: number): RealtimeChannel;
|
||||
private _updatePostgresBindings;
|
||||
/**
|
||||
* Returns the current presence state for this channel.
|
||||
*
|
||||
* The shape is a map keyed by presence key (for example a user id) where each entry contains the
|
||||
* tracked metadata for that user.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
presenceState<T extends {
|
||||
[key: string]: any;
|
||||
} = {}>(): RealtimePresenceState<T>;
|
||||
/**
|
||||
* Sends the supplied payload to the presence tracker so other subscribers can see that this
|
||||
* client is online. Use `untrack` to stop broadcasting presence for the same key.
|
||||
*
|
||||
* Tracking makes this client visible to other subscribers immediately, regardless of this
|
||||
* channel's `config.presence.enabled` setting or whether it has a `presence` listener — that
|
||||
* flag only affects whether *this* client receives presence updates from others (and, on
|
||||
* RLS-protected channels, whether it's authorized to do so).
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
track(payload: {
|
||||
[key: string]: any;
|
||||
}, opts?: {
|
||||
[key: string]: any;
|
||||
}): Promise<RealtimeChannelSendResponse>;
|
||||
/**
|
||||
* Removes the current presence state for this client.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
untrack(opts?: {
|
||||
[key: string]: any;
|
||||
}): Promise<RealtimeChannelSendResponse>;
|
||||
/**
|
||||
* Listen for presence events on this channel — when peers join, leave, or
|
||||
* sync presence state.
|
||||
*/
|
||||
on(type: `${REALTIME_LISTEN_TYPES.PRESENCE}`, filter: {
|
||||
event: `${REALTIME_PRESENCE_LISTEN_EVENTS.SYNC}`;
|
||||
}, callback: () => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.PRESENCE}`, filter: {
|
||||
event: `${REALTIME_PRESENCE_LISTEN_EVENTS.JOIN}`;
|
||||
}, callback: (payload: RealtimePresenceJoinPayload<T>) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.PRESENCE}`, filter: {
|
||||
event: `${REALTIME_PRESENCE_LISTEN_EVENTS.LEAVE}`;
|
||||
}, callback: (payload: RealtimePresenceLeavePayload<T>) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.PRESENCE}`, filter: {
|
||||
event: '*';
|
||||
}, callback: (payload?: RealtimePresenceJoinPayload<T> | RealtimePresenceLeavePayload<T>) => void): RealtimeChannel;
|
||||
/**
|
||||
* Listen for Postgres database changes (insert / update / delete) streamed
|
||||
* over this channel.
|
||||
*/
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.POSTGRES_CHANGES}`, filter: RealtimePostgresChangesFilter<`${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.ALL}`>, callback: (payload: RealtimePostgresChangesPayload<T>) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.POSTGRES_CHANGES}`, filter: RealtimePostgresChangesFilter<`${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.INSERT}`>, callback: (payload: RealtimePostgresInsertPayload<T>) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.POSTGRES_CHANGES}`, filter: RealtimePostgresChangesFilter<`${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.UPDATE}`>, callback: (payload: RealtimePostgresUpdatePayload<T>) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.POSTGRES_CHANGES}`, filter: RealtimePostgresChangesFilter<`${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.DELETE}`>, callback: (payload: RealtimePostgresDeletePayload<T>) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.POSTGRES_CHANGES}`, filter: RealtimePostgresChangesFilter<`${REALTIME_POSTGRES_CHANGES_LISTEN_EVENT}`>, callback: (payload: RealtimePostgresChangesPayload<T>) => void): RealtimeChannel;
|
||||
/**
|
||||
* Listen for broadcast messages sent on this channel.
|
||||
*
|
||||
* @param type One of "broadcast", "presence", or "postgres_changes".
|
||||
* @param filter Custom object specific to the Realtime feature detailing which payloads to receive.
|
||||
* @param callback Function to be invoked when event handler is triggered.
|
||||
*/
|
||||
on(type: `${REALTIME_LISTEN_TYPES.BROADCAST}`, filter: {
|
||||
event: string;
|
||||
}, callback: (payload: {
|
||||
type: `${REALTIME_LISTEN_TYPES.BROADCAST}`;
|
||||
event: string;
|
||||
meta?: {
|
||||
replayed?: boolean;
|
||||
id: string;
|
||||
};
|
||||
[key: string]: any;
|
||||
}) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.BROADCAST}`, filter: {
|
||||
event: string;
|
||||
}, callback: (payload: {
|
||||
type: `${REALTIME_LISTEN_TYPES.BROADCAST}`;
|
||||
event: string;
|
||||
meta?: {
|
||||
replayed?: boolean;
|
||||
id: string;
|
||||
};
|
||||
payload: T;
|
||||
}) => void): RealtimeChannel;
|
||||
on<T extends Record<string, unknown>>(type: `${REALTIME_LISTEN_TYPES.BROADCAST}`, filter: {
|
||||
event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.ALL;
|
||||
}, callback: (payload: {
|
||||
type: `${REALTIME_LISTEN_TYPES.BROADCAST}`;
|
||||
event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.ALL;
|
||||
payload: RealtimeBroadcastPayload<T>;
|
||||
}) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.BROADCAST}`, filter: {
|
||||
event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.INSERT;
|
||||
}, callback: (payload: {
|
||||
type: `${REALTIME_LISTEN_TYPES.BROADCAST}`;
|
||||
event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.INSERT;
|
||||
payload: RealtimeBroadcastInsertPayload<T>;
|
||||
}) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.BROADCAST}`, filter: {
|
||||
event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.UPDATE;
|
||||
}, callback: (payload: {
|
||||
type: `${REALTIME_LISTEN_TYPES.BROADCAST}`;
|
||||
event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.UPDATE;
|
||||
payload: RealtimeBroadcastUpdatePayload<T>;
|
||||
}) => void): RealtimeChannel;
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.BROADCAST}`, filter: {
|
||||
event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.DELETE;
|
||||
}, callback: (payload: {
|
||||
type: `${REALTIME_LISTEN_TYPES.BROADCAST}`;
|
||||
event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.DELETE;
|
||||
payload: RealtimeBroadcastDeletePayload<T>;
|
||||
}) => void): RealtimeChannel;
|
||||
/**
|
||||
* Listen for `system` events on this channel.
|
||||
*
|
||||
* The payload follows the {@link RealtimeSystemPayload} shape. Opt in to the replication-ready
|
||||
* notification with `config.broadcast.replication_ready: true` when creating the channel, then
|
||||
* watch for `payload.status === 'ok'` to know the Postgres replication connection is ready.
|
||||
*
|
||||
* @example Know when the replication connection is ready
|
||||
* ```js
|
||||
* const channel = supabase.channel('room1', {
|
||||
* config: { broadcast: { replication_ready: true } },
|
||||
* })
|
||||
*
|
||||
* channel
|
||||
* .on('postgres_changes', { event: '*', schema: 'public', table: 'messages' }, (payload) => {
|
||||
* console.log('Change received!', payload)
|
||||
* })
|
||||
* .on('system', {}, (payload) => {
|
||||
* if (payload.extension === 'system' && payload.status === 'ok') {
|
||||
* console.log('Replication connection is ready:', payload.message)
|
||||
* }
|
||||
* })
|
||||
* .subscribe()
|
||||
* ```
|
||||
*/
|
||||
on<T extends {
|
||||
[key: string]: any;
|
||||
}>(type: `${REALTIME_LISTEN_TYPES.SYSTEM}`, filter: {}, callback: (payload: any) => void): RealtimeChannel;
|
||||
/**
|
||||
* Sends a broadcast message explicitly via REST API.
|
||||
*
|
||||
* This method always uses the REST API endpoint regardless of WebSocket connection state.
|
||||
* Useful when you want to guarantee REST delivery or when gradually migrating from implicit REST fallback.
|
||||
*
|
||||
* Payloads that are `ArrayBuffer` or `ArrayBufferView` (e.g. `Uint8Array`) are sent as
|
||||
* `application/octet-stream`; all other payloads are JSON-encoded.
|
||||
*
|
||||
* @param event The name of the broadcast event
|
||||
* @param payload Payload to be sent (required)
|
||||
* @param opts Options including timeout
|
||||
* @returns Promise resolving to object with success status, and error details if failed
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
httpSend(event: string, payload: any, opts?: {
|
||||
timeout?: number;
|
||||
}): Promise<{
|
||||
success: true;
|
||||
} | {
|
||||
success: false;
|
||||
status: number;
|
||||
error: string;
|
||||
}>;
|
||||
/**
|
||||
* Sends a message into the channel.
|
||||
*
|
||||
* @param args Arguments to send to channel
|
||||
* @param args.type The type of event to send
|
||||
* @param args.event The name of the event being sent
|
||||
* @param args.payload Payload to be sent
|
||||
* @param opts Options to be used during the send process
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @remarks
|
||||
* - When using REST you don't need to subscribe to the channel
|
||||
* - REST calls are only available from 2.37.0 onwards
|
||||
* - If you create a channel only to send a REST broadcast, remove it from
|
||||
* the client when the send completes
|
||||
*
|
||||
* @example Send a message via websocket
|
||||
* ```js
|
||||
* const channel = supabase.channel('room1')
|
||||
*
|
||||
* channel.subscribe((status) => {
|
||||
* if (status === 'SUBSCRIBED') {
|
||||
* channel.send({
|
||||
* type: 'broadcast',
|
||||
* event: 'cursor-pos',
|
||||
* payload: { x: Math.random(), y: Math.random() },
|
||||
* })
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @exampleResponse Send a message via websocket
|
||||
* ```js
|
||||
* ok | timed out | error
|
||||
* ```
|
||||
*
|
||||
* @example Send a message via REST
|
||||
* ```js
|
||||
* const channel = supabase.channel('room1')
|
||||
*
|
||||
* try {
|
||||
* await channel.httpSend('cursor-pos', { x: Math.random(), y: Math.random() })
|
||||
* } finally {
|
||||
* await supabase.removeChannel(channel)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
send(args: {
|
||||
type: 'broadcast' | 'presence' | 'postgres_changes';
|
||||
event: string;
|
||||
payload?: any;
|
||||
[key: string]: any;
|
||||
}, opts?: {
|
||||
[key: string]: any;
|
||||
}): Promise<RealtimeChannelSendResponse>;
|
||||
/**
|
||||
* Updates the payload that will be sent the next time the channel joins (reconnects).
|
||||
* Useful for rotating access tokens or updating config without re-creating the channel.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
updateJoinPayload(payload: Record<string, any>): void;
|
||||
/**
|
||||
* Leaves the channel.
|
||||
*
|
||||
* Unsubscribes from server events, and instructs channel to terminate on server.
|
||||
* Triggers onClose() hooks.
|
||||
*
|
||||
* To receive leave acknowledgements, use the a `receive` hook to bind to the server ack, ie:
|
||||
* channel.unsubscribe().receive("ok", () => alert("left!") )
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
unsubscribe(timeout?: number): Promise<RealtimeChannelSendResponse>;
|
||||
/**
|
||||
* Destroys and stops related timers.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
teardown(): void;
|
||||
copyBindings(other: RealtimeChannel): void;
|
||||
}
|
||||
//# sourceMappingURL=RealtimeChannel.d.ts.map
|
||||
+1
File diff suppressed because one or more lines are too long
+758
@@ -0,0 +1,758 @@
|
||||
import { CHANNEL_EVENTS, CHANNEL_STATES } from './lib/constants';
|
||||
import RealtimePresence from './RealtimePresence';
|
||||
import * as Transformers from './lib/transformers';
|
||||
import { httpEndpointURL } from './lib/transformers';
|
||||
import { normalizeChannelError } from './lib/normalizeChannelError';
|
||||
import ChannelAdapter from './phoenix/channelAdapter';
|
||||
import { RealtimePostgresFilterBuilder } from './RealtimePostgresFilterBuilder';
|
||||
export { RealtimePostgresFilterBuilder, postgresChangesFilter, } from './RealtimePostgresFilterBuilder';
|
||||
export var REALTIME_POSTGRES_CHANGES_LISTEN_EVENT;
|
||||
(function (REALTIME_POSTGRES_CHANGES_LISTEN_EVENT) {
|
||||
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["ALL"] = "*";
|
||||
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["INSERT"] = "INSERT";
|
||||
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["UPDATE"] = "UPDATE";
|
||||
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT["DELETE"] = "DELETE";
|
||||
})(REALTIME_POSTGRES_CHANGES_LISTEN_EVENT || (REALTIME_POSTGRES_CHANGES_LISTEN_EVENT = {}));
|
||||
export var REALTIME_LISTEN_TYPES;
|
||||
(function (REALTIME_LISTEN_TYPES) {
|
||||
REALTIME_LISTEN_TYPES["BROADCAST"] = "broadcast";
|
||||
REALTIME_LISTEN_TYPES["PRESENCE"] = "presence";
|
||||
REALTIME_LISTEN_TYPES["POSTGRES_CHANGES"] = "postgres_changes";
|
||||
REALTIME_LISTEN_TYPES["SYSTEM"] = "system";
|
||||
})(REALTIME_LISTEN_TYPES || (REALTIME_LISTEN_TYPES = {}));
|
||||
export var REALTIME_SUBSCRIBE_STATES;
|
||||
(function (REALTIME_SUBSCRIBE_STATES) {
|
||||
REALTIME_SUBSCRIBE_STATES["SUBSCRIBED"] = "SUBSCRIBED";
|
||||
REALTIME_SUBSCRIBE_STATES["TIMED_OUT"] = "TIMED_OUT";
|
||||
REALTIME_SUBSCRIBE_STATES["CLOSED"] = "CLOSED";
|
||||
REALTIME_SUBSCRIBE_STATES["CHANNEL_ERROR"] = "CHANNEL_ERROR";
|
||||
})(REALTIME_SUBSCRIBE_STATES || (REALTIME_SUBSCRIBE_STATES = {}));
|
||||
export const REALTIME_CHANNEL_STATES = CHANNEL_STATES;
|
||||
/** A channel is the basic building block of Realtime
|
||||
* and narrows the scope of data flow to subscribed clients.
|
||||
* You can think of a channel as a chatroom where participants are able to see who's online
|
||||
* and send and receive messages.
|
||||
*/
|
||||
export default class RealtimeChannel {
|
||||
get state() {
|
||||
return this.channelAdapter.state;
|
||||
}
|
||||
set state(state) {
|
||||
this.channelAdapter.state = state;
|
||||
}
|
||||
get joinedOnce() {
|
||||
return this.channelAdapter.joinedOnce;
|
||||
}
|
||||
get timeout() {
|
||||
return this.socket.timeout;
|
||||
}
|
||||
get joinPush() {
|
||||
return this.channelAdapter.joinPush;
|
||||
}
|
||||
get rejoinTimer() {
|
||||
return this.channelAdapter.rejoinTimer;
|
||||
}
|
||||
/**
|
||||
* Creates a channel that can broadcast messages, sync presence, and listen to Postgres changes.
|
||||
*
|
||||
* The topic determines which realtime stream you are subscribing to. Config options let you
|
||||
* enable acknowledgement for broadcasts, presence tracking, or private channels.
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Using supabase-js (recommended)
|
||||
* ```ts
|
||||
* import { createClient } from '@supabase/supabase-js'
|
||||
*
|
||||
* const supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key')
|
||||
* const channel = supabase.channel('room1')
|
||||
* channel
|
||||
* .on('broadcast', { event: 'cursor-pos' }, (payload) => console.log(payload))
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @example Standalone import for bundle-sensitive environments
|
||||
* ```ts
|
||||
* import RealtimeClient from '@supabase/realtime-js'
|
||||
*
|
||||
* const client = new RealtimeClient('https://xyzcompany.supabase.co/realtime/v1', {
|
||||
* params: { apikey: 'your-publishable-key' },
|
||||
* })
|
||||
* const channel = new RealtimeChannel('realtime:public:messages', { config: {} }, client)
|
||||
* ```
|
||||
*/
|
||||
constructor(
|
||||
/** Topic name can be any string. */
|
||||
topic, params = { config: {} }, socket) {
|
||||
var _a, _b;
|
||||
this.topic = topic;
|
||||
this.params = params;
|
||||
this.socket = socket;
|
||||
this.bindings = {};
|
||||
this.subTopic = topic.replace(/^realtime:/i, '');
|
||||
this.params.config = Object.assign({
|
||||
broadcast: { ack: false, self: false },
|
||||
presence: { key: '', enabled: false },
|
||||
private: false,
|
||||
}, params.config);
|
||||
this.channelAdapter = new ChannelAdapter(this.socket.socketAdapter, topic, this.params);
|
||||
this.presence = new RealtimePresence(this);
|
||||
this._onClose(() => {
|
||||
this.socket._remove(this);
|
||||
});
|
||||
this._updateFilterTransform();
|
||||
this.broadcastEndpointURL = httpEndpointURL(this.socket.socketAdapter.endPointURL());
|
||||
this.private = this.params.config.private || false;
|
||||
if (!this.private && ((_b = (_a = this.params.config) === null || _a === void 0 ? void 0 : _a.broadcast) === null || _b === void 0 ? void 0 : _b.replay)) {
|
||||
throw new Error(`tried to use replay on public channel '${this.topic}'. It must be a private channel.`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Subscribe registers your client with the server.
|
||||
*
|
||||
* The optional `callback` receives a `status` and, on failure, an `err` argument.
|
||||
* Log the full `err` so its `cause`, `name`, and any structured fields aren't hidden
|
||||
* behind `err.message`.
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Handling errors
|
||||
* ```js
|
||||
* supabase.channel('room1').subscribe((status, err) => {
|
||||
* if (status === 'CHANNEL_ERROR' || status === 'TIMED_OUT') {
|
||||
* // Log the full error: its `cause` often holds the underlying reason.
|
||||
* console.error(status, err)
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
subscribe(callback, timeout = this.timeout) {
|
||||
var _a, _b, _c;
|
||||
if (!this.socket.isConnected()) {
|
||||
this.socket.connect();
|
||||
}
|
||||
if (this.channelAdapter.isClosed()) {
|
||||
const { config: { broadcast, presence, private: isPrivate }, } = this.params;
|
||||
const postgres_changes = (_b = (_a = this.bindings.postgres_changes) === null || _a === void 0 ? void 0 : _a.map((r) => r.filter)) !== null && _b !== void 0 ? _b : [];
|
||||
const presence_enabled = (!!this.bindings[REALTIME_LISTEN_TYPES.PRESENCE] &&
|
||||
this.bindings[REALTIME_LISTEN_TYPES.PRESENCE].length > 0) ||
|
||||
((_c = this.params.config.presence) === null || _c === void 0 ? void 0 : _c.enabled) === true;
|
||||
const accessTokenPayload = {};
|
||||
const config = {
|
||||
broadcast,
|
||||
presence: Object.assign(Object.assign({}, presence), { enabled: presence_enabled }),
|
||||
postgres_changes,
|
||||
private: isPrivate,
|
||||
};
|
||||
if (this.socket.accessTokenValue) {
|
||||
accessTokenPayload.access_token = this.socket.accessTokenValue;
|
||||
}
|
||||
this._onError((reason) => {
|
||||
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, normalizeChannelError(reason));
|
||||
});
|
||||
this._onClose(() => callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CLOSED));
|
||||
this.updateJoinPayload(Object.assign({ config }, accessTokenPayload));
|
||||
this._updateFilterMessage();
|
||||
this.channelAdapter
|
||||
.subscribe(timeout)
|
||||
.receive('ok', async ({ postgres_changes }) => {
|
||||
// Only refresh auth if using callback-based tokens
|
||||
if (!this.socket._isManualToken()) {
|
||||
this.socket.setAuth();
|
||||
}
|
||||
if (postgres_changes === undefined) {
|
||||
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED);
|
||||
return;
|
||||
}
|
||||
this._updatePostgresBindings(postgres_changes, callback);
|
||||
})
|
||||
.receive('error', (error) => {
|
||||
this.state = CHANNEL_STATES.errored;
|
||||
const message = Object.values(error).join(', ') || 'error';
|
||||
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, new Error(message, { cause: error }));
|
||||
})
|
||||
.receive('timeout', () => {
|
||||
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.TIMED_OUT);
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
_updatePostgresBindings(postgres_changes, callback) {
|
||||
var _a;
|
||||
const clientPostgresBindings = this.bindings.postgres_changes;
|
||||
const bindingsLen = (_a = clientPostgresBindings === null || clientPostgresBindings === void 0 ? void 0 : clientPostgresBindings.length) !== null && _a !== void 0 ? _a : 0;
|
||||
const newPostgresBindings = [];
|
||||
for (let i = 0; i < bindingsLen; i++) {
|
||||
const clientPostgresBinding = clientPostgresBindings[i];
|
||||
const { filter: { event, schema, table, filter }, } = clientPostgresBinding;
|
||||
const serverPostgresFilter = postgres_changes && postgres_changes[i];
|
||||
if (serverPostgresFilter &&
|
||||
serverPostgresFilter.event === event &&
|
||||
RealtimeChannel.isFilterValueEqual(serverPostgresFilter.schema, schema) &&
|
||||
RealtimeChannel.isFilterValueEqual(serverPostgresFilter.table, table) &&
|
||||
RealtimeChannel.isFilterValueEqual(serverPostgresFilter.filter, filter)) {
|
||||
newPostgresBindings.push(Object.assign(Object.assign({}, clientPostgresBinding), { id: serverPostgresFilter.id }));
|
||||
}
|
||||
else {
|
||||
this.unsubscribe();
|
||||
this.state = CHANNEL_STATES.errored;
|
||||
callback === null || callback === void 0 ? void 0 : callback(REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR, new Error('mismatch between server and client bindings for postgres changes'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.bindings.postgres_changes = newPostgresBindings;
|
||||
if (this.state != CHANNEL_STATES.errored && callback) {
|
||||
callback(REALTIME_SUBSCRIBE_STATES.SUBSCRIBED);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns the current presence state for this channel.
|
||||
*
|
||||
* The shape is a map keyed by presence key (for example a user id) where each entry contains the
|
||||
* tracked metadata for that user.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
presenceState() {
|
||||
return this.presence.state;
|
||||
}
|
||||
/**
|
||||
* Sends the supplied payload to the presence tracker so other subscribers can see that this
|
||||
* client is online. Use `untrack` to stop broadcasting presence for the same key.
|
||||
*
|
||||
* Tracking makes this client visible to other subscribers immediately, regardless of this
|
||||
* channel's `config.presence.enabled` setting or whether it has a `presence` listener — that
|
||||
* flag only affects whether *this* client receives presence updates from others (and, on
|
||||
* RLS-protected channels, whether it's authorized to do so).
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
async track(payload, opts = {}) {
|
||||
return await this.send({
|
||||
type: 'presence',
|
||||
event: 'track',
|
||||
payload,
|
||||
}, opts);
|
||||
}
|
||||
/**
|
||||
* Removes the current presence state for this client.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
async untrack(opts = {}) {
|
||||
return await this.send({
|
||||
type: 'presence',
|
||||
event: 'untrack',
|
||||
}, opts);
|
||||
}
|
||||
/**
|
||||
* Listen to realtime events on this channel.
|
||||
* @category Realtime
|
||||
*
|
||||
* @remarks
|
||||
* - By default, Broadcast and Presence are enabled for all projects.
|
||||
* - By default, listening to database changes is disabled for new projects due to database performance and security concerns. You can turn it on by managing Realtime's [replication](/docs/guides/api#realtime-api-overview).
|
||||
* - You can receive the "previous" data for updates and deletes by setting the table's `REPLICA IDENTITY` to `FULL` (e.g., `ALTER TABLE your_table REPLICA IDENTITY FULL;`).
|
||||
* - Row level security is not applied to delete statements. When RLS is enabled and replica identity is set to full, only the primary key is sent to clients.
|
||||
*
|
||||
* @example Listen to broadcast messages
|
||||
* ```js
|
||||
* const channel = supabase.channel("room1")
|
||||
*
|
||||
* channel.on("broadcast", { event: "cursor-pos" }, (payload) => {
|
||||
* console.log("Cursor position received!", payload);
|
||||
* }).subscribe((status) => {
|
||||
* if (status === "SUBSCRIBED") {
|
||||
* channel.send({
|
||||
* type: "broadcast",
|
||||
* event: "cursor-pos",
|
||||
* payload: { x: Math.random(), y: Math.random() },
|
||||
* });
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @example Listen to presence sync
|
||||
* ```js
|
||||
* const channel = supabase.channel('room1')
|
||||
* channel
|
||||
* .on('presence', { event: 'sync' }, () => {
|
||||
* console.log('Synced presence state: ', channel.presenceState())
|
||||
* })
|
||||
* .subscribe(async (status) => {
|
||||
* if (status === 'SUBSCRIBED') {
|
||||
* await channel.track({ online_at: new Date().toISOString() })
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @example Listen to presence join
|
||||
* ```js
|
||||
* const channel = supabase.channel('room1')
|
||||
* channel
|
||||
* .on('presence', { event: 'join' }, ({ newPresences }) => {
|
||||
* console.log('Newly joined presences: ', newPresences)
|
||||
* })
|
||||
* .subscribe(async (status) => {
|
||||
* if (status === 'SUBSCRIBED') {
|
||||
* await channel.track({ online_at: new Date().toISOString() })
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @example Listen to presence leave
|
||||
* ```js
|
||||
* const channel = supabase.channel('room1')
|
||||
* channel
|
||||
* .on('presence', { event: 'leave' }, ({ leftPresences }) => {
|
||||
* console.log('Newly left presences: ', leftPresences)
|
||||
* })
|
||||
* .subscribe(async (status) => {
|
||||
* if (status === 'SUBSCRIBED') {
|
||||
* await channel.track({ online_at: new Date().toISOString() })
|
||||
* await channel.untrack()
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @example Listen to all database changes
|
||||
* ```js
|
||||
* supabase
|
||||
* .channel('room1')
|
||||
* .on('postgres_changes', { event: '*', schema: '*' }, payload => {
|
||||
* console.log('Change received!', payload)
|
||||
* })
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @example Listen to a specific table
|
||||
* ```js
|
||||
* supabase
|
||||
* .channel('room1')
|
||||
* .on('postgres_changes', { event: '*', schema: 'public', table: 'countries' }, payload => {
|
||||
* console.log('Change received!', payload)
|
||||
* })
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @example Listen to inserts
|
||||
* ```js
|
||||
* supabase
|
||||
* .channel('room1')
|
||||
* .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'countries' }, payload => {
|
||||
* console.log('Change received!', payload)
|
||||
* })
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @exampleDescription Listen to updates
|
||||
* By default, Supabase will send only the updated record. If you want to receive the previous values as well you can
|
||||
* enable full replication for the table you are listening to:
|
||||
*
|
||||
* ```sql
|
||||
* alter table "your_table" replica identity full;
|
||||
* ```
|
||||
*
|
||||
* @example Listen to updates
|
||||
* ```js
|
||||
* supabase
|
||||
* .channel('room1')
|
||||
* .on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'countries' }, payload => {
|
||||
* console.log('Change received!', payload)
|
||||
* })
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @exampleDescription Listen to deletes
|
||||
* By default, Supabase does not send deleted records. If you want to receive the deleted record you can
|
||||
* enable full replication for the table you are listening to:
|
||||
*
|
||||
* ```sql
|
||||
* alter table "your_table" replica identity full;
|
||||
* ```
|
||||
*
|
||||
* @example Listen to deletes
|
||||
* ```js
|
||||
* supabase
|
||||
* .channel('room1')
|
||||
* .on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'countries' }, payload => {
|
||||
* console.log('Change received!', payload)
|
||||
* })
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @exampleDescription Listen to multiple events
|
||||
* You can chain listeners if you want to listen to multiple events for each table.
|
||||
*
|
||||
* @example Listen to multiple events
|
||||
* ```js
|
||||
* supabase
|
||||
* .channel('room1')
|
||||
* .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'countries' }, handleRecordInserted)
|
||||
* .on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'countries' }, handleRecordDeleted)
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @exampleDescription Listen to row level changes
|
||||
* You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match.
|
||||
*
|
||||
* @example Listen to row level changes
|
||||
* ```js
|
||||
* supabase
|
||||
* .channel('room1')
|
||||
* .on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'countries', filter: 'id=eq.200' }, handleRecordUpdated)
|
||||
* .subscribe()
|
||||
* ```
|
||||
*/
|
||||
on(type, filter, callback) {
|
||||
const stateCheck = this.channelAdapter.isJoined() || this.channelAdapter.isJoining();
|
||||
const typeCheck = type === REALTIME_LISTEN_TYPES.PRESENCE || type === REALTIME_LISTEN_TYPES.POSTGRES_CHANGES;
|
||||
if (stateCheck && typeCheck) {
|
||||
this.socket.log('channel', `cannot add \`${type}\` callbacks for ${this.topic} after \`subscribe()\`.`);
|
||||
throw new Error(`cannot add \`${type}\` callbacks for ${this.topic} after \`subscribe()\`.`);
|
||||
}
|
||||
return this._on(type, filter, callback);
|
||||
}
|
||||
/**
|
||||
* Sends a broadcast message explicitly via REST API.
|
||||
*
|
||||
* This method always uses the REST API endpoint regardless of WebSocket connection state.
|
||||
* Useful when you want to guarantee REST delivery or when gradually migrating from implicit REST fallback.
|
||||
*
|
||||
* Payloads that are `ArrayBuffer` or `ArrayBufferView` (e.g. `Uint8Array`) are sent as
|
||||
* `application/octet-stream`; all other payloads are JSON-encoded.
|
||||
*
|
||||
* @param event The name of the broadcast event
|
||||
* @param payload Payload to be sent (required)
|
||||
* @param opts Options including timeout
|
||||
* @returns Promise resolving to object with success status, and error details if failed
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
async httpSend(event, payload, opts = {}) {
|
||||
var _a;
|
||||
if (payload === undefined || payload === null) {
|
||||
return Promise.reject(new Error('Payload is required for httpSend()'));
|
||||
}
|
||||
const isBinary = payload instanceof ArrayBuffer || ArrayBuffer.isView(payload);
|
||||
const headers = {
|
||||
apikey: this.socket.apiKey ? this.socket.apiKey : '',
|
||||
'Content-Type': isBinary ? 'application/octet-stream' : 'application/json',
|
||||
};
|
||||
if (this.socket.accessTokenValue) {
|
||||
headers['Authorization'] = `Bearer ${this.socket.accessTokenValue}`;
|
||||
}
|
||||
const url = new URL(this.broadcastEndpointURL);
|
||||
url.pathname += `/${encodeURIComponent(this.subTopic)}/events/${encodeURIComponent(event)}`;
|
||||
if (this.private) {
|
||||
url.searchParams.set('private', 'true');
|
||||
}
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: isBinary ? payload : JSON.stringify(payload),
|
||||
};
|
||||
const response = await this._fetchWithTimeout(url.toString(), options, (_a = opts.timeout) !== null && _a !== void 0 ? _a : this.timeout);
|
||||
if (response.status === 202) {
|
||||
return { success: true };
|
||||
}
|
||||
if (response.status === 404) {
|
||||
return Promise.reject(new Error('httpSend() requires Realtime server v2.97.0 or newer; the endpoint returned 404. ' +
|
||||
'Update your Supabase CLI to a recent version, or upgrade the Realtime server in your self-hosted setup. ' +
|
||||
'See https://github.com/supabase/supabase-js/blob/master/packages/core/realtime-js/migrations/httpsend-server-version.md'));
|
||||
}
|
||||
let errorMessage = response.statusText;
|
||||
try {
|
||||
const errorBody = await response.json();
|
||||
errorMessage = errorBody.error || errorBody.message || errorMessage;
|
||||
}
|
||||
catch (_b) { }
|
||||
return Promise.reject(new Error(errorMessage));
|
||||
}
|
||||
/**
|
||||
* Sends a message into the channel.
|
||||
*
|
||||
* @param args Arguments to send to channel
|
||||
* @param args.type The type of event to send
|
||||
* @param args.event The name of the event being sent
|
||||
* @param args.payload Payload to be sent
|
||||
* @param opts Options to be used during the send process
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @remarks
|
||||
* - When using REST you don't need to subscribe to the channel
|
||||
* - REST calls are only available from 2.37.0 onwards
|
||||
* - If you create a channel only to send a REST broadcast, remove it from
|
||||
* the client when the send completes
|
||||
*
|
||||
* @example Send a message via websocket
|
||||
* ```js
|
||||
* const channel = supabase.channel('room1')
|
||||
*
|
||||
* channel.subscribe((status) => {
|
||||
* if (status === 'SUBSCRIBED') {
|
||||
* channel.send({
|
||||
* type: 'broadcast',
|
||||
* event: 'cursor-pos',
|
||||
* payload: { x: Math.random(), y: Math.random() },
|
||||
* })
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @exampleResponse Send a message via websocket
|
||||
* ```js
|
||||
* ok | timed out | error
|
||||
* ```
|
||||
*
|
||||
* @example Send a message via REST
|
||||
* ```js
|
||||
* const channel = supabase.channel('room1')
|
||||
*
|
||||
* try {
|
||||
* await channel.httpSend('cursor-pos', { x: Math.random(), y: Math.random() })
|
||||
* } finally {
|
||||
* await supabase.removeChannel(channel)
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
async send(args, opts = {}) {
|
||||
var _a, _b;
|
||||
if (!this.channelAdapter.canPush() && args.type === 'broadcast') {
|
||||
console.warn('Realtime send() is automatically falling back to REST API. ' +
|
||||
'This behavior will be deprecated in the future. ' +
|
||||
'Please use httpSend() explicitly for REST delivery.');
|
||||
const { event, payload: endpoint_payload } = args;
|
||||
const headers = {
|
||||
apikey: this.socket.apiKey ? this.socket.apiKey : '',
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if (this.socket.accessTokenValue) {
|
||||
headers['Authorization'] = `Bearer ${this.socket.accessTokenValue}`;
|
||||
}
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
messages: [
|
||||
{
|
||||
topic: this.subTopic,
|
||||
event,
|
||||
payload: endpoint_payload,
|
||||
private: this.private,
|
||||
},
|
||||
],
|
||||
}),
|
||||
};
|
||||
try {
|
||||
const response = await this._fetchWithTimeout(this.broadcastEndpointURL, options, (_a = opts.timeout) !== null && _a !== void 0 ? _a : this.timeout);
|
||||
await ((_b = response.body) === null || _b === void 0 ? void 0 : _b.cancel());
|
||||
return response.ok ? 'ok' : 'error';
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
return 'timed out';
|
||||
}
|
||||
else {
|
||||
return 'error';
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return new Promise((resolve) => {
|
||||
var _a, _b, _c;
|
||||
const push = this.channelAdapter.push(args.type, args, opts.timeout || this.timeout);
|
||||
if (args.type === 'broadcast' && !((_c = (_b = (_a = this.params) === null || _a === void 0 ? void 0 : _a.config) === null || _b === void 0 ? void 0 : _b.broadcast) === null || _c === void 0 ? void 0 : _c.ack)) {
|
||||
resolve('ok');
|
||||
}
|
||||
push.receive('ok', () => resolve('ok'));
|
||||
push.receive('error', () => resolve('error'));
|
||||
push.receive('timeout', () => resolve('timed out'));
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Updates the payload that will be sent the next time the channel joins (reconnects).
|
||||
* Useful for rotating access tokens or updating config without re-creating the channel.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
updateJoinPayload(payload) {
|
||||
this.channelAdapter.updateJoinPayload(payload);
|
||||
}
|
||||
/**
|
||||
* Leaves the channel.
|
||||
*
|
||||
* Unsubscribes from server events, and instructs channel to terminate on server.
|
||||
* Triggers onClose() hooks.
|
||||
*
|
||||
* To receive leave acknowledgements, use the a `receive` hook to bind to the server ack, ie:
|
||||
* channel.unsubscribe().receive("ok", () => alert("left!") )
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
async unsubscribe(timeout = this.timeout) {
|
||||
return new Promise((resolve) => {
|
||||
this.channelAdapter
|
||||
.unsubscribe(timeout)
|
||||
.receive('ok', () => resolve('ok'))
|
||||
.receive('timeout', () => resolve('timed out'))
|
||||
.receive('error', () => resolve('error'));
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Destroys and stops related timers.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
teardown() {
|
||||
this.channelAdapter.teardown();
|
||||
}
|
||||
/** @internal */
|
||||
async _fetchWithTimeout(url, options, timeout) {
|
||||
const controller = new AbortController();
|
||||
const id = setTimeout(() => controller.abort(), timeout);
|
||||
const response = await this.socket.fetch(url, Object.assign(Object.assign({}, options), { signal: controller.signal }));
|
||||
clearTimeout(id);
|
||||
return response;
|
||||
}
|
||||
/** @internal */
|
||||
_on(type, filter, callback) {
|
||||
const typeLower = type.toLocaleLowerCase();
|
||||
// Serialize a postgres_changes filter builder into its string form so the
|
||||
// rest of the pipeline (join payload, server binding match) sees a string.
|
||||
// Duck-type `build()` in addition to `instanceof` so a builder constructed
|
||||
// against a duplicate copy of the package (separate module realm) still works.
|
||||
const filterValue = filter === null || filter === void 0 ? void 0 : filter.filter;
|
||||
if (filterValue instanceof RealtimePostgresFilterBuilder ||
|
||||
(typeof filterValue === 'object' &&
|
||||
filterValue !== null &&
|
||||
typeof filterValue.build === 'function')) {
|
||||
filter = Object.assign(Object.assign({}, filter), { filter: filterValue.build() });
|
||||
}
|
||||
const ref = this.channelAdapter.on(type, callback);
|
||||
const binding = {
|
||||
type: typeLower,
|
||||
filter: filter,
|
||||
callback: callback,
|
||||
ref: ref,
|
||||
};
|
||||
if (this.bindings[typeLower]) {
|
||||
this.bindings[typeLower].push(binding);
|
||||
}
|
||||
else {
|
||||
this.bindings[typeLower] = [binding];
|
||||
}
|
||||
this._updateFilterMessage();
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Registers a callback that will be executed when the channel closes.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_onClose(callback) {
|
||||
this.channelAdapter.onClose(callback);
|
||||
}
|
||||
/**
|
||||
* Registers a callback that will be executed when the channel encounteres an error.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_onError(callback) {
|
||||
this.channelAdapter.onError(callback);
|
||||
}
|
||||
/** @internal */
|
||||
_updateFilterMessage() {
|
||||
this.channelAdapter.updateFilterBindings((binding, payload, ref) => {
|
||||
var _a, _b, _c, _d, _e, _f, _g;
|
||||
const typeLower = binding.event.toLocaleLowerCase();
|
||||
if (this._notThisChannelEvent(typeLower, ref)) {
|
||||
return false;
|
||||
}
|
||||
const bind = (_a = this.bindings[typeLower]) === null || _a === void 0 ? void 0 : _a.find((bind) => bind.ref === binding.ref);
|
||||
if (!bind) {
|
||||
return true;
|
||||
}
|
||||
if (['broadcast', 'presence', 'postgres_changes'].includes(typeLower)) {
|
||||
if ('id' in bind) {
|
||||
const bindId = bind.id;
|
||||
const bindEvent = (_b = bind.filter) === null || _b === void 0 ? void 0 : _b.event;
|
||||
return (bindId &&
|
||||
((_c = payload.ids) === null || _c === void 0 ? void 0 : _c.includes(bindId)) &&
|
||||
(bindEvent === '*' ||
|
||||
(bindEvent === null || bindEvent === void 0 ? void 0 : bindEvent.toLocaleLowerCase()) === ((_d = payload.data) === null || _d === void 0 ? void 0 : _d.type.toLocaleLowerCase())));
|
||||
}
|
||||
else {
|
||||
const bindEvent = (_f = (_e = bind === null || bind === void 0 ? void 0 : bind.filter) === null || _e === void 0 ? void 0 : _e.event) === null || _f === void 0 ? void 0 : _f.toLocaleLowerCase();
|
||||
return bindEvent === '*' || bindEvent === ((_g = payload === null || payload === void 0 ? void 0 : payload.event) === null || _g === void 0 ? void 0 : _g.toLocaleLowerCase());
|
||||
}
|
||||
}
|
||||
else {
|
||||
return bind.type.toLocaleLowerCase() === typeLower;
|
||||
}
|
||||
});
|
||||
}
|
||||
/** @internal */
|
||||
_notThisChannelEvent(event, ref) {
|
||||
const { close, error, leave, join } = CHANNEL_EVENTS;
|
||||
const events = [close, error, leave, join];
|
||||
return ref && events.includes(event) && ref !== this.joinPush.ref;
|
||||
}
|
||||
/** @internal */
|
||||
_updateFilterTransform() {
|
||||
this.channelAdapter.updatePayloadTransform((event, payload, ref) => {
|
||||
if (typeof payload === 'object' && 'ids' in payload) {
|
||||
const postgresChanges = payload.data;
|
||||
const { schema, table, commit_timestamp, type, errors } = postgresChanges;
|
||||
const enrichedPayload = {
|
||||
schema: schema,
|
||||
table: table,
|
||||
commit_timestamp: commit_timestamp,
|
||||
eventType: type,
|
||||
new: {},
|
||||
old: {},
|
||||
errors: errors,
|
||||
};
|
||||
return Object.assign(Object.assign({}, enrichedPayload), this._getPayloadRecords(postgresChanges));
|
||||
}
|
||||
return payload;
|
||||
});
|
||||
}
|
||||
copyBindings(other) {
|
||||
if (this.joinedOnce) {
|
||||
throw new Error('cannot copy bindings into joined channel');
|
||||
}
|
||||
for (const kind in other.bindings) {
|
||||
for (const binding of other.bindings[kind]) {
|
||||
this._on(binding.type, binding.filter, binding.callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Compares two optional filter values for equality.
|
||||
* Treats undefined, null, and empty string as equivalent empty values.
|
||||
* @internal
|
||||
*/
|
||||
static isFilterValueEqual(serverValue, clientValue) {
|
||||
const normalizedServer = serverValue !== null && serverValue !== void 0 ? serverValue : undefined;
|
||||
const normalizedClient = clientValue !== null && clientValue !== void 0 ? clientValue : undefined;
|
||||
return normalizedServer === normalizedClient;
|
||||
}
|
||||
/** @internal */
|
||||
_getPayloadRecords(payload) {
|
||||
const records = {
|
||||
new: {},
|
||||
old: {},
|
||||
};
|
||||
if (payload.type === 'INSERT' || payload.type === 'UPDATE') {
|
||||
records.new = Transformers.convertChangeData(payload.columns, payload.record);
|
||||
}
|
||||
if (payload.type === 'UPDATE' || payload.type === 'DELETE') {
|
||||
records.old = Transformers.convertChangeData(payload.columns, payload.old_record);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=RealtimeChannel.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+274
@@ -0,0 +1,274 @@
|
||||
import { WebSocketLike } from './lib/websocket-factory';
|
||||
import Serializer from './lib/serializer';
|
||||
import RealtimeChannel from './RealtimeChannel';
|
||||
import type { RealtimeChannelOptions } from './RealtimeChannel';
|
||||
import type { HeartbeatCallback, Encode, Decode, Timer, Vsn } from './phoenix/types';
|
||||
type Fetch = typeof fetch;
|
||||
export type LogLevel = 'info' | 'warn' | 'error' | (string & {});
|
||||
export type RealtimeMessage = {
|
||||
topic: string;
|
||||
event: string;
|
||||
payload: any;
|
||||
ref: string;
|
||||
join_ref?: string;
|
||||
};
|
||||
export type RealtimeRemoveChannelResponse = 'ok' | 'timed out' | 'error' | (string & {});
|
||||
export type HeartbeatStatus = 'sent' | 'ok' | 'error' | 'timeout' | 'disconnected' | (string & {});
|
||||
export type HeartbeatTimer = ReturnType<typeof setTimeout> | undefined;
|
||||
/**
|
||||
* Minimal WebSocket constructor interface that RealtimeClient can work with.
|
||||
* Supply a compatible implementation (native WebSocket, `ws`, etc) when running outside the browser.
|
||||
*/
|
||||
export interface WebSocketLikeConstructor {
|
||||
new (address: string | URL, subprotocols?: string | string[] | undefined): WebSocketLike;
|
||||
[key: string]: any;
|
||||
}
|
||||
export type RealtimeClientOptions = {
|
||||
transport?: WebSocketLikeConstructor;
|
||||
timeout?: number;
|
||||
heartbeatIntervalMs?: number;
|
||||
heartbeatCallback?: (status: HeartbeatStatus, latency?: number) => void;
|
||||
vsn?: string;
|
||||
logger?: (kind: string, msg: string, data?: any) => void;
|
||||
encode?: Encode<void>;
|
||||
decode?: Decode<void>;
|
||||
reconnectAfterMs?: (tries: number) => number;
|
||||
headers?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
params?: {
|
||||
[key: string]: any;
|
||||
};
|
||||
log_level?: LogLevel;
|
||||
logLevel?: LogLevel;
|
||||
fetch?: Fetch;
|
||||
worker?: boolean;
|
||||
workerUrl?: string;
|
||||
accessToken?: () => Promise<string | null>;
|
||||
disconnectOnEmptyChannelsAfterMs?: number;
|
||||
/**
|
||||
* Storage compatible object used by the underlying socket for longpoll fallback history.
|
||||
* Provide a custom implementation in environments where reading `globalThis.sessionStorage`
|
||||
* throws (sandboxed iframes, in-app webviews, "block third-party storage" privacy modes).
|
||||
* Defaults to `globalThis.sessionStorage` when accessible, otherwise an in-memory store.
|
||||
*/
|
||||
sessionStorage?: Storage;
|
||||
};
|
||||
export default class RealtimeClient {
|
||||
channels: RealtimeChannel[];
|
||||
accessTokenValue: string | null;
|
||||
accessToken: (() => Promise<string | null>) | null;
|
||||
apiKey: string | null;
|
||||
httpEndpoint: string;
|
||||
/** @deprecated headers cannot be set on websocket connections */
|
||||
headers?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
params?: {
|
||||
[key: string]: string;
|
||||
};
|
||||
ref: number;
|
||||
logLevel?: LogLevel;
|
||||
fetch: Fetch;
|
||||
worker?: boolean;
|
||||
workerUrl?: string;
|
||||
workerRef?: Worker;
|
||||
serializer: Serializer;
|
||||
get endPoint(): string;
|
||||
get timeout(): number;
|
||||
get transport(): WebSocketLikeConstructor;
|
||||
get heartbeatCallback(): HeartbeatCallback;
|
||||
get heartbeatIntervalMs(): number;
|
||||
get heartbeatTimer(): HeartbeatTimer;
|
||||
get pendingHeartbeatRef(): string | null;
|
||||
get reconnectTimer(): Timer;
|
||||
get vsn(): Vsn;
|
||||
get encode(): Encode<void>;
|
||||
get decode(): Decode<void>;
|
||||
get reconnectAfterMs(): (tries: number) => number;
|
||||
get sendBuffer(): (() => void)[];
|
||||
get stateChangeCallbacks(): {
|
||||
open: [string, Function][];
|
||||
close: [string, Function][];
|
||||
error: [string, Function][];
|
||||
message: [string, Function][];
|
||||
};
|
||||
private _manuallySetToken;
|
||||
private _authPromise;
|
||||
private _workerHeartbeatTimer;
|
||||
private _pendingWorkerHeartbeatRef;
|
||||
private _pendingDisconnectTimer;
|
||||
private _disconnectOnEmptyChannelsAfterMs;
|
||||
/**
|
||||
* Initializes the Socket.
|
||||
*
|
||||
* @param endPoint The string WebSocket endpoint, ie, "ws://example.com/socket", "wss://example.com", "/socket" (inherited host & protocol)
|
||||
* @param options.transport The Websocket Transport, for example WebSocket. This can be a custom implementation
|
||||
* @param options.timeout The default timeout in milliseconds to trigger push timeouts.
|
||||
* @param options.params The optional params to pass when connecting.
|
||||
* @param options.headers Deprecated: headers cannot be set on websocket connections and this option will be removed in the future.
|
||||
* @param options.heartbeatIntervalMs The millisec interval to send a heartbeat message.
|
||||
* @param options.heartbeatCallback The optional function to handle heartbeat status and latency.
|
||||
* @param options.logger The optional function for specialized logging, ie: logger: (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }
|
||||
* @param options.logLevel Sets the log level for Realtime
|
||||
* @param options.encode The function to encode outgoing messages. Defaults to JSON: (payload, callback) => callback(JSON.stringify(payload))
|
||||
* @param options.decode The function to decode incoming messages. Defaults to Serializer's decode.
|
||||
* @param options.reconnectAfterMs he optional function that returns the millsec reconnect interval. Defaults to stepped backoff off.
|
||||
* @param options.worker Use Web Worker to set a side flow. Defaults to false.
|
||||
* @param options.workerUrl The URL of the worker script. Defaults to https://realtime.supabase.com/worker.js that includes a heartbeat event call to keep the connection alive.
|
||||
* @param options.vsn The protocol version to use when connecting. Supported versions are "1.0.0" and "2.0.0". Defaults to "2.0.0".
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Using supabase-js (recommended)
|
||||
* ```ts
|
||||
* import { createClient } from '@supabase/supabase-js'
|
||||
*
|
||||
* const supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key')
|
||||
* const channel = supabase.channel('room1')
|
||||
* channel
|
||||
* .on('broadcast', { event: 'cursor-pos' }, (payload) => console.log(payload))
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @example Standalone import for bundle-sensitive environments
|
||||
* ```ts
|
||||
* import RealtimeClient from '@supabase/realtime-js'
|
||||
*
|
||||
* const client = new RealtimeClient('https://xyzcompany.supabase.co/realtime/v1', {
|
||||
* params: { apikey: 'your-publishable-key' },
|
||||
* })
|
||||
* client.connect()
|
||||
* ```
|
||||
*/
|
||||
constructor(endPoint: string, options?: RealtimeClientOptions);
|
||||
/**
|
||||
* Connects the socket, unless already connected.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
connect(): void;
|
||||
/**
|
||||
* Returns the URL of the websocket.
|
||||
* @returns string The URL of the websocket.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
endpointURL(): string;
|
||||
/**
|
||||
* Disconnects the socket.
|
||||
*
|
||||
* @param code A numeric status code to send on disconnect.
|
||||
* @param reason A custom reason for the disconnect.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
disconnect(code?: number, reason?: string): Promise<"ok" | "timeout">;
|
||||
/**
|
||||
* Returns all created channels
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
getChannels(): RealtimeChannel[];
|
||||
/**
|
||||
* Unsubscribes, removes and tears down a single channel
|
||||
* @param channel A RealtimeChannel instance
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
removeChannel(channel: RealtimeChannel): Promise<RealtimeRemoveChannelResponse>;
|
||||
/**
|
||||
* Unsubscribes, removes and tears down all channels
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
removeAllChannels(): Promise<RealtimeRemoveChannelResponse[]>;
|
||||
/**
|
||||
* Logs the message.
|
||||
*
|
||||
* For customized logging, `this.logger` can be overridden in Client constructor.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
log(kind: string, msg: string, data?: any): void;
|
||||
/**
|
||||
* Returns the current state of the socket.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
connectionState(): import("./lib/constants").ConnectionState;
|
||||
/**
|
||||
* Returns `true` is the connection is open.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
isConnected(): boolean;
|
||||
/**
|
||||
* Returns `true` if the connection is currently connecting.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
isConnecting(): boolean;
|
||||
/**
|
||||
* Returns `true` if the connection is currently disconnecting.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
isDisconnecting(): boolean;
|
||||
/**
|
||||
* Creates (or reuses) a {@link RealtimeChannel} for the provided topic.
|
||||
*
|
||||
* Topics are automatically prefixed with `realtime:` to match the Realtime service.
|
||||
* If a channel with the same topic already exists it will be returned instead of creating
|
||||
* a duplicate connection.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
channel(topic: string, params?: RealtimeChannelOptions): RealtimeChannel;
|
||||
/**
|
||||
* Push out a message if the socket is connected.
|
||||
*
|
||||
* If the socket is not connected, the message gets enqueued within a local buffer, and sent out when a connection is next established.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
push(data: RealtimeMessage): void;
|
||||
/**
|
||||
* Sets the JWT access token used for channel subscription authorization and Realtime RLS.
|
||||
*
|
||||
* If param is null it will use the `accessToken` callback function or the token set on the client.
|
||||
*
|
||||
* On callback used, it will set the value of the token internal to the client.
|
||||
*
|
||||
* When a token is explicitly provided, it will be preserved across channel operations
|
||||
* (including removeChannel and resubscribe). The `accessToken` callback will not be
|
||||
* invoked until `setAuth()` is called without arguments.
|
||||
*
|
||||
* @param token A JWT string to override the token set on the client.
|
||||
*
|
||||
* @example Setting the authorization header
|
||||
* // Use a manual token (preserved across resubscribes, ignores accessToken callback)
|
||||
* client.realtime.setAuth('my-custom-jwt')
|
||||
*
|
||||
* // Switch back to using the accessToken callback
|
||||
* client.realtime.setAuth()
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
setAuth(token?: string | null): Promise<void>;
|
||||
/**
|
||||
* Sends a heartbeat message if the socket is connected.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
sendHeartbeat(): Promise<void>;
|
||||
/**
|
||||
* Sets a callback that receives lifecycle events for internal heartbeat messages.
|
||||
* Useful for instrumenting connection health (e.g. sent/ok/timeout).
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
onHeartbeat(callback: HeartbeatCallback): void;
|
||||
}
|
||||
export {};
|
||||
//# sourceMappingURL=RealtimeClient.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RealtimeClient.d.ts","sourceRoot":"","sources":["../../src/RealtimeClient.ts"],"names":[],"mappings":"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"}
|
||||
+681
@@ -0,0 +1,681 @@
|
||||
import WebSocketFactory from './lib/websocket-factory';
|
||||
import { CHANNEL_EVENTS, CONNECTION_STATE, DEFAULT_VERSION, DEFAULT_TIMEOUT, DEFAULT_VSN, VSN_1_0_0, VSN_2_0_0, } from './lib/constants';
|
||||
import Serializer from './lib/serializer';
|
||||
import { httpEndpointURL } from './lib/transformers';
|
||||
import RealtimeChannel from './RealtimeChannel';
|
||||
import SocketAdapter from './phoenix/socketAdapter';
|
||||
// Connection-related constants
|
||||
const CONNECTION_TIMEOUTS = {
|
||||
HEARTBEAT_INTERVAL: 25000,
|
||||
RECONNECT_DELAY: 10,
|
||||
HEARTBEAT_TIMEOUT_FALLBACK: 100,
|
||||
};
|
||||
const RECONNECT_INTERVALS = [1000, 2000, 5000, 10000];
|
||||
const DEFAULT_RECONNECT_FALLBACK = 10000;
|
||||
function createMemorySessionStorage() {
|
||||
const store = new Map();
|
||||
return {
|
||||
get length() {
|
||||
return store.size;
|
||||
},
|
||||
clear() {
|
||||
store.clear();
|
||||
},
|
||||
getItem(key) {
|
||||
return store.has(key) ? store.get(key) : null;
|
||||
},
|
||||
key(index) {
|
||||
var _a;
|
||||
return (_a = Array.from(store.keys())[index]) !== null && _a !== void 0 ? _a : null;
|
||||
},
|
||||
removeItem(key) {
|
||||
store.delete(key);
|
||||
},
|
||||
setItem(key, value) {
|
||||
store.set(key, String(value));
|
||||
},
|
||||
};
|
||||
}
|
||||
function resolveSessionStorage() {
|
||||
try {
|
||||
if (typeof globalThis !== 'undefined' && globalThis.sessionStorage) {
|
||||
return globalThis.sessionStorage;
|
||||
}
|
||||
}
|
||||
catch (_a) {
|
||||
// Property access on `sessionStorage` itself throws in restricted-storage browsers.
|
||||
}
|
||||
return createMemorySessionStorage();
|
||||
}
|
||||
const WORKER_SCRIPT = `
|
||||
addEventListener("message", (e) => {
|
||||
if (e.data.event === "start") {
|
||||
setInterval(() => postMessage({ event: "keepAlive" }), e.data.interval);
|
||||
}
|
||||
});`;
|
||||
export default class RealtimeClient {
|
||||
get endPoint() {
|
||||
return this.socketAdapter.endPoint;
|
||||
}
|
||||
get timeout() {
|
||||
return this.socketAdapter.timeout;
|
||||
}
|
||||
get transport() {
|
||||
return this.socketAdapter.transport;
|
||||
}
|
||||
get heartbeatCallback() {
|
||||
return this.socketAdapter.heartbeatCallback;
|
||||
}
|
||||
get heartbeatIntervalMs() {
|
||||
return this.socketAdapter.heartbeatIntervalMs;
|
||||
}
|
||||
get heartbeatTimer() {
|
||||
if (this.worker) {
|
||||
return this._workerHeartbeatTimer;
|
||||
}
|
||||
return this.socketAdapter.heartbeatTimer;
|
||||
}
|
||||
get pendingHeartbeatRef() {
|
||||
if (this.worker) {
|
||||
return this._pendingWorkerHeartbeatRef;
|
||||
}
|
||||
return this.socketAdapter.pendingHeartbeatRef;
|
||||
}
|
||||
get reconnectTimer() {
|
||||
return this.socketAdapter.reconnectTimer;
|
||||
}
|
||||
get vsn() {
|
||||
return this.socketAdapter.vsn;
|
||||
}
|
||||
get encode() {
|
||||
return this.socketAdapter.encode;
|
||||
}
|
||||
get decode() {
|
||||
return this.socketAdapter.decode;
|
||||
}
|
||||
get reconnectAfterMs() {
|
||||
return this.socketAdapter.reconnectAfterMs;
|
||||
}
|
||||
get sendBuffer() {
|
||||
return this.socketAdapter.sendBuffer;
|
||||
}
|
||||
get stateChangeCallbacks() {
|
||||
return this.socketAdapter.stateChangeCallbacks;
|
||||
}
|
||||
/**
|
||||
* Initializes the Socket.
|
||||
*
|
||||
* @param endPoint The string WebSocket endpoint, ie, "ws://example.com/socket", "wss://example.com", "/socket" (inherited host & protocol)
|
||||
* @param options.transport The Websocket Transport, for example WebSocket. This can be a custom implementation
|
||||
* @param options.timeout The default timeout in milliseconds to trigger push timeouts.
|
||||
* @param options.params The optional params to pass when connecting.
|
||||
* @param options.headers Deprecated: headers cannot be set on websocket connections and this option will be removed in the future.
|
||||
* @param options.heartbeatIntervalMs The millisec interval to send a heartbeat message.
|
||||
* @param options.heartbeatCallback The optional function to handle heartbeat status and latency.
|
||||
* @param options.logger The optional function for specialized logging, ie: logger: (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }
|
||||
* @param options.logLevel Sets the log level for Realtime
|
||||
* @param options.encode The function to encode outgoing messages. Defaults to JSON: (payload, callback) => callback(JSON.stringify(payload))
|
||||
* @param options.decode The function to decode incoming messages. Defaults to Serializer's decode.
|
||||
* @param options.reconnectAfterMs he optional function that returns the millsec reconnect interval. Defaults to stepped backoff off.
|
||||
* @param options.worker Use Web Worker to set a side flow. Defaults to false.
|
||||
* @param options.workerUrl The URL of the worker script. Defaults to https://realtime.supabase.com/worker.js that includes a heartbeat event call to keep the connection alive.
|
||||
* @param options.vsn The protocol version to use when connecting. Supported versions are "1.0.0" and "2.0.0". Defaults to "2.0.0".
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Using supabase-js (recommended)
|
||||
* ```ts
|
||||
* import { createClient } from '@supabase/supabase-js'
|
||||
*
|
||||
* const supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key')
|
||||
* const channel = supabase.channel('room1')
|
||||
* channel
|
||||
* .on('broadcast', { event: 'cursor-pos' }, (payload) => console.log(payload))
|
||||
* .subscribe()
|
||||
* ```
|
||||
*
|
||||
* @example Standalone import for bundle-sensitive environments
|
||||
* ```ts
|
||||
* import RealtimeClient from '@supabase/realtime-js'
|
||||
*
|
||||
* const client = new RealtimeClient('https://xyzcompany.supabase.co/realtime/v1', {
|
||||
* params: { apikey: 'your-publishable-key' },
|
||||
* })
|
||||
* client.connect()
|
||||
* ```
|
||||
*/
|
||||
constructor(endPoint, options) {
|
||||
var _a;
|
||||
this.channels = new Array();
|
||||
this.accessTokenValue = null;
|
||||
this.accessToken = null;
|
||||
this.apiKey = null;
|
||||
this.httpEndpoint = '';
|
||||
/** @deprecated headers cannot be set on websocket connections */
|
||||
this.headers = {};
|
||||
this.params = {};
|
||||
this.ref = 0;
|
||||
this.serializer = new Serializer();
|
||||
this._manuallySetToken = false;
|
||||
this._authPromise = null;
|
||||
this._workerHeartbeatTimer = undefined;
|
||||
this._pendingWorkerHeartbeatRef = null;
|
||||
this._pendingDisconnectTimer = null;
|
||||
this._disconnectOnEmptyChannelsAfterMs = 0;
|
||||
/**
|
||||
* Use either custom fetch, if provided, or default fetch to make HTTP requests
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
this._resolveFetch = (customFetch) => {
|
||||
if (customFetch) {
|
||||
return (...args) => customFetch(...args);
|
||||
}
|
||||
return (...args) => fetch(...args);
|
||||
};
|
||||
// Validate required parameters
|
||||
if (!((_a = options === null || options === void 0 ? void 0 : options.params) === null || _a === void 0 ? void 0 : _a.apikey)) {
|
||||
throw new Error('API key is required to connect to Realtime');
|
||||
}
|
||||
this.apiKey = options.params.apikey;
|
||||
const socketAdapterOptions = this._initializeOptions(options);
|
||||
this.socketAdapter = new SocketAdapter(endPoint, socketAdapterOptions);
|
||||
this.httpEndpoint = httpEndpointURL(endPoint);
|
||||
this.fetch = this._resolveFetch(options === null || options === void 0 ? void 0 : options.fetch);
|
||||
}
|
||||
/**
|
||||
* Connects the socket, unless already connected.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
connect() {
|
||||
// Skip if already connecting, disconnecting, or connected
|
||||
if (this.isConnecting() || this.isDisconnecting() || this.isConnected()) {
|
||||
return;
|
||||
}
|
||||
// Trigger auth if needed and not already in progress
|
||||
// This ensures auth is called for standalone RealtimeClient usage
|
||||
// while avoiding race conditions with SupabaseClient's immediate setAuth call
|
||||
if (this.accessToken && !this._authPromise) {
|
||||
this._setAuthSafely('connect');
|
||||
}
|
||||
this._setupConnectionHandlers();
|
||||
try {
|
||||
this.socketAdapter.connect();
|
||||
}
|
||||
catch (error) {
|
||||
const errorMessage = error.message;
|
||||
throw new Error(`WebSocket not available: ${errorMessage}`);
|
||||
}
|
||||
this._handleNodeJsRaceCondition();
|
||||
}
|
||||
/**
|
||||
* Returns the URL of the websocket.
|
||||
* @returns string The URL of the websocket.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
endpointURL() {
|
||||
return this.socketAdapter.endPointURL();
|
||||
}
|
||||
/**
|
||||
* Disconnects the socket.
|
||||
*
|
||||
* @param code A numeric status code to send on disconnect.
|
||||
* @param reason A custom reason for the disconnect.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
async disconnect(code, reason) {
|
||||
this._cancelPendingDisconnect();
|
||||
if (this.isDisconnecting()) {
|
||||
return 'ok';
|
||||
}
|
||||
return await this.socketAdapter.disconnect(() => {
|
||||
clearInterval(this._workerHeartbeatTimer);
|
||||
this._terminateWorker();
|
||||
}, code, reason);
|
||||
}
|
||||
/**
|
||||
* Returns all created channels
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
getChannels() {
|
||||
return this.channels;
|
||||
}
|
||||
/**
|
||||
* Unsubscribes, removes and tears down a single channel
|
||||
* @param channel A RealtimeChannel instance
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
async removeChannel(channel) {
|
||||
const status = await channel.unsubscribe();
|
||||
if (status === 'ok') {
|
||||
channel.teardown();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
/**
|
||||
* Unsubscribes, removes and tears down all channels
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
async removeAllChannels() {
|
||||
const promises = this.channels.map(async (channel) => {
|
||||
const result = await channel.unsubscribe();
|
||||
channel.teardown();
|
||||
return result;
|
||||
});
|
||||
const result = await Promise.all(promises);
|
||||
await this.disconnect();
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Logs the message.
|
||||
*
|
||||
* For customized logging, `this.logger` can be overridden in Client constructor.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
log(kind, msg, data) {
|
||||
this.socketAdapter.log(kind, msg, data);
|
||||
}
|
||||
/**
|
||||
* Returns the current state of the socket.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
connectionState() {
|
||||
return this.socketAdapter.connectionState() || CONNECTION_STATE.closed;
|
||||
}
|
||||
/**
|
||||
* Returns `true` is the connection is open.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
isConnected() {
|
||||
return this.socketAdapter.isConnected();
|
||||
}
|
||||
/**
|
||||
* Returns `true` if the connection is currently connecting.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
isConnecting() {
|
||||
return this.socketAdapter.isConnecting();
|
||||
}
|
||||
/**
|
||||
* Returns `true` if the connection is currently disconnecting.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
isDisconnecting() {
|
||||
return this.socketAdapter.isDisconnecting();
|
||||
}
|
||||
/**
|
||||
* Creates (or reuses) a {@link RealtimeChannel} for the provided topic.
|
||||
*
|
||||
* Topics are automatically prefixed with `realtime:` to match the Realtime service.
|
||||
* If a channel with the same topic already exists it will be returned instead of creating
|
||||
* a duplicate connection.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
channel(topic, params = { config: {} }) {
|
||||
const realtimeTopic = `realtime:${topic}`;
|
||||
const exists = this.getChannels().find((c) => c.topic === realtimeTopic);
|
||||
if (!exists) {
|
||||
const chan = new RealtimeChannel(`realtime:${topic}`, params, this);
|
||||
this._cancelPendingDisconnect();
|
||||
this.channels.push(chan);
|
||||
return chan;
|
||||
}
|
||||
else {
|
||||
return exists;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Push out a message if the socket is connected.
|
||||
*
|
||||
* If the socket is not connected, the message gets enqueued within a local buffer, and sent out when a connection is next established.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
push(data) {
|
||||
this.socketAdapter.push(data);
|
||||
}
|
||||
/**
|
||||
* Sets the JWT access token used for channel subscription authorization and Realtime RLS.
|
||||
*
|
||||
* If param is null it will use the `accessToken` callback function or the token set on the client.
|
||||
*
|
||||
* On callback used, it will set the value of the token internal to the client.
|
||||
*
|
||||
* When a token is explicitly provided, it will be preserved across channel operations
|
||||
* (including removeChannel and resubscribe). The `accessToken` callback will not be
|
||||
* invoked until `setAuth()` is called without arguments.
|
||||
*
|
||||
* @param token A JWT string to override the token set on the client.
|
||||
*
|
||||
* @example Setting the authorization header
|
||||
* // Use a manual token (preserved across resubscribes, ignores accessToken callback)
|
||||
* client.realtime.setAuth('my-custom-jwt')
|
||||
*
|
||||
* // Switch back to using the accessToken callback
|
||||
* client.realtime.setAuth()
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
async setAuth(token = null) {
|
||||
this._authPromise = this._performAuth(token);
|
||||
try {
|
||||
await this._authPromise;
|
||||
}
|
||||
finally {
|
||||
this._authPromise = null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns true if the current access token was explicitly set via setAuth(token),
|
||||
* false if it was obtained via the accessToken callback.
|
||||
* @internal
|
||||
*/
|
||||
_isManualToken() {
|
||||
return this._manuallySetToken;
|
||||
}
|
||||
/**
|
||||
* Sends a heartbeat message if the socket is connected.
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
async sendHeartbeat() {
|
||||
this.socketAdapter.sendHeartbeat();
|
||||
}
|
||||
/**
|
||||
* Sets a callback that receives lifecycle events for internal heartbeat messages.
|
||||
* Useful for instrumenting connection health (e.g. sent/ok/timeout).
|
||||
*
|
||||
* @category Realtime
|
||||
*/
|
||||
onHeartbeat(callback) {
|
||||
this.socketAdapter.heartbeatCallback = this._wrapHeartbeatCallback(callback);
|
||||
}
|
||||
/**
|
||||
* Return the next message ref, accounting for overflows
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_makeRef() {
|
||||
return this.socketAdapter.makeRef();
|
||||
}
|
||||
/**
|
||||
* Removes a channel from RealtimeClient
|
||||
*
|
||||
* @param channel An open subscription.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_remove(channel) {
|
||||
this.channels = this.channels.filter((c) => c.topic !== channel.topic);
|
||||
if (this.channels.length === 0) {
|
||||
this.log('transport', 'no channels remaining, scheduling disconnect');
|
||||
this._schedulePendingDisconnect();
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
_schedulePendingDisconnect() {
|
||||
this._cancelPendingDisconnect();
|
||||
if (this._disconnectOnEmptyChannelsAfterMs === 0) {
|
||||
this.log('transport', 'disconnecting immediately - no channels');
|
||||
this.disconnect();
|
||||
return;
|
||||
}
|
||||
this._pendingDisconnectTimer = setTimeout(() => {
|
||||
this._pendingDisconnectTimer = null;
|
||||
if (this.channels.length === 0) {
|
||||
this.log('transport', 'deferred disconnect fired - no channels, disconnecting');
|
||||
this.disconnect();
|
||||
}
|
||||
}, this._disconnectOnEmptyChannelsAfterMs);
|
||||
this.log('transport', `deferred disconnect scheduled in ${this._disconnectOnEmptyChannelsAfterMs}ms`);
|
||||
}
|
||||
/** @internal */
|
||||
_cancelPendingDisconnect() {
|
||||
if (this._pendingDisconnectTimer !== null) {
|
||||
this.log('transport', 'pending disconnect cancelled - channel activity detected');
|
||||
clearTimeout(this._pendingDisconnectTimer);
|
||||
this._pendingDisconnectTimer = null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Perform the actual auth operation
|
||||
* @internal
|
||||
*/
|
||||
async _performAuth(token = null) {
|
||||
let tokenToSend;
|
||||
let isManualToken = false;
|
||||
if (token) {
|
||||
tokenToSend = token;
|
||||
// Track if this is a manually-provided token
|
||||
isManualToken = true;
|
||||
}
|
||||
else if (this.accessToken) {
|
||||
// Call the accessToken callback to get fresh token
|
||||
try {
|
||||
tokenToSend = await this.accessToken();
|
||||
}
|
||||
catch (e) {
|
||||
this.log('error', 'Error fetching access token from callback', e);
|
||||
// Fall back to cached value if callback fails
|
||||
tokenToSend = this.accessTokenValue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
tokenToSend = this.accessTokenValue;
|
||||
}
|
||||
// Track whether this token was manually set or fetched via callback
|
||||
if (isManualToken) {
|
||||
this._manuallySetToken = true;
|
||||
}
|
||||
else if (this.accessToken) {
|
||||
// If we used the callback, clear the manual flag
|
||||
this._manuallySetToken = false;
|
||||
}
|
||||
if (this.accessTokenValue != tokenToSend) {
|
||||
this.accessTokenValue = tokenToSend;
|
||||
this.channels.forEach((channel) => {
|
||||
const payload = {
|
||||
access_token: tokenToSend,
|
||||
version: DEFAULT_VERSION,
|
||||
};
|
||||
tokenToSend && channel.updateJoinPayload(payload);
|
||||
if (channel.joinedOnce && channel.channelAdapter.isJoined()) {
|
||||
channel.channelAdapter.push(CHANNEL_EVENTS.access_token, {
|
||||
access_token: tokenToSend,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Wait for any in-flight auth operations to complete
|
||||
* @internal
|
||||
*/
|
||||
async _waitForAuthIfNeeded() {
|
||||
if (this._authPromise) {
|
||||
await this._authPromise;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Safely call setAuth with standardized error handling
|
||||
* @internal
|
||||
*/
|
||||
_setAuthSafely(context = 'general') {
|
||||
// Only refresh auth if using callback-based tokens
|
||||
if (!this._isManualToken()) {
|
||||
this.setAuth().catch((e) => {
|
||||
this.log('error', `Error setting auth in ${context}`, e);
|
||||
});
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
_setupConnectionHandlers() {
|
||||
this.socketAdapter.onOpen(() => {
|
||||
const authPromise = this._authPromise ||
|
||||
(this.accessToken && !this.accessTokenValue ? this.setAuth() : Promise.resolve());
|
||||
authPromise.catch((e) => {
|
||||
this.log('error', 'error waiting for auth on connect', e);
|
||||
});
|
||||
if (this.worker && !this.workerRef) {
|
||||
this._startWorkerHeartbeat();
|
||||
}
|
||||
});
|
||||
this.socketAdapter.onClose(() => {
|
||||
if (this.worker && this.workerRef) {
|
||||
this._terminateWorker();
|
||||
}
|
||||
});
|
||||
this.socketAdapter.onMessage((message) => {
|
||||
if (message.ref && message.ref === this._pendingWorkerHeartbeatRef) {
|
||||
this._pendingWorkerHeartbeatRef = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
/** @internal */
|
||||
_handleNodeJsRaceCondition() {
|
||||
if (this.socketAdapter.isConnected()) {
|
||||
// hack: ensure onConnOpen is called
|
||||
this.socketAdapter.getSocket().onConnOpen();
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
_wrapHeartbeatCallback(heartbeatCallback) {
|
||||
return (status, latency) => {
|
||||
if (status === 'disconnected')
|
||||
return;
|
||||
if (status == 'sent')
|
||||
this._setAuthSafely();
|
||||
if (heartbeatCallback)
|
||||
heartbeatCallback(status, latency);
|
||||
};
|
||||
}
|
||||
/** @internal */
|
||||
_startWorkerHeartbeat() {
|
||||
if (this.workerUrl) {
|
||||
this.log('worker', `starting worker for from ${this.workerUrl}`);
|
||||
}
|
||||
else {
|
||||
this.log('worker', `starting default worker`);
|
||||
}
|
||||
const objectUrl = this._workerObjectUrl(this.workerUrl);
|
||||
this.workerRef = new Worker(objectUrl);
|
||||
this.workerRef.onerror = (error) => {
|
||||
this.log('worker', 'worker error', error.message);
|
||||
this._terminateWorker();
|
||||
this.disconnect();
|
||||
};
|
||||
this.workerRef.onmessage = (event) => {
|
||||
if (event.data.event === 'keepAlive') {
|
||||
this.sendHeartbeat();
|
||||
}
|
||||
};
|
||||
this.workerRef.postMessage({
|
||||
event: 'start',
|
||||
interval: this.heartbeatIntervalMs,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Terminate the Web Worker and clear the reference
|
||||
* @internal
|
||||
*/
|
||||
_terminateWorker() {
|
||||
if (this.workerRef) {
|
||||
this.log('worker', 'terminating worker');
|
||||
this.workerRef.terminate();
|
||||
this.workerRef = undefined;
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
_workerObjectUrl(url) {
|
||||
let result_url;
|
||||
if (url) {
|
||||
result_url = url;
|
||||
}
|
||||
else {
|
||||
const blob = new Blob([WORKER_SCRIPT], { type: 'application/javascript' });
|
||||
result_url = URL.createObjectURL(blob);
|
||||
}
|
||||
return result_url;
|
||||
}
|
||||
/**
|
||||
* Initialize socket options with defaults
|
||||
* @internal
|
||||
*/
|
||||
_initializeOptions(options) {
|
||||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
||||
this.worker = (_a = options === null || options === void 0 ? void 0 : options.worker) !== null && _a !== void 0 ? _a : false;
|
||||
this.accessToken = (_b = options === null || options === void 0 ? void 0 : options.accessToken) !== null && _b !== void 0 ? _b : null;
|
||||
const result = {};
|
||||
result.timeout = (_c = options === null || options === void 0 ? void 0 : options.timeout) !== null && _c !== void 0 ? _c : DEFAULT_TIMEOUT;
|
||||
result.heartbeatIntervalMs =
|
||||
(_d = options === null || options === void 0 ? void 0 : options.heartbeatIntervalMs) !== null && _d !== void 0 ? _d : CONNECTION_TIMEOUTS.HEARTBEAT_INTERVAL;
|
||||
this._disconnectOnEmptyChannelsAfterMs =
|
||||
(_e = options === null || options === void 0 ? void 0 : options.disconnectOnEmptyChannelsAfterMs) !== null && _e !== void 0 ? _e : 2 * ((_f = options === null || options === void 0 ? void 0 : options.heartbeatIntervalMs) !== null && _f !== void 0 ? _f : CONNECTION_TIMEOUTS.HEARTBEAT_INTERVAL);
|
||||
// @ts-ignore - mismatch between phoenix and supabase
|
||||
result.transport = (_g = options === null || options === void 0 ? void 0 : options.transport) !== null && _g !== void 0 ? _g : WebSocketFactory.getWebSocketConstructor();
|
||||
result.params = options === null || options === void 0 ? void 0 : options.params;
|
||||
result.logger = options === null || options === void 0 ? void 0 : options.logger;
|
||||
result.heartbeatCallback = this._wrapHeartbeatCallback(options === null || options === void 0 ? void 0 : options.heartbeatCallback);
|
||||
result.sessionStorage = (_h = options === null || options === void 0 ? void 0 : options.sessionStorage) !== null && _h !== void 0 ? _h : resolveSessionStorage();
|
||||
result.reconnectAfterMs =
|
||||
(_j = options === null || options === void 0 ? void 0 : options.reconnectAfterMs) !== null && _j !== void 0 ? _j : ((tries) => {
|
||||
return RECONNECT_INTERVALS[tries - 1] || DEFAULT_RECONNECT_FALLBACK;
|
||||
});
|
||||
let defaultEncode;
|
||||
let defaultDecode;
|
||||
const vsn = (_k = options === null || options === void 0 ? void 0 : options.vsn) !== null && _k !== void 0 ? _k : DEFAULT_VSN;
|
||||
switch (vsn) {
|
||||
case VSN_1_0_0:
|
||||
defaultEncode = (payload, callback) => {
|
||||
return callback(JSON.stringify(payload));
|
||||
};
|
||||
defaultDecode = (payload, callback) => {
|
||||
return callback(JSON.parse(payload));
|
||||
};
|
||||
break;
|
||||
case VSN_2_0_0:
|
||||
defaultEncode = this.serializer.encode.bind(this.serializer);
|
||||
defaultDecode = this.serializer.decode.bind(this.serializer);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported serializer version: ${result.vsn}`);
|
||||
}
|
||||
result.vsn = vsn;
|
||||
result.encode = (_l = options === null || options === void 0 ? void 0 : options.encode) !== null && _l !== void 0 ? _l : defaultEncode;
|
||||
result.decode = (_m = options === null || options === void 0 ? void 0 : options.decode) !== null && _m !== void 0 ? _m : defaultDecode;
|
||||
result.beforeReconnect = this._reconnectAuth.bind(this);
|
||||
if ((options === null || options === void 0 ? void 0 : options.logLevel) || (options === null || options === void 0 ? void 0 : options.log_level)) {
|
||||
this.logLevel = options.logLevel || options.log_level;
|
||||
result.params = Object.assign(Object.assign({}, result.params), { log_level: this.logLevel });
|
||||
}
|
||||
// Handle worker setup
|
||||
if (this.worker) {
|
||||
if (typeof window !== 'undefined' && !window.Worker) {
|
||||
throw new Error('Web Worker is not supported');
|
||||
}
|
||||
this.workerUrl = options === null || options === void 0 ? void 0 : options.workerUrl;
|
||||
result.autoSendHeartbeat = !this.worker;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/** @internal */
|
||||
async _reconnectAuth() {
|
||||
await this._waitForAuthIfNeeded();
|
||||
if (!this.isConnected()) {
|
||||
this.connect();
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=RealtimeClient.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
Generated
Vendored
+132
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Comparison operators accepted in a Postgres Changes `filter` string.
|
||||
*
|
||||
* These mirror the PostgREST operator surface and are evaluated server-side.
|
||||
* Any operator can be negated via {@link RealtimePostgresFilterBuilder.not}
|
||||
* (the `not.` prefix).
|
||||
*
|
||||
* This is the subset of PostgREST's `FilterOperator` (see `postgrest-js`) that
|
||||
* Realtime Postgres Changes supports. Containment, range and full-text search
|
||||
* operators (`cs`, `cd`, `ov`, range ops, `fts`, …) are intentionally not
|
||||
* included because the Realtime server does not evaluate them.
|
||||
*
|
||||
* - `eq`, `neq`, `lt`, `lte`, `gt`, `gte` — comparison
|
||||
* - `in` — membership: `status=in.(active,pending)`
|
||||
* - `like`, `ilike` — pattern match (case-sensitive / insensitive): `title=like.%foo%`
|
||||
* - `is` — `IS` check against `null` / `true` / `false` / `unknown`: `deleted_at=is.null`
|
||||
* - `match`, `imatch` — POSIX regex match (`~` / `~*`)
|
||||
* - `isdistinct` — NULL-safe inequality (`IS DISTINCT FROM`)
|
||||
*/
|
||||
export type RealtimePostgresChangesFilterOperator = 'eq' | 'neq' | 'lt' | 'lte' | 'gt' | 'gte' | 'in' | 'like' | 'ilike' | 'is' | 'match' | 'imatch' | 'isdistinct';
|
||||
/** Scalar value accepted by a single filter operator. */
|
||||
export type RealtimeFilterValue = string | number | boolean | null;
|
||||
/** Value accepted by the `is` operator. */
|
||||
export type RealtimeIsFilterValue = null | boolean | 'null' | 'true' | 'false' | 'unknown';
|
||||
/**
|
||||
* Fluent builder for Postgres Changes `filter` strings.
|
||||
*
|
||||
* Each method appends a single `column=operator.value` condition. Multiple
|
||||
* conditions are combined with commas, which the Realtime server applies as an
|
||||
* `AND`. Pass an instance straight to `channel.on('postgres_changes', …)` — the
|
||||
* SDK serializes it to a string automatically — or call {@link build} to obtain
|
||||
* the string yourself.
|
||||
*
|
||||
* The builder mirrors the `postgrest-js` filter API (`eq`, `neq`, `in`, `like`,
|
||||
* `not`, …) for the operators that Realtime supports. Values containing reserved
|
||||
* characters (`,`, `(`, `)`, `"`, `\`) — or surrounding whitespace — are
|
||||
* automatically double-quoted and escaped the same way PostgREST does, so they
|
||||
* survive the server's filter parser; all other values are sent verbatim.
|
||||
*
|
||||
* The filter is snapshotted when passed to `channel.on(...)`; mutating the
|
||||
* builder afterwards does not affect an existing subscription. An empty builder
|
||||
* serializes to `''`, which the server treats as "no filter".
|
||||
*
|
||||
* @example
|
||||
* channel.on('postgres_changes', {
|
||||
* event: '*',
|
||||
* schema: 'public',
|
||||
* table: 'users',
|
||||
* filter: postgresChangesFilter().eq('id', 1).lt('age', 30), // → 'id=eq.1,age=lt.30'
|
||||
* }, (payload) => { ... })
|
||||
*/
|
||||
export declare class RealtimePostgresFilterBuilder {
|
||||
private readonly filters;
|
||||
private add;
|
||||
/** Match rows where `column` equals `value` (`column=eq.value`). */
|
||||
eq(column: string, value: RealtimeFilterValue): this;
|
||||
/** Match rows where `column` does not equal `value` (`column=neq.value`). */
|
||||
neq(column: string, value: RealtimeFilterValue): this;
|
||||
/** Match rows where `column` is greater than `value` (`column=gt.value`). */
|
||||
gt(column: string, value: RealtimeFilterValue): this;
|
||||
/** Match rows where `column` is greater than or equal to `value` (`column=gte.value`). */
|
||||
gte(column: string, value: RealtimeFilterValue): this;
|
||||
/** Match rows where `column` is less than `value` (`column=lt.value`). */
|
||||
lt(column: string, value: RealtimeFilterValue): this;
|
||||
/** Match rows where `column` is less than or equal to `value` (`column=lte.value`). */
|
||||
lte(column: string, value: RealtimeFilterValue): this;
|
||||
/**
|
||||
* Match rows where `column` is one of `values` (`column=in.(a,b,c)`).
|
||||
* Requires at least one value; duplicates are removed. An element containing a
|
||||
* reserved character is double-quoted (`in.("a,b",c)`), so commas inside an
|
||||
* element are preserved. `null` is intentionally not accepted (`IN (null)`
|
||||
* never matches in SQL) — use `is`/`not('col','is',null)` for null checks.
|
||||
*/
|
||||
in(column: string, values: ReadonlyArray<string | number | boolean>): this;
|
||||
/** Match rows where `column` matches the case-sensitive `pattern` (`column=like.pattern`). */
|
||||
like(column: string, pattern: string): this;
|
||||
/** Match rows where `column` matches the case-insensitive `pattern` (`column=ilike.pattern`). */
|
||||
ilike(column: string, pattern: string): this;
|
||||
/** Match rows where `column` matches the POSIX regex `pattern` (`column=match.pattern`). */
|
||||
match(column: string, pattern: string): this;
|
||||
/** Match rows where `column` matches the case-insensitive POSIX regex `pattern` (`column=imatch.pattern`). */
|
||||
imatch(column: string, pattern: string): this;
|
||||
/**
|
||||
* Match rows where `column` `IS` the given value (`column=is.null`).
|
||||
* Accepts `null`, a boolean, or the keywords `'null' | 'true' | 'false' | 'unknown'`.
|
||||
*/
|
||||
is(column: string, value: RealtimeIsFilterValue): this;
|
||||
/** Match rows where `column` is distinct from `value` (`column=isdistinct.value`). NULL-safe inequality. */
|
||||
isDistinct(column: string, value: RealtimeFilterValue): this;
|
||||
/**
|
||||
* Negate any operator with the `not.` prefix (`column=not.operator.value`).
|
||||
* `in` takes an array, `is` takes an `IS` keyword/boolean/null, and every
|
||||
* other operator takes a scalar value.
|
||||
*
|
||||
* @example
|
||||
* postgresChangesFilter().not('status', 'in', ['draft', 'archived'])
|
||||
* // → status=not.in.(draft,archived)
|
||||
* postgresChangesFilter().not('deleted_at', 'is', null)
|
||||
* // → deleted_at=not.is.null
|
||||
*/
|
||||
not(column: string, operator: 'in', value: ReadonlyArray<string | number | boolean>): this;
|
||||
not(column: string, operator: 'is', value: RealtimeIsFilterValue): this;
|
||||
not(column: string, operator: Exclude<RealtimePostgresChangesFilterOperator, 'in' | 'is'>, value: RealtimeFilterValue): this;
|
||||
/**
|
||||
* Serialize all conditions into the comma-separated (AND) filter string.
|
||||
*
|
||||
* Conditions are joined by commas, which the server applies as `AND`. A scalar
|
||||
* value (or single `in` element) that contains a reserved character — `,`,
|
||||
* `(`, `)`, `"`, `\` — or surrounding whitespace is double-quoted and escaped
|
||||
* the way PostgREST does, so commas inside a value are preserved rather than
|
||||
* read as a condition boundary.
|
||||
*/
|
||||
build(): string;
|
||||
/** Alias for {@link build}; lets the builder be used wherever a string is expected. */
|
||||
toString(): string;
|
||||
}
|
||||
/**
|
||||
* Create a {@link RealtimePostgresFilterBuilder} for composing a Postgres
|
||||
* Changes `filter`. Conditions are combined with `AND`.
|
||||
*
|
||||
* @example
|
||||
* import { postgresChangesFilter } from '@supabase/realtime-js'
|
||||
*
|
||||
* channel.on('postgres_changes', {
|
||||
* event: 'UPDATE',
|
||||
* schema: 'public',
|
||||
* table: 'orders',
|
||||
* filter: postgresChangesFilter().gt('amount', 100).eq('status', 'open'),
|
||||
* }, (payload) => { ... })
|
||||
*/
|
||||
export declare const postgresChangesFilter: () => RealtimePostgresFilterBuilder;
|
||||
//# sourceMappingURL=RealtimePostgresFilterBuilder.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RealtimePostgresFilterBuilder.d.ts","sourceRoot":"","sources":["../../src/RealtimePostgresFilterBuilder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,qCAAqC,GAC7C,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,KAAK,GACL,IAAI,GACJ,MAAM,GACN,OAAO,GACP,IAAI,GACJ,OAAO,GACP,QAAQ,GACR,YAAY,CAAA;AAEhB,yDAAyD;AACzD,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAA;AAElE,2CAA2C;AAC3C,MAAM,MAAM,qBAAqB,GAAG,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;AAuC1F;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,6BAA6B;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IAEvC,OAAO,CAAC,GAAG;IAWX,oEAAoE;IACpE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAIpD,6EAA6E;IAC7E,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAIrD,6EAA6E;IAC7E,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAIpD,0FAA0F;IAC1F,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAIrD,0EAA0E;IAC1E,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAIpD,uFAAuF;IACvF,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAIrD;;;;;;OAMG;IACH,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI;IAI1E,8FAA8F;IAC9F,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3C,iGAAiG;IACjG,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAI5C,4FAA4F;IAC5F,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAI5C,8GAA8G;IAC9G,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAI7C;;;OAGG;IACH,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,qBAAqB,GAAG,IAAI;IAItD,4GAA4G;IAC5G,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,mBAAmB,GAAG,IAAI;IAI5D;;;;;;;;;;OAUG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,GAAG,IAAI;IAC1F,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,qBAAqB,GAAG,IAAI;IACvE,GAAG,CACD,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,OAAO,CAAC,qCAAqC,EAAE,IAAI,GAAG,IAAI,CAAC,EACrE,KAAK,EAAE,mBAAmB,GACzB,IAAI;IASP;;;;;;;;OAQG;IACH,KAAK,IAAI,MAAM;IAIf,uFAAuF;IACvF,QAAQ,IAAI,MAAM;CAGnB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qBAAqB,QAAO,6BACJ,CAAA"}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
// Reserved characters that force PostgREST-style quoting: `[,()]` (which the
|
||||
// server reads as condition/list delimiters) plus `"`/`\` (escaped inside quotes).
|
||||
const PostgrestReservedCharsRegexp = /[,()"\\]/;
|
||||
const needsQuoting = (value) => PostgrestReservedCharsRegexp.test(value) || value !== value.trim();
|
||||
const quote = (value) => `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
||||
const serializeScalar = (value) => {
|
||||
const serialized = value === null ? 'null' : String(value);
|
||||
return needsQuoting(serialized) ? quote(serialized) : serialized;
|
||||
};
|
||||
const serializeIsValue = (value) => value === null ? 'null' : String(value);
|
||||
// Builds the `operator.value` portion of a filter (everything after `column=`).
|
||||
const serialize = (operator, value) => {
|
||||
if (operator === 'in') {
|
||||
const values = Array.isArray(value) ? value : [value];
|
||||
if (values.length === 0) {
|
||||
throw new Error('Realtime `in` filter requires at least one value.');
|
||||
}
|
||||
const items = Array.from(new Set(values))
|
||||
.map((v) => serializeScalar(v))
|
||||
.join(',');
|
||||
return `in.(${items})`;
|
||||
}
|
||||
if (operator === 'is') {
|
||||
return `is.${serializeIsValue(value)}`;
|
||||
}
|
||||
return `${operator}.${serializeScalar(value)}`;
|
||||
};
|
||||
/**
|
||||
* Fluent builder for Postgres Changes `filter` strings.
|
||||
*
|
||||
* Each method appends a single `column=operator.value` condition. Multiple
|
||||
* conditions are combined with commas, which the Realtime server applies as an
|
||||
* `AND`. Pass an instance straight to `channel.on('postgres_changes', …)` — the
|
||||
* SDK serializes it to a string automatically — or call {@link build} to obtain
|
||||
* the string yourself.
|
||||
*
|
||||
* The builder mirrors the `postgrest-js` filter API (`eq`, `neq`, `in`, `like`,
|
||||
* `not`, …) for the operators that Realtime supports. Values containing reserved
|
||||
* characters (`,`, `(`, `)`, `"`, `\`) — or surrounding whitespace — are
|
||||
* automatically double-quoted and escaped the same way PostgREST does, so they
|
||||
* survive the server's filter parser; all other values are sent verbatim.
|
||||
*
|
||||
* The filter is snapshotted when passed to `channel.on(...)`; mutating the
|
||||
* builder afterwards does not affect an existing subscription. An empty builder
|
||||
* serializes to `''`, which the server treats as "no filter".
|
||||
*
|
||||
* @example
|
||||
* channel.on('postgres_changes', {
|
||||
* event: '*',
|
||||
* schema: 'public',
|
||||
* table: 'users',
|
||||
* filter: postgresChangesFilter().eq('id', 1).lt('age', 30), // → 'id=eq.1,age=lt.30'
|
||||
* }, (payload) => { ... })
|
||||
*/
|
||||
export class RealtimePostgresFilterBuilder {
|
||||
constructor() {
|
||||
this.filters = [];
|
||||
}
|
||||
add(column, operator, value, negate = false) {
|
||||
const prefix = negate ? 'not.' : '';
|
||||
this.filters.push(`${column}=${prefix}${serialize(operator, value)}`);
|
||||
return this;
|
||||
}
|
||||
/** Match rows where `column` equals `value` (`column=eq.value`). */
|
||||
eq(column, value) {
|
||||
return this.add(column, 'eq', value);
|
||||
}
|
||||
/** Match rows where `column` does not equal `value` (`column=neq.value`). */
|
||||
neq(column, value) {
|
||||
return this.add(column, 'neq', value);
|
||||
}
|
||||
/** Match rows where `column` is greater than `value` (`column=gt.value`). */
|
||||
gt(column, value) {
|
||||
return this.add(column, 'gt', value);
|
||||
}
|
||||
/** Match rows where `column` is greater than or equal to `value` (`column=gte.value`). */
|
||||
gte(column, value) {
|
||||
return this.add(column, 'gte', value);
|
||||
}
|
||||
/** Match rows where `column` is less than `value` (`column=lt.value`). */
|
||||
lt(column, value) {
|
||||
return this.add(column, 'lt', value);
|
||||
}
|
||||
/** Match rows where `column` is less than or equal to `value` (`column=lte.value`). */
|
||||
lte(column, value) {
|
||||
return this.add(column, 'lte', value);
|
||||
}
|
||||
/**
|
||||
* Match rows where `column` is one of `values` (`column=in.(a,b,c)`).
|
||||
* Requires at least one value; duplicates are removed. An element containing a
|
||||
* reserved character is double-quoted (`in.("a,b",c)`), so commas inside an
|
||||
* element are preserved. `null` is intentionally not accepted (`IN (null)`
|
||||
* never matches in SQL) — use `is`/`not('col','is',null)` for null checks.
|
||||
*/
|
||||
in(column, values) {
|
||||
return this.add(column, 'in', values);
|
||||
}
|
||||
/** Match rows where `column` matches the case-sensitive `pattern` (`column=like.pattern`). */
|
||||
like(column, pattern) {
|
||||
return this.add(column, 'like', pattern);
|
||||
}
|
||||
/** Match rows where `column` matches the case-insensitive `pattern` (`column=ilike.pattern`). */
|
||||
ilike(column, pattern) {
|
||||
return this.add(column, 'ilike', pattern);
|
||||
}
|
||||
/** Match rows where `column` matches the POSIX regex `pattern` (`column=match.pattern`). */
|
||||
match(column, pattern) {
|
||||
return this.add(column, 'match', pattern);
|
||||
}
|
||||
/** Match rows where `column` matches the case-insensitive POSIX regex `pattern` (`column=imatch.pattern`). */
|
||||
imatch(column, pattern) {
|
||||
return this.add(column, 'imatch', pattern);
|
||||
}
|
||||
/**
|
||||
* Match rows where `column` `IS` the given value (`column=is.null`).
|
||||
* Accepts `null`, a boolean, or the keywords `'null' | 'true' | 'false' | 'unknown'`.
|
||||
*/
|
||||
is(column, value) {
|
||||
return this.add(column, 'is', value);
|
||||
}
|
||||
/** Match rows where `column` is distinct from `value` (`column=isdistinct.value`). NULL-safe inequality. */
|
||||
isDistinct(column, value) {
|
||||
return this.add(column, 'isdistinct', value);
|
||||
}
|
||||
not(column, operator, value) {
|
||||
return this.add(column, operator, value, true);
|
||||
}
|
||||
/**
|
||||
* Serialize all conditions into the comma-separated (AND) filter string.
|
||||
*
|
||||
* Conditions are joined by commas, which the server applies as `AND`. A scalar
|
||||
* value (or single `in` element) that contains a reserved character — `,`,
|
||||
* `(`, `)`, `"`, `\` — or surrounding whitespace is double-quoted and escaped
|
||||
* the way PostgREST does, so commas inside a value are preserved rather than
|
||||
* read as a condition boundary.
|
||||
*/
|
||||
build() {
|
||||
return this.filters.join(',');
|
||||
}
|
||||
/** Alias for {@link build}; lets the builder be used wherever a string is expected. */
|
||||
toString() {
|
||||
return this.build();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Create a {@link RealtimePostgresFilterBuilder} for composing a Postgres
|
||||
* Changes `filter`. Conditions are combined with `AND`.
|
||||
*
|
||||
* @example
|
||||
* import { postgresChangesFilter } from '@supabase/realtime-js'
|
||||
*
|
||||
* channel.on('postgres_changes', {
|
||||
* event: 'UPDATE',
|
||||
* schema: 'public',
|
||||
* table: 'orders',
|
||||
* filter: postgresChangesFilter().gt('amount', 100).eq('status', 'open'),
|
||||
* }, (payload) => { ... })
|
||||
*/
|
||||
export const postgresChangesFilter = () => new RealtimePostgresFilterBuilder();
|
||||
//# sourceMappingURL=RealtimePostgresFilterBuilder.js.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RealtimePostgresFilterBuilder.js","sourceRoot":"","sources":["../../src/RealtimePostgresFilterBuilder.ts"],"names":[],"mappings":"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"}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import type RealtimeChannel from './RealtimeChannel';
|
||||
export type Presence<T extends {
|
||||
[key: string]: any;
|
||||
} = {}> = {
|
||||
presence_ref: string;
|
||||
} & T;
|
||||
export type RealtimePresenceState<T extends {
|
||||
[key: string]: any;
|
||||
} = {}> = {
|
||||
[key: string]: Presence<T>[];
|
||||
};
|
||||
export type RealtimePresenceJoinPayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = {
|
||||
event: `${REALTIME_PRESENCE_LISTEN_EVENTS.JOIN}`;
|
||||
key: string;
|
||||
currentPresences: Presence<T>[];
|
||||
newPresences: Presence<T>[];
|
||||
};
|
||||
export type RealtimePresenceLeavePayload<T extends {
|
||||
[key: string]: any;
|
||||
}> = {
|
||||
event: `${REALTIME_PRESENCE_LISTEN_EVENTS.LEAVE}`;
|
||||
key: string;
|
||||
currentPresences: Presence<T>[];
|
||||
leftPresences: Presence<T>[];
|
||||
};
|
||||
export declare enum REALTIME_PRESENCE_LISTEN_EVENTS {
|
||||
SYNC = "sync",
|
||||
JOIN = "join",
|
||||
LEAVE = "leave"
|
||||
}
|
||||
export type RealtimePresenceOptions = {
|
||||
events?: {
|
||||
state: string;
|
||||
diff: string;
|
||||
};
|
||||
};
|
||||
export default class RealtimePresence {
|
||||
channel: RealtimeChannel;
|
||||
get state(): RealtimePresenceState;
|
||||
private presenceAdapter;
|
||||
/**
|
||||
* Creates a Presence helper that keeps the local presence state in sync with the server.
|
||||
*
|
||||
* @param channel - The realtime channel to bind to.
|
||||
* @param opts - Optional custom event names, e.g. `{ events: { state: 'state', diff: 'diff' } }`.
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Example for a presence channel
|
||||
* ```ts
|
||||
* const presence = new RealtimePresence(channel)
|
||||
*
|
||||
* channel.on('presence', ({ event, key }) => {
|
||||
* console.log(`Presence ${event} on ${key}`)
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
constructor(channel: RealtimeChannel, opts?: RealtimePresenceOptions);
|
||||
}
|
||||
//# sourceMappingURL=RealtimePresence.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RealtimePresence.d.ts","sourceRoot":"","sources":["../../src/RealtimePresence.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,eAAe,MAAM,mBAAmB,CAAA;AAGpD,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,GAAG,EAAE,IAAI;IAC5D,YAAY,EAAE,MAAM,CAAA;CACrB,GAAG,CAAC,CAAA;AAEL,MAAM,MAAM,qBAAqB,CAAC,CAAC,SAAS;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,GAAG,EAAE,IAAI;IACzE,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,2BAA2B,CAAC,CAAC,SAAS;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,IAAI;IAC1E,KAAK,EAAE,GAAG,+BAA+B,CAAC,IAAI,EAAE,CAAA;IAChD,GAAG,EAAE,MAAM,CAAA;IACX,gBAAgB,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;IAC/B,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;CAC5B,CAAA;AAED,MAAM,MAAM,4BAA4B,CAAC,CAAC,SAAS;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,IAAI;IAC3E,KAAK,EAAE,GAAG,+BAA+B,CAAC,KAAK,EAAE,CAAA;IACjD,GAAG,EAAE,MAAM,CAAA;IACX,gBAAgB,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;IAC/B,aAAa,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAA;CAC7B,CAAA;AAED,oBAAY,+BAA+B;IACzC,IAAI,SAAS;IACb,IAAI,SAAS;IACb,KAAK,UAAU;CAChB;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CACzC,CAAA;AAED,MAAM,CAAC,OAAO,OAAO,gBAAgB;IAyB1B,OAAO,EAAE,eAAe;IAxBjC,IAAI,KAAK,0BAER;IAED,OAAO,CAAC,eAAe,CAAiB;IAExC;;;;;;;;;;;;;;;;OAgBG;gBAEM,OAAO,EAAE,eAAe,EAC/B,IAAI,CAAC,EAAE,uBAAuB;CAIjC"}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
This file draws heavily from https://github.com/phoenixframework/phoenix/blob/d344ec0a732ab4ee204215b31de69cf4be72e3bf/assets/js/phoenix/presence.js
|
||||
License: https://github.com/phoenixframework/phoenix/blob/d344ec0a732ab4ee204215b31de69cf4be72e3bf/LICENSE.md
|
||||
*/
|
||||
import PresenceAdapter from './phoenix/presenceAdapter';
|
||||
export var REALTIME_PRESENCE_LISTEN_EVENTS;
|
||||
(function (REALTIME_PRESENCE_LISTEN_EVENTS) {
|
||||
REALTIME_PRESENCE_LISTEN_EVENTS["SYNC"] = "sync";
|
||||
REALTIME_PRESENCE_LISTEN_EVENTS["JOIN"] = "join";
|
||||
REALTIME_PRESENCE_LISTEN_EVENTS["LEAVE"] = "leave";
|
||||
})(REALTIME_PRESENCE_LISTEN_EVENTS || (REALTIME_PRESENCE_LISTEN_EVENTS = {}));
|
||||
export default class RealtimePresence {
|
||||
get state() {
|
||||
return this.presenceAdapter.state;
|
||||
}
|
||||
/**
|
||||
* Creates a Presence helper that keeps the local presence state in sync with the server.
|
||||
*
|
||||
* @param channel - The realtime channel to bind to.
|
||||
* @param opts - Optional custom event names, e.g. `{ events: { state: 'state', diff: 'diff' } }`.
|
||||
*
|
||||
* @category Realtime
|
||||
*
|
||||
* @example Example for a presence channel
|
||||
* ```ts
|
||||
* const presence = new RealtimePresence(channel)
|
||||
*
|
||||
* channel.on('presence', ({ event, key }) => {
|
||||
* console.log(`Presence ${event} on ${key}`)
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
constructor(channel, opts) {
|
||||
this.channel = channel;
|
||||
this.presenceAdapter = new PresenceAdapter(this.channel.channelAdapter, opts);
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=RealtimePresence.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RealtimePresence.js","sourceRoot":"","sources":["../../src/RealtimePresence.ts"],"names":[],"mappings":"AAAA;;;EAGE;AAGF,OAAO,eAAe,MAAM,2BAA2B,CAAA;AAwBvD,MAAM,CAAN,IAAY,+BAIX;AAJD,WAAY,+BAA+B;IACzC,gDAAa,CAAA;IACb,gDAAa,CAAA;IACb,kDAAe,CAAA;AACjB,CAAC,EAJW,+BAA+B,KAA/B,+BAA+B,QAI1C;AAMD,MAAM,CAAC,OAAO,OAAO,gBAAgB;IACnC,IAAI,KAAK;QACP,OAAO,IAAI,CAAC,eAAe,CAAC,KAAK,CAAA;IACnC,CAAC;IAID;;;;;;;;;;;;;;;;OAgBG;IACH,YACS,OAAwB,EAC/B,IAA8B;QADvB,YAAO,GAAP,OAAO,CAAiB;QAG/B,IAAI,CAAC,eAAe,GAAG,IAAI,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC,CAAA;IAC/E,CAAC;CACF"}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import RealtimeClient, { RealtimeClientOptions, RealtimeMessage, RealtimeRemoveChannelResponse, WebSocketLikeConstructor } from './RealtimeClient';
|
||||
import RealtimeChannel, { RealtimeChannelOptions, RealtimeChannelSendResponse, RealtimePostgresChangesFilter, RealtimePostgresChangesFilterOperator, RealtimePostgresFilterBuilder, postgresChangesFilter, RealtimePostgresChangesPayload, RealtimePostgresInsertPayload, RealtimePostgresUpdatePayload, RealtimePostgresDeletePayload, RealtimeSystemPayload, REALTIME_LISTEN_TYPES, REALTIME_POSTGRES_CHANGES_LISTEN_EVENT, REALTIME_SUBSCRIBE_STATES, REALTIME_CHANNEL_STATES } from './RealtimeChannel';
|
||||
import RealtimePresence, { RealtimePresenceState, RealtimePresenceJoinPayload, RealtimePresenceLeavePayload, REALTIME_PRESENCE_LISTEN_EVENTS } from './RealtimePresence';
|
||||
import WebSocketFactory, { WebSocketLike } from './lib/websocket-factory';
|
||||
export { RealtimePresence, RealtimeChannel, RealtimeChannelOptions, RealtimeChannelSendResponse, RealtimeClient, RealtimeClientOptions, RealtimeMessage, RealtimePostgresChangesFilter, RealtimePostgresChangesFilterOperator, RealtimePostgresFilterBuilder, postgresChangesFilter, RealtimePostgresChangesPayload, RealtimePostgresInsertPayload, RealtimePostgresUpdatePayload, RealtimePostgresDeletePayload, RealtimeSystemPayload, RealtimePresenceJoinPayload, RealtimePresenceLeavePayload, RealtimePresenceState, RealtimeRemoveChannelResponse, REALTIME_LISTEN_TYPES, REALTIME_POSTGRES_CHANGES_LISTEN_EVENT, REALTIME_PRESENCE_LISTEN_EVENTS, REALTIME_SUBSCRIBE_STATES, REALTIME_CHANNEL_STATES, WebSocketFactory, WebSocketLike, WebSocketLikeConstructor, };
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,EAAE,EACrB,qBAAqB,EACrB,eAAe,EACf,6BAA6B,EAC7B,wBAAwB,EACzB,MAAM,kBAAkB,CAAA;AACzB,OAAO,eAAe,EAAE,EACtB,sBAAsB,EACtB,2BAA2B,EAC3B,6BAA6B,EAC7B,qCAAqC,EACrC,6BAA6B,EAC7B,qBAAqB,EACrB,8BAA8B,EAC9B,6BAA6B,EAC7B,6BAA6B,EAC7B,6BAA6B,EAC7B,qBAAqB,EACrB,qBAAqB,EACrB,sCAAsC,EACtC,yBAAyB,EACzB,uBAAuB,EACxB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,gBAAgB,EAAE,EACvB,qBAAqB,EACrB,2BAA2B,EAC3B,4BAA4B,EAC5B,+BAA+B,EAChC,MAAM,oBAAoB,CAAA;AAC3B,OAAO,gBAAgB,EAAE,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAEzE,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,2BAA2B,EAC3B,cAAc,EACd,qBAAqB,EACrB,eAAe,EACf,6BAA6B,EAC7B,qCAAqC,EACrC,6BAA6B,EAC7B,qBAAqB,EACrB,8BAA8B,EAC9B,6BAA6B,EAC7B,6BAA6B,EAC7B,6BAA6B,EAC7B,qBAAqB,EACrB,2BAA2B,EAC3B,4BAA4B,EAC5B,qBAAqB,EACrB,6BAA6B,EAC7B,qBAAqB,EACrB,sCAAsC,EACtC,+BAA+B,EAC/B,yBAAyB,EACzB,uBAAuB,EACvB,gBAAgB,EAChB,aAAa,EACb,wBAAwB,GACzB,CAAA"}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import RealtimeClient from './RealtimeClient';
|
||||
import RealtimeChannel, { RealtimePostgresFilterBuilder, postgresChangesFilter, REALTIME_LISTEN_TYPES, REALTIME_POSTGRES_CHANGES_LISTEN_EVENT, REALTIME_SUBSCRIBE_STATES, REALTIME_CHANNEL_STATES, } from './RealtimeChannel';
|
||||
import RealtimePresence, { REALTIME_PRESENCE_LISTEN_EVENTS, } from './RealtimePresence';
|
||||
import WebSocketFactory from './lib/websocket-factory';
|
||||
export { RealtimePresence, RealtimeChannel, RealtimeClient, RealtimePostgresFilterBuilder, postgresChangesFilter, REALTIME_LISTEN_TYPES, REALTIME_POSTGRES_CHANGES_LISTEN_EVENT, REALTIME_PRESENCE_LISTEN_EVENTS, REALTIME_SUBSCRIBE_STATES, REALTIME_CHANNEL_STATES, WebSocketFactory, };
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAKN,MAAM,kBAAkB,CAAA;AACzB,OAAO,eAAe,EAAE,EAKtB,6BAA6B,EAC7B,qBAAqB,EAMrB,qBAAqB,EACrB,sCAAsC,EACtC,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,gBAAgB,EAAE,EAIvB,+BAA+B,GAChC,MAAM,oBAAoB,CAAA;AAC3B,OAAO,gBAAmC,MAAM,yBAAyB,CAAA;AAEzE,OAAO,EACL,gBAAgB,EAChB,eAAe,EAGf,cAAc,EAKd,6BAA6B,EAC7B,qBAAqB,EAUrB,qBAAqB,EACrB,sCAAsC,EACtC,+BAA+B,EAC/B,yBAAyB,EACzB,uBAAuB,EACvB,gBAAgB,GAGjB,CAAA"}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import type { SocketState, ChannelState, ChannelEvent as PhoenixChannelEvent, Transport, Vsn } from '../phoenix/types';
|
||||
export type { SocketState, ChannelState, Transport };
|
||||
export declare const DEFAULT_VERSION = "realtime-js/2.110.8";
|
||||
export declare const VSN_1_0_0: Vsn;
|
||||
export declare const VSN_2_0_0: Vsn;
|
||||
export declare const DEFAULT_VSN: Vsn;
|
||||
export declare const VERSION = "2.110.8";
|
||||
export declare const DEFAULT_TIMEOUT = 10000;
|
||||
export declare const WS_CLOSE_NORMAL = 1000;
|
||||
export declare const MAX_PUSH_BUFFER_SIZE = 100;
|
||||
export declare const SOCKET_STATES: {
|
||||
readonly connecting: 0;
|
||||
readonly open: 1;
|
||||
readonly closing: 2;
|
||||
readonly closed: 3;
|
||||
};
|
||||
export declare const CHANNEL_STATES: {
|
||||
readonly closed: "closed";
|
||||
readonly errored: "errored";
|
||||
readonly joined: "joined";
|
||||
readonly joining: "joining";
|
||||
readonly leaving: "leaving";
|
||||
};
|
||||
export type ChannelEvent = PhoenixChannelEvent | 'access_token';
|
||||
export declare const CHANNEL_EVENTS: {
|
||||
readonly close: "phx_close";
|
||||
readonly error: "phx_error";
|
||||
readonly join: "phx_join";
|
||||
readonly reply: "phx_reply";
|
||||
readonly leave: "phx_leave";
|
||||
readonly access_token: "access_token";
|
||||
};
|
||||
export declare const TRANSPORTS: {
|
||||
readonly websocket: "websocket";
|
||||
};
|
||||
export type ConnectionState = 'connecting' | 'open' | 'closing' | 'closed' | (string & {});
|
||||
export declare const CONNECTION_STATE: {
|
||||
readonly connecting: "connecting";
|
||||
readonly open: "open";
|
||||
readonly closing: "closing";
|
||||
readonly closed: "closed";
|
||||
};
|
||||
//# sourceMappingURL=constants.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/lib/constants.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,YAAY,IAAI,mBAAmB,EACnC,SAAS,EACT,GAAG,EACJ,MAAM,kBAAkB,CAAA;AAEzB,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,SAAS,EAAE,CAAA;AAEpD,eAAO,MAAM,eAAe,wBAA2B,CAAA;AAEvD,eAAO,MAAM,SAAS,EAAE,GAAa,CAAA;AACrC,eAAO,MAAM,SAAS,EAAE,GAAa,CAAA;AACrC,eAAO,MAAM,WAAW,EAAE,GAAe,CAAA;AAEzC,eAAO,MAAM,OAAO,YAAU,CAAA;AAE9B,eAAO,MAAM,eAAe,QAAQ,CAAA;AAEpC,eAAO,MAAM,eAAe,OAAO,CAAA;AACnC,eAAO,MAAM,oBAAoB,MAAM,CAAA;AAEvC,eAAO,MAAM,aAAa;;;;;CAKhB,CAAA;AAEV,eAAO,MAAM,cAAc;;;;;;CAMjB,CAAA;AAEV,MAAM,MAAM,YAAY,GAAG,mBAAmB,GAAG,cAAc,CAAA;AAE/D,eAAO,MAAM,cAAc;;;;;;;CAOjB,CAAA;AAEV,eAAO,MAAM,UAAU;;CAEb,CAAA;AAEV,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAE1F,eAAO,MAAM,gBAAgB;;;;;CAKnB,CAAA"}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import { version } from './version';
|
||||
export const DEFAULT_VERSION = `realtime-js/${version}`;
|
||||
export const VSN_1_0_0 = '1.0.0';
|
||||
export const VSN_2_0_0 = '2.0.0';
|
||||
export const DEFAULT_VSN = VSN_2_0_0;
|
||||
export const VERSION = version;
|
||||
export const DEFAULT_TIMEOUT = 10000;
|
||||
export const WS_CLOSE_NORMAL = 1000;
|
||||
export const MAX_PUSH_BUFFER_SIZE = 100;
|
||||
export const SOCKET_STATES = {
|
||||
connecting: 0,
|
||||
open: 1,
|
||||
closing: 2,
|
||||
closed: 3,
|
||||
};
|
||||
export const CHANNEL_STATES = {
|
||||
closed: 'closed',
|
||||
errored: 'errored',
|
||||
joined: 'joined',
|
||||
joining: 'joining',
|
||||
leaving: 'leaving',
|
||||
};
|
||||
export const CHANNEL_EVENTS = {
|
||||
close: 'phx_close',
|
||||
error: 'phx_error',
|
||||
join: 'phx_join',
|
||||
reply: 'phx_reply',
|
||||
leave: 'phx_leave',
|
||||
access_token: 'access_token',
|
||||
};
|
||||
export const TRANSPORTS = {
|
||||
websocket: 'websocket',
|
||||
};
|
||||
export const CONNECTION_STATE = {
|
||||
connecting: 'connecting',
|
||||
open: 'open',
|
||||
closing: 'closing',
|
||||
closed: 'closed',
|
||||
};
|
||||
//# sourceMappingURL=constants.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/lib/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAWnC,MAAM,CAAC,MAAM,eAAe,GAAG,eAAe,OAAO,EAAE,CAAA;AAEvD,MAAM,CAAC,MAAM,SAAS,GAAQ,OAAO,CAAA;AACrC,MAAM,CAAC,MAAM,SAAS,GAAQ,OAAO,CAAA;AACrC,MAAM,CAAC,MAAM,WAAW,GAAQ,SAAS,CAAA;AAEzC,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAA;AAE9B,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAA;AAEpC,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAA;AACnC,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,CAAA;AAEvC,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,UAAU,EAAE,CAAC;IACb,IAAI,EAAE,CAAC;IACP,OAAO,EAAE,CAAC;IACV,MAAM,EAAE,CAAC;CACD,CAAA;AAEV,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;CACV,CAAA;AAIV,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,KAAK,EAAE,WAAW;IAClB,KAAK,EAAE,WAAW;IAClB,IAAI,EAAE,UAAU;IAChB,KAAK,EAAE,WAAW;IAClB,KAAK,EAAE,WAAW;IAClB,YAAY,EAAE,cAAc;CACpB,CAAA;AAEV,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,SAAS,EAAE,WAAW;CACd,CAAA;AAIV,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,UAAU,EAAE,YAAY;IACxB,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;CACR,CAAA"}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Normalize the various shapes a channel error reason can take into a real `Error`.
|
||||
*
|
||||
* Transport-level channel errors arrive as a `CloseEvent`, a transport `Event`, an `Error`,
|
||||
* a string, or `undefined` depending on which path in the underlying socket fired. Server-reply
|
||||
* errors arrive as a payload object. This helper produces a consistent `Error` for every case
|
||||
* and preserves the original via `cause` so callers can still inspect the raw event.
|
||||
*/
|
||||
export declare function normalizeChannelError(reason: unknown): Error;
|
||||
//# sourceMappingURL=normalizeChannelError.d.ts.map
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"normalizeChannelError.d.ts","sourceRoot":"","sources":["../../../src/lib/normalizeChannelError.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,KAAK,CAqB5D"}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Normalize the various shapes a channel error reason can take into a real `Error`.
|
||||
*
|
||||
* Transport-level channel errors arrive as a `CloseEvent`, a transport `Event`, an `Error`,
|
||||
* a string, or `undefined` depending on which path in the underlying socket fired. Server-reply
|
||||
* errors arrive as a payload object. This helper produces a consistent `Error` for every case
|
||||
* and preserves the original via `cause` so callers can still inspect the raw event.
|
||||
*/
|
||||
export function normalizeChannelError(reason) {
|
||||
if (reason instanceof Error) {
|
||||
return reason;
|
||||
}
|
||||
if (typeof reason === 'string') {
|
||||
return new Error(reason);
|
||||
}
|
||||
if (reason && typeof reason === 'object') {
|
||||
const obj = reason;
|
||||
if (typeof obj.code === 'number') {
|
||||
const detail = typeof obj.reason === 'string' && obj.reason ? ` (${obj.reason})` : '';
|
||||
return new Error(`socket closed: ${obj.code}${detail}`, { cause: reason });
|
||||
}
|
||||
return new Error('channel error: transport failure', { cause: reason });
|
||||
}
|
||||
return new Error('channel error: connection lost');
|
||||
}
|
||||
//# sourceMappingURL=normalizeChannelError.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"normalizeChannelError.js","sourceRoot":"","sources":["../../../src/lib/normalizeChannelError.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAe;IACnD,IAAI,MAAM,YAAY,KAAK,EAAE,CAAC;QAC5B,OAAO,MAAM,CAAA;IACf,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,IAAI,KAAK,CAAC,MAAM,CAAC,CAAA;IAC1B,CAAC;IAED,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QACzC,MAAM,GAAG,GAAG,MAAiC,CAAA;QAE7C,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;YACrF,OAAO,IAAI,KAAK,CAAC,kBAAkB,GAAG,CAAC,IAAI,GAAG,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;QAC5E,CAAC;QAED,OAAO,IAAI,KAAK,CAAC,kCAAkC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAA;IACzE,CAAC;IAED,OAAO,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAA;AACpD,CAAC"}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
export type Msg<T> = {
|
||||
join_ref?: string | null;
|
||||
ref?: string | null;
|
||||
topic: string;
|
||||
event: string;
|
||||
payload: T;
|
||||
};
|
||||
export default class Serializer {
|
||||
HEADER_LENGTH: number;
|
||||
USER_BROADCAST_PUSH_META_LENGTH: number;
|
||||
KINDS: {
|
||||
userBroadcastPush: number;
|
||||
userBroadcast: number;
|
||||
};
|
||||
BINARY_ENCODING: number;
|
||||
JSON_ENCODING: number;
|
||||
BROADCAST_EVENT: string;
|
||||
allowedMetadataKeys: string[];
|
||||
constructor(allowedMetadataKeys?: string[] | null);
|
||||
encode(msg: Msg<{
|
||||
[key: string]: any;
|
||||
}>, callback: (result: ArrayBuffer | string) => any): any;
|
||||
private _binaryEncodeUserBroadcastPush;
|
||||
private _encodeBinaryUserBroadcastPush;
|
||||
private _encodeJsonUserBroadcastPush;
|
||||
private _encodeUserBroadcastPush;
|
||||
decode(rawPayload: ArrayBuffer | string, callback: Function): any;
|
||||
private _binaryDecode;
|
||||
private _decodeUserBroadcast;
|
||||
private _isArrayBuffer;
|
||||
private _pick;
|
||||
}
|
||||
//# sourceMappingURL=serializer.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"serializer.d.ts","sourceRoot":"","sources":["../../../src/lib/serializer.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI;IACnB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,CAAC,CAAA;CACX,CAAA;AAED,MAAM,CAAC,OAAO,OAAO,UAAU;IAC7B,aAAa,SAAI;IACjB,+BAA+B,SAAI;IACnC,KAAK;;;MAA6C;IAClD,eAAe,SAAI;IACnB,aAAa,SAAI;IACjB,eAAe,SAAc;IAE7B,mBAAmB,EAAE,MAAM,EAAE,CAAK;gBAEtB,mBAAmB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI;IAIjD,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,KAAK,GAAG;IAexF,OAAO,CAAC,8BAA8B;IAQtC,OAAO,CAAC,8BAA8B;IAKtC,OAAO,CAAC,4BAA4B;IAOpC,OAAO,CAAC,wBAAwB;IA6EhC,MAAM,CAAC,UAAU,EAAE,WAAW,GAAG,MAAM,EAAE,QAAQ,EAAE,QAAQ;IAe3D,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,oBAAoB;IA0C5B,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,KAAK;CAMd"}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
export default class Serializer {
|
||||
constructor(allowedMetadataKeys) {
|
||||
this.HEADER_LENGTH = 1;
|
||||
this.USER_BROADCAST_PUSH_META_LENGTH = 6;
|
||||
this.KINDS = { userBroadcastPush: 3, userBroadcast: 4 };
|
||||
this.BINARY_ENCODING = 0;
|
||||
this.JSON_ENCODING = 1;
|
||||
this.BROADCAST_EVENT = 'broadcast';
|
||||
this.allowedMetadataKeys = [];
|
||||
this.allowedMetadataKeys = allowedMetadataKeys !== null && allowedMetadataKeys !== void 0 ? allowedMetadataKeys : [];
|
||||
}
|
||||
encode(msg, callback) {
|
||||
if (msg.event === this.BROADCAST_EVENT &&
|
||||
!(msg.payload instanceof ArrayBuffer) &&
|
||||
typeof msg.payload.event === 'string') {
|
||||
return callback(this._binaryEncodeUserBroadcastPush(msg));
|
||||
}
|
||||
let payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload];
|
||||
return callback(JSON.stringify(payload));
|
||||
}
|
||||
_binaryEncodeUserBroadcastPush(message) {
|
||||
var _a;
|
||||
if (this._isArrayBuffer((_a = message.payload) === null || _a === void 0 ? void 0 : _a.payload)) {
|
||||
return this._encodeBinaryUserBroadcastPush(message);
|
||||
}
|
||||
else {
|
||||
return this._encodeJsonUserBroadcastPush(message);
|
||||
}
|
||||
}
|
||||
_encodeBinaryUserBroadcastPush(message) {
|
||||
var _a, _b;
|
||||
const userPayload = (_b = (_a = message.payload) === null || _a === void 0 ? void 0 : _a.payload) !== null && _b !== void 0 ? _b : new ArrayBuffer(0);
|
||||
return this._encodeUserBroadcastPush(message, this.BINARY_ENCODING, userPayload);
|
||||
}
|
||||
_encodeJsonUserBroadcastPush(message) {
|
||||
var _a, _b;
|
||||
const userPayload = (_b = (_a = message.payload) === null || _a === void 0 ? void 0 : _a.payload) !== null && _b !== void 0 ? _b : {};
|
||||
const encoder = new TextEncoder();
|
||||
const encodedUserPayload = encoder.encode(JSON.stringify(userPayload)).buffer;
|
||||
return this._encodeUserBroadcastPush(message, this.JSON_ENCODING, encodedUserPayload);
|
||||
}
|
||||
_encodeUserBroadcastPush(message, encodingType, encodedPayload) {
|
||||
var _a, _b;
|
||||
// Encode each header field as UTF-8. The length prefixes are byte counts and
|
||||
// the decode side uses TextDecoder (UTF-8), so measuring with String.length
|
||||
// and writing with charCodeAt would corrupt any multi-byte character (e.g.
|
||||
// accents or emoji) and desynchronize the buffer.
|
||||
const encoder = new TextEncoder();
|
||||
const topic = encoder.encode(message.topic);
|
||||
const ref = encoder.encode((_a = message.ref) !== null && _a !== void 0 ? _a : '');
|
||||
const joinRef = encoder.encode((_b = message.join_ref) !== null && _b !== void 0 ? _b : '');
|
||||
const userEvent = encoder.encode(message.payload.event);
|
||||
// Filter metadata based on allowed keys
|
||||
const rest = this.allowedMetadataKeys
|
||||
? this._pick(message.payload, this.allowedMetadataKeys)
|
||||
: {};
|
||||
const metadata = encoder.encode(Object.keys(rest).length === 0 ? '' : JSON.stringify(rest));
|
||||
// Validate byte lengths don't exceed uint8 max value (255)
|
||||
if (joinRef.length > 255) {
|
||||
throw new Error(`joinRef length ${joinRef.length} exceeds maximum of 255`);
|
||||
}
|
||||
if (ref.length > 255) {
|
||||
throw new Error(`ref length ${ref.length} exceeds maximum of 255`);
|
||||
}
|
||||
if (topic.length > 255) {
|
||||
throw new Error(`topic length ${topic.length} exceeds maximum of 255`);
|
||||
}
|
||||
if (userEvent.length > 255) {
|
||||
throw new Error(`userEvent length ${userEvent.length} exceeds maximum of 255`);
|
||||
}
|
||||
if (metadata.length > 255) {
|
||||
throw new Error(`metadata length ${metadata.length} exceeds maximum of 255`);
|
||||
}
|
||||
const metaLength = this.USER_BROADCAST_PUSH_META_LENGTH +
|
||||
joinRef.length +
|
||||
ref.length +
|
||||
topic.length +
|
||||
userEvent.length +
|
||||
metadata.length;
|
||||
const header = new ArrayBuffer(this.HEADER_LENGTH + metaLength);
|
||||
const view = new DataView(header);
|
||||
const bytes = new Uint8Array(header);
|
||||
let offset = 0;
|
||||
view.setUint8(offset++, this.KINDS.userBroadcastPush); // kind
|
||||
view.setUint8(offset++, joinRef.length);
|
||||
view.setUint8(offset++, ref.length);
|
||||
view.setUint8(offset++, topic.length);
|
||||
view.setUint8(offset++, userEvent.length);
|
||||
view.setUint8(offset++, metadata.length);
|
||||
view.setUint8(offset++, encodingType);
|
||||
bytes.set(joinRef, offset);
|
||||
offset += joinRef.length;
|
||||
bytes.set(ref, offset);
|
||||
offset += ref.length;
|
||||
bytes.set(topic, offset);
|
||||
offset += topic.length;
|
||||
bytes.set(userEvent, offset);
|
||||
offset += userEvent.length;
|
||||
bytes.set(metadata, offset);
|
||||
offset += metadata.length;
|
||||
var combined = new Uint8Array(header.byteLength + encodedPayload.byteLength);
|
||||
combined.set(new Uint8Array(header), 0);
|
||||
combined.set(new Uint8Array(encodedPayload), header.byteLength);
|
||||
return combined.buffer;
|
||||
}
|
||||
decode(rawPayload, callback) {
|
||||
if (this._isArrayBuffer(rawPayload)) {
|
||||
let result = this._binaryDecode(rawPayload);
|
||||
return callback(result);
|
||||
}
|
||||
if (typeof rawPayload === 'string') {
|
||||
const jsonPayload = JSON.parse(rawPayload);
|
||||
const [join_ref, ref, topic, event, payload] = jsonPayload;
|
||||
return callback({ join_ref, ref, topic, event, payload });
|
||||
}
|
||||
return callback({});
|
||||
}
|
||||
_binaryDecode(buffer) {
|
||||
const view = new DataView(buffer);
|
||||
const kind = view.getUint8(0);
|
||||
const decoder = new TextDecoder();
|
||||
switch (kind) {
|
||||
case this.KINDS.userBroadcast:
|
||||
return this._decodeUserBroadcast(buffer, view, decoder);
|
||||
}
|
||||
}
|
||||
_decodeUserBroadcast(buffer, view, decoder) {
|
||||
const topicSize = view.getUint8(1);
|
||||
const userEventSize = view.getUint8(2);
|
||||
const metadataSize = view.getUint8(3);
|
||||
const payloadEncoding = view.getUint8(4);
|
||||
let offset = this.HEADER_LENGTH + 4;
|
||||
const topic = decoder.decode(buffer.slice(offset, offset + topicSize));
|
||||
offset = offset + topicSize;
|
||||
const userEvent = decoder.decode(buffer.slice(offset, offset + userEventSize));
|
||||
offset = offset + userEventSize;
|
||||
const metadata = decoder.decode(buffer.slice(offset, offset + metadataSize));
|
||||
offset = offset + metadataSize;
|
||||
const payload = buffer.slice(offset, buffer.byteLength);
|
||||
const parsedPayload = payloadEncoding === this.JSON_ENCODING ? JSON.parse(decoder.decode(payload)) : payload;
|
||||
const data = {
|
||||
type: this.BROADCAST_EVENT,
|
||||
event: userEvent,
|
||||
payload: parsedPayload,
|
||||
};
|
||||
// Metadata is optional and always JSON encoded
|
||||
if (metadataSize > 0) {
|
||||
data['meta'] = JSON.parse(metadata);
|
||||
}
|
||||
return { join_ref: null, ref: null, topic: topic, event: this.BROADCAST_EVENT, payload: data };
|
||||
}
|
||||
_isArrayBuffer(buffer) {
|
||||
var _a;
|
||||
return buffer instanceof ArrayBuffer || ((_a = buffer === null || buffer === void 0 ? void 0 : buffer.constructor) === null || _a === void 0 ? void 0 : _a.name) === 'ArrayBuffer';
|
||||
}
|
||||
_pick(obj, keys) {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return {};
|
||||
}
|
||||
return Object.fromEntries(Object.entries(obj).filter(([key]) => keys.includes(key)));
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=serializer.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+109
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Helpers to convert the change Payload into native JS types.
|
||||
*/
|
||||
export declare enum PostgresTypes {
|
||||
abstime = "abstime",
|
||||
bool = "bool",
|
||||
date = "date",
|
||||
daterange = "daterange",
|
||||
float4 = "float4",
|
||||
float8 = "float8",
|
||||
int2 = "int2",
|
||||
int4 = "int4",
|
||||
int4range = "int4range",
|
||||
int8 = "int8",
|
||||
int8range = "int8range",
|
||||
json = "json",
|
||||
jsonb = "jsonb",
|
||||
money = "money",
|
||||
numeric = "numeric",
|
||||
oid = "oid",
|
||||
reltime = "reltime",
|
||||
text = "text",
|
||||
time = "time",
|
||||
timestamp = "timestamp",
|
||||
timestamptz = "timestamptz",
|
||||
timetz = "timetz",
|
||||
tsrange = "tsrange",
|
||||
tstzrange = "tstzrange"
|
||||
}
|
||||
type Columns = {
|
||||
name: string;
|
||||
type: string;
|
||||
flags?: string[];
|
||||
type_modifier?: number;
|
||||
}[];
|
||||
type BaseValue = null | string | number | boolean;
|
||||
type RecordValue = BaseValue | BaseValue[];
|
||||
type Record = {
|
||||
[key: string]: RecordValue;
|
||||
};
|
||||
/**
|
||||
* Takes an array of columns and an object of string values then converts each string value
|
||||
* to its mapped type.
|
||||
*
|
||||
* @param {{name: String, type: String}[]} columns
|
||||
* @param {Object} record
|
||||
* @param {Object} options The map of various options that can be applied to the mapper
|
||||
* @param {Array} options.skipTypes The array of types that should not be converted
|
||||
*
|
||||
* @example convertChangeData([{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age:'33'}, {})
|
||||
* //=>{ first_name: 'Paul', age: 33 }
|
||||
*/
|
||||
export declare const convertChangeData: (columns: Columns, record: Record | null, options?: {
|
||||
skipTypes?: string[];
|
||||
}) => Record;
|
||||
/**
|
||||
* Converts the value of an individual column.
|
||||
*
|
||||
* @param {String} columnName The column that you want to convert
|
||||
* @param {{name: String, type: String}[]} columns All of the columns
|
||||
* @param {Object} record The map of string values
|
||||
* @param {Array} skipTypes An array of types that should not be converted
|
||||
* @return {object} Useless information
|
||||
*
|
||||
* @example convertColumn('age', [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age: '33'}, [])
|
||||
* //=> 33
|
||||
* @example convertColumn('age', [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age: '33'}, ['int4'])
|
||||
* //=> "33"
|
||||
*/
|
||||
export declare const convertColumn: (columnName: string, columns: Columns, record: Record, skipTypes: string[]) => RecordValue;
|
||||
/**
|
||||
* If the value of the cell is `null`, returns null.
|
||||
* Otherwise converts the string value to the correct type.
|
||||
* @param {String} type A postgres column type
|
||||
* @param {String} value The cell value
|
||||
*
|
||||
* @example convertCell('bool', 't')
|
||||
* //=> true
|
||||
* @example convertCell('int8', '10')
|
||||
* //=> 10
|
||||
* @example convertCell('_int4', '{1,2,3,4}')
|
||||
* //=> [1,2,3,4]
|
||||
*/
|
||||
export declare const convertCell: (type: string, value: RecordValue) => RecordValue;
|
||||
export declare const toBoolean: (value: RecordValue) => RecordValue;
|
||||
export declare const toNumber: (value: RecordValue) => RecordValue;
|
||||
export declare const toJson: (value: RecordValue) => RecordValue;
|
||||
/**
|
||||
* Converts a Postgres Array into a native JS array
|
||||
*
|
||||
* @example toArray('{}', 'int4')
|
||||
* //=> []
|
||||
* @example toArray('{"[2021-01-01,2021-12-31)","(2021-01-01,2021-12-32]"}', 'daterange')
|
||||
* //=> ['[2021-01-01,2021-12-31)', '(2021-01-01,2021-12-32]']
|
||||
* @example toArray([1,2,3,4], 'int4')
|
||||
* //=> [1,2,3,4]
|
||||
*/
|
||||
export declare const toArray: (value: RecordValue, type: string) => RecordValue;
|
||||
/**
|
||||
* Fixes timestamp to be ISO-8601. Swaps the space between the date and time for a 'T'
|
||||
* See https://github.com/supabase/supabase/issues/18
|
||||
*
|
||||
* @example toTimestampString('2019-09-10 00:00:00')
|
||||
* //=> '2019-09-10T00:00:00'
|
||||
*/
|
||||
export declare const toTimestampString: (value: RecordValue) => RecordValue;
|
||||
export declare const httpEndpointURL: (socketUrl: string) => string;
|
||||
export {};
|
||||
//# sourceMappingURL=transformers.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"transformers.d.ts","sourceRoot":"","sources":["../../../src/lib/transformers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,oBAAY,aAAa;IACvB,OAAO,YAAY;IACnB,IAAI,SAAS;IACb,IAAI,SAAS;IACb,SAAS,cAAc;IACvB,MAAM,WAAW;IACjB,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,IAAI,SAAS;IACb,SAAS,cAAc;IACvB,IAAI,SAAS;IACb,SAAS,cAAc;IACvB,IAAI,SAAS;IACb,KAAK,UAAU;IACf,KAAK,UAAU;IACf,OAAO,YAAY;IACnB,GAAG,QAAQ;IACX,OAAO,YAAY;IACnB,IAAI,SAAS;IACb,IAAI,SAAS;IACb,SAAS,cAAc;IACvB,WAAW,gBAAgB;IAC3B,MAAM,WAAW;IACjB,OAAO,YAAY;IACnB,SAAS,cAAc;CACxB;AAED,KAAK,OAAO,GAAG;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,EAAE,CAAA;AAEH,KAAK,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AACjD,KAAK,WAAW,GAAG,SAAS,GAAG,SAAS,EAAE,CAAA;AAE1C,KAAK,MAAM,GAAG;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAAA;CAC3B,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,GAC5B,SAAS,OAAO,EAChB,QAAQ,MAAM,GAAG,IAAI,EACrB,UAAS;IAAE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,KACrC,MAWF,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa,GACxB,YAAY,MAAM,EAClB,SAAS,OAAO,EAChB,QAAQ,MAAM,EACd,WAAW,MAAM,EAAE,KAClB,WAUF,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,EAAE,OAAO,WAAW,KAAG,WA0C9D,CAAA;AAKD,eAAO,MAAM,SAAS,GAAI,OAAO,WAAW,KAAG,WAS9C,CAAA;AACD,eAAO,MAAM,QAAQ,GAAI,OAAO,WAAW,KAAG,WAQ7C,CAAA;AACD,eAAO,MAAM,MAAM,GAAI,OAAO,WAAW,KAAG,WAS3C,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,OAAO,GAAI,OAAO,WAAW,EAAE,MAAM,MAAM,KAAG,WA0B1D,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,WAAW,KAAG,WAMtD,CAAA;AAED,eAAO,MAAM,eAAe,GAAI,WAAW,MAAM,KAAG,MAkBnD,CAAA"}
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* Helpers to convert the change Payload into native JS types.
|
||||
*/
|
||||
// Adapted from epgsql (src/epgsql_binary.erl), this module licensed under
|
||||
// 3-clause BSD found here: https://raw.githubusercontent.com/epgsql/epgsql/devel/LICENSE
|
||||
export var PostgresTypes;
|
||||
(function (PostgresTypes) {
|
||||
PostgresTypes["abstime"] = "abstime";
|
||||
PostgresTypes["bool"] = "bool";
|
||||
PostgresTypes["date"] = "date";
|
||||
PostgresTypes["daterange"] = "daterange";
|
||||
PostgresTypes["float4"] = "float4";
|
||||
PostgresTypes["float8"] = "float8";
|
||||
PostgresTypes["int2"] = "int2";
|
||||
PostgresTypes["int4"] = "int4";
|
||||
PostgresTypes["int4range"] = "int4range";
|
||||
PostgresTypes["int8"] = "int8";
|
||||
PostgresTypes["int8range"] = "int8range";
|
||||
PostgresTypes["json"] = "json";
|
||||
PostgresTypes["jsonb"] = "jsonb";
|
||||
PostgresTypes["money"] = "money";
|
||||
PostgresTypes["numeric"] = "numeric";
|
||||
PostgresTypes["oid"] = "oid";
|
||||
PostgresTypes["reltime"] = "reltime";
|
||||
PostgresTypes["text"] = "text";
|
||||
PostgresTypes["time"] = "time";
|
||||
PostgresTypes["timestamp"] = "timestamp";
|
||||
PostgresTypes["timestamptz"] = "timestamptz";
|
||||
PostgresTypes["timetz"] = "timetz";
|
||||
PostgresTypes["tsrange"] = "tsrange";
|
||||
PostgresTypes["tstzrange"] = "tstzrange";
|
||||
})(PostgresTypes || (PostgresTypes = {}));
|
||||
/**
|
||||
* Takes an array of columns and an object of string values then converts each string value
|
||||
* to its mapped type.
|
||||
*
|
||||
* @param {{name: String, type: String}[]} columns
|
||||
* @param {Object} record
|
||||
* @param {Object} options The map of various options that can be applied to the mapper
|
||||
* @param {Array} options.skipTypes The array of types that should not be converted
|
||||
*
|
||||
* @example convertChangeData([{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age:'33'}, {})
|
||||
* //=>{ first_name: 'Paul', age: 33 }
|
||||
*/
|
||||
export const convertChangeData = (columns, record, options = {}) => {
|
||||
var _a;
|
||||
const skipTypes = (_a = options.skipTypes) !== null && _a !== void 0 ? _a : [];
|
||||
if (!record) {
|
||||
return {};
|
||||
}
|
||||
return Object.keys(record).reduce((acc, rec_key) => {
|
||||
acc[rec_key] = convertColumn(rec_key, columns, record, skipTypes);
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
/**
|
||||
* Converts the value of an individual column.
|
||||
*
|
||||
* @param {String} columnName The column that you want to convert
|
||||
* @param {{name: String, type: String}[]} columns All of the columns
|
||||
* @param {Object} record The map of string values
|
||||
* @param {Array} skipTypes An array of types that should not be converted
|
||||
* @return {object} Useless information
|
||||
*
|
||||
* @example convertColumn('age', [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age: '33'}, [])
|
||||
* //=> 33
|
||||
* @example convertColumn('age', [{name: 'first_name', type: 'text'}, {name: 'age', type: 'int4'}], {first_name: 'Paul', age: '33'}, ['int4'])
|
||||
* //=> "33"
|
||||
*/
|
||||
export const convertColumn = (columnName, columns, record, skipTypes) => {
|
||||
const column = columns.find((x) => x.name === columnName);
|
||||
const colType = column === null || column === void 0 ? void 0 : column.type;
|
||||
const value = record[columnName];
|
||||
if (colType && !skipTypes.includes(colType)) {
|
||||
return convertCell(colType, value);
|
||||
}
|
||||
return noop(value);
|
||||
};
|
||||
/**
|
||||
* If the value of the cell is `null`, returns null.
|
||||
* Otherwise converts the string value to the correct type.
|
||||
* @param {String} type A postgres column type
|
||||
* @param {String} value The cell value
|
||||
*
|
||||
* @example convertCell('bool', 't')
|
||||
* //=> true
|
||||
* @example convertCell('int8', '10')
|
||||
* //=> 10
|
||||
* @example convertCell('_int4', '{1,2,3,4}')
|
||||
* //=> [1,2,3,4]
|
||||
*/
|
||||
export const convertCell = (type, value) => {
|
||||
// if data type is an array
|
||||
if (type.charAt(0) === '_') {
|
||||
const dataType = type.slice(1, type.length);
|
||||
return toArray(value, dataType);
|
||||
}
|
||||
// If not null, convert to correct type.
|
||||
switch (type) {
|
||||
case PostgresTypes.bool:
|
||||
return toBoolean(value);
|
||||
case PostgresTypes.float4:
|
||||
case PostgresTypes.float8:
|
||||
case PostgresTypes.int2:
|
||||
case PostgresTypes.int4:
|
||||
case PostgresTypes.int8:
|
||||
case PostgresTypes.numeric:
|
||||
case PostgresTypes.oid:
|
||||
return toNumber(value);
|
||||
case PostgresTypes.json:
|
||||
case PostgresTypes.jsonb:
|
||||
return toJson(value);
|
||||
case PostgresTypes.timestamp:
|
||||
return toTimestampString(value); // Format to be consistent with PostgREST
|
||||
case PostgresTypes.abstime: // To allow users to cast it based on Timezone
|
||||
case PostgresTypes.date: // To allow users to cast it based on Timezone
|
||||
case PostgresTypes.daterange:
|
||||
case PostgresTypes.int4range:
|
||||
case PostgresTypes.int8range:
|
||||
case PostgresTypes.money:
|
||||
case PostgresTypes.reltime: // To allow users to cast it based on Timezone
|
||||
case PostgresTypes.text:
|
||||
case PostgresTypes.time: // To allow users to cast it based on Timezone
|
||||
case PostgresTypes.timestamptz: // To allow users to cast it based on Timezone
|
||||
case PostgresTypes.timetz: // To allow users to cast it based on Timezone
|
||||
case PostgresTypes.tsrange:
|
||||
case PostgresTypes.tstzrange:
|
||||
return noop(value);
|
||||
default:
|
||||
// Return the value for remaining types
|
||||
return noop(value);
|
||||
}
|
||||
};
|
||||
const noop = (value) => {
|
||||
return value;
|
||||
};
|
||||
export const toBoolean = (value) => {
|
||||
switch (value) {
|
||||
case 't':
|
||||
return true;
|
||||
case 'f':
|
||||
return false;
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
};
|
||||
export const toNumber = (value) => {
|
||||
if (typeof value === 'string') {
|
||||
const parsedValue = parseFloat(value);
|
||||
if (!Number.isNaN(parsedValue)) {
|
||||
return parsedValue;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
export const toJson = (value) => {
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
}
|
||||
catch (_a) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
/**
|
||||
* Converts a Postgres Array into a native JS array
|
||||
*
|
||||
* @example toArray('{}', 'int4')
|
||||
* //=> []
|
||||
* @example toArray('{"[2021-01-01,2021-12-31)","(2021-01-01,2021-12-32]"}', 'daterange')
|
||||
* //=> ['[2021-01-01,2021-12-31)', '(2021-01-01,2021-12-32]']
|
||||
* @example toArray([1,2,3,4], 'int4')
|
||||
* //=> [1,2,3,4]
|
||||
*/
|
||||
export const toArray = (value, type) => {
|
||||
if (typeof value !== 'string') {
|
||||
return value;
|
||||
}
|
||||
const lastIdx = value.length - 1;
|
||||
const closeBrace = value[lastIdx];
|
||||
const openBrace = value[0];
|
||||
// Confirm value is a Postgres array by checking curly brackets
|
||||
if (openBrace === '{' && closeBrace === '}') {
|
||||
let arr;
|
||||
const valTrim = value.slice(1, lastIdx);
|
||||
// TODO: find a better solution to separate Postgres array data
|
||||
try {
|
||||
arr = JSON.parse('[' + valTrim + ']');
|
||||
}
|
||||
catch (_) {
|
||||
// WARNING: splitting on comma does not cover all edge cases
|
||||
arr = valTrim ? valTrim.split(',') : [];
|
||||
}
|
||||
return arr.map((val) => convertCell(type, val));
|
||||
}
|
||||
return value;
|
||||
};
|
||||
/**
|
||||
* Fixes timestamp to be ISO-8601. Swaps the space between the date and time for a 'T'
|
||||
* See https://github.com/supabase/supabase/issues/18
|
||||
*
|
||||
* @example toTimestampString('2019-09-10 00:00:00')
|
||||
* //=> '2019-09-10T00:00:00'
|
||||
*/
|
||||
export const toTimestampString = (value) => {
|
||||
if (typeof value === 'string') {
|
||||
return value.replace(' ', 'T');
|
||||
}
|
||||
return value;
|
||||
};
|
||||
export const httpEndpointURL = (socketUrl) => {
|
||||
const wsUrl = new URL(socketUrl);
|
||||
wsUrl.protocol = wsUrl.protocol.replace(/^ws/i, 'http');
|
||||
wsUrl.pathname = wsUrl.pathname
|
||||
.replace(/\/+$/, '') // remove all trailing slashes
|
||||
.replace(/\/socket\/websocket$/i, '') // remove the socket/websocket path
|
||||
.replace(/\/socket$/i, '') // remove the socket path
|
||||
.replace(/\/websocket$/i, ''); // remove the websocket path
|
||||
if (wsUrl.pathname === '' || wsUrl.pathname === '/') {
|
||||
wsUrl.pathname = '/api/broadcast';
|
||||
}
|
||||
else {
|
||||
wsUrl.pathname = wsUrl.pathname + '/api/broadcast';
|
||||
}
|
||||
return wsUrl.href;
|
||||
};
|
||||
//# sourceMappingURL=transformers.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+2
@@ -0,0 +1,2 @@
|
||||
export declare const version = "2.110.8";
|
||||
//# sourceMappingURL=version.d.ts.map
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user