@supabase/phoenix 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE.md +22 -0
  2. package/README.md +122 -0
  3. package/assets/js/phoenix/ajax.js +116 -0
  4. package/assets/js/phoenix/channel.js +331 -0
  5. package/assets/js/phoenix/constants.js +35 -0
  6. package/assets/js/phoenix/index.js +212 -0
  7. package/assets/js/phoenix/longpoll.js +192 -0
  8. package/assets/js/phoenix/presence.js +208 -0
  9. package/assets/js/phoenix/push.js +134 -0
  10. package/assets/js/phoenix/serializer.js +133 -0
  11. package/assets/js/phoenix/socket.js +747 -0
  12. package/assets/js/phoenix/timer.js +48 -0
  13. package/assets/js/phoenix/types.js +184 -0
  14. package/assets/js/phoenix/utils.js +16 -0
  15. package/package.json +58 -0
  16. package/priv/static/favicon.ico +0 -0
  17. package/priv/static/phoenix-orange.png +0 -0
  18. package/priv/static/phoenix.cjs.js +1812 -0
  19. package/priv/static/phoenix.cjs.js.map +7 -0
  20. package/priv/static/phoenix.js +1834 -0
  21. package/priv/static/phoenix.min.js +2 -0
  22. package/priv/static/phoenix.mjs +1789 -0
  23. package/priv/static/phoenix.mjs.map +7 -0
  24. package/priv/static/phoenix.png +0 -0
  25. package/priv/static/types/ajax.d.ts +10 -0
  26. package/priv/static/types/ajax.d.ts.map +1 -0
  27. package/priv/static/types/channel.d.ts +167 -0
  28. package/priv/static/types/channel.d.ts.map +1 -0
  29. package/priv/static/types/constants.d.ts +36 -0
  30. package/priv/static/types/constants.d.ts.map +1 -0
  31. package/priv/static/types/index.d.ts +10 -0
  32. package/priv/static/types/index.d.ts.map +1 -0
  33. package/priv/static/types/longpoll.d.ts +29 -0
  34. package/priv/static/types/longpoll.d.ts.map +1 -0
  35. package/priv/static/types/presence.d.ts +107 -0
  36. package/priv/static/types/presence.d.ts.map +1 -0
  37. package/priv/static/types/push.d.ts +70 -0
  38. package/priv/static/types/push.d.ts.map +1 -0
  39. package/priv/static/types/serializer.d.ts +74 -0
  40. package/priv/static/types/serializer.d.ts.map +1 -0
  41. package/priv/static/types/socket.d.ts +284 -0
  42. package/priv/static/types/socket.d.ts.map +1 -0
  43. package/priv/static/types/timer.d.ts +36 -0
  44. package/priv/static/types/timer.d.ts.map +1 -0
  45. package/priv/static/types/types.d.ts +280 -0
  46. package/priv/static/types/types.d.ts.map +1 -0
  47. package/priv/static/types/utils.d.ts +2 -0
  48. package/priv/static/types/utils.d.ts.map +1 -0
  49. package/tsconfig.json +20 -0
package/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ # MIT License
2
+
3
+ Copyright (c) 2014 Chris McCord
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ <picture>
2
+ <source media="(prefers-color-scheme: dark)" srcset="./priv/static/phoenix-orange.png" />
3
+ <source media="(prefers-color-scheme: light)" srcset="./priv/static/phoenix.png" />
4
+ <img src="./priv/static/phoenix.png" alt="Phoenix logo" />
5
+ </picture>
6
+
7
+ > Peace of mind from prototype to production.
8
+
9
+ [![Build Status](https://github.com/supabase/phoenix/workflows/CI/badge.svg)](https://github.com/supabase/phoenix/actions/workflows/ci.yml) [![npm version](https://img.shields.io/npm/v/@supabase/phoenix.svg)](https://www.npmjs.com/package/@supabase/phoenix)
10
+
11
+ ## Supabase Fork
12
+
13
+ This is a Supabase fork of Phoenix Framework, published to npm as `@supabase/phoenix`.
14
+
15
+ **Installation:**
16
+ ```bash
17
+ npm install @supabase/phoenix
18
+ ```
19
+
20
+ **Releases**: This fork uses automated releases via [release-please](https://github.com/googleapis/release-please). See [RELEASE.md](RELEASE.md) for details.
21
+
22
+ **Upstream**: Based on [phoenixframework/phoenix](https://github.com/phoenixframework/phoenix)
23
+
24
+ ## Versioning
25
+
26
+ This package uses **independent semantic versioning** for the JavaScript client.
27
+
28
+ - **Current version**: 0.1.0
29
+ - **Based on**: Phoenix Framework 1.8.3 JS client
30
+ - **Last synced**: 2026-02-17
31
+
32
+ We version based on **JS API changes only**, not upstream Phoenix framework releases.
33
+ When we merge upstream Phoenix changes, we evaluate the JS API impact and version accordingly.
34
+
35
+ ## Getting started
36
+
37
+ See the official site at <https://www.phoenixframework.org/>.
38
+
39
+ Install the latest version of Phoenix by following the instructions at <https://hexdocs.pm/phoenix/installation.html#phoenix>.
40
+
41
+ ## Documentation
42
+
43
+ API documentation is available at <https://hexdocs.pm/phoenix>.
44
+
45
+ Phoenix.js documentation is available at <https://hexdocs.pm/phoenix/js>.
46
+
47
+ ## Contributing
48
+
49
+ We appreciate any contribution to Phoenix. Check our [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) and [CONTRIBUTING.md](CONTRIBUTING.md) guides for more information. We usually keep a list of features and bugs in the [issue tracker][4].
50
+
51
+ ### Generating a Phoenix project from unreleased versions
52
+
53
+ You can create a new project using the latest Phoenix source installer (the `phx.new` Mix task) with the following steps:
54
+
55
+ 1. Remove any previously installed `phx_new` archives so that Mix will pick up the local source code. This can be done with `mix archive.uninstall phx_new` or by simply deleting the file, which is usually in `~/.mix/archives/`.
56
+ 2. Copy this repo via `git clone https://github.com/phoenixframework/phoenix` or by downloading it
57
+ 3. Run the `phx.new` Mix task from within the `installer` directory, for example:
58
+
59
+ ```bash
60
+ cd phoenix/installer
61
+ mix phx.new dev_app --dev
62
+ ```
63
+
64
+ The `--dev` flag will configure your new project's `:phoenix` dep as a relative path dependency, pointing to your local Phoenix checkout:
65
+
66
+ ```elixir
67
+ defp deps do
68
+ [{:phoenix, path: "../..", override: true},
69
+ ```
70
+
71
+ To create projects outside of the `installer/` directory, add the latest archive to your machine by following the instructions in [installer/README.md](https://github.com/phoenixframework/phoenix/blob/main/installer/README.md)
72
+
73
+ ### Building from source
74
+
75
+ To build the documentation:
76
+
77
+ ```bash
78
+ npm install
79
+ MIX_ENV=docs mix docs
80
+ ```
81
+
82
+ To build Phoenix:
83
+
84
+ ```bash
85
+ mix deps.get
86
+ mix compile
87
+ ```
88
+
89
+ To build the Phoenix installer:
90
+
91
+ ```bash
92
+ mix deps.get
93
+ mix compile
94
+ mix archive.build
95
+ ```
96
+
97
+ To build Phoenix.js:
98
+
99
+ ```bash
100
+ cd assets
101
+ npm install
102
+ ```
103
+
104
+ ## Important links
105
+
106
+ * [#elixir][1] on [Libera][2] IRC
107
+ * [elixir-lang Slack channel][3]
108
+ * [Issues tracker][4]
109
+ * [Phoenix Forum (questions and proposals)][5]
110
+ * Visit Phoenix's sponsor, DockYard, for expert [Phoenix Consulting](https://dockyard.com/phoenix-consulting)
111
+
112
+ [1]: https://web.libera.chat/?channels=#elixir
113
+ [2]: https://libera.chat/
114
+ [3]: https://elixir-lang.slack.com/
115
+ [4]: https://github.com/phoenixframework/phoenix/issues
116
+ [5]: https://elixirforum.com/c/phoenix-forum
117
+
118
+ ## Copyright and License
119
+
120
+ Copyright (c) 2014, Chris McCord.
121
+
122
+ Phoenix source code is licensed under the [MIT License](LICENSE.md).
@@ -0,0 +1,116 @@
1
+ import {
2
+ global,
3
+ XHR_STATES
4
+ } from "./constants"
5
+
6
+ export default class Ajax {
7
+
8
+ static request(method, endPoint, headers, body, timeout, ontimeout, callback){
9
+ if(global.XDomainRequest){
10
+ let req = new global.XDomainRequest() // IE8, IE9
11
+ return this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback)
12
+ } else if(global.XMLHttpRequest){
13
+ let req = new global.XMLHttpRequest() // IE7+, Firefox, Chrome, Opera, Safari
14
+ return this.xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback)
15
+ } else if(global.fetch && global.AbortController){
16
+ // Fetch with AbortController for modern browsers
17
+ return this.fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback)
18
+ } else {
19
+ throw new Error("No suitable XMLHttpRequest implementation found")
20
+ }
21
+ }
22
+
23
+ static fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback){
24
+ let options = {
25
+ method,
26
+ headers,
27
+ body,
28
+ }
29
+ let controller = null
30
+ if(timeout){
31
+ controller = new AbortController()
32
+ const _timeoutId = setTimeout(() => controller.abort(), timeout)
33
+ options.signal = controller.signal
34
+ }
35
+ global.fetch(endPoint, options)
36
+ .then(response => response.text())
37
+ .then(data => this.parseJSON(data))
38
+ .then(data => callback && callback(data))
39
+ .catch(err => {
40
+ if(err.name === "AbortError" && ontimeout){
41
+ ontimeout()
42
+ } else {
43
+ callback && callback(null)
44
+ }
45
+ })
46
+ return controller
47
+ }
48
+
49
+ static xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback){
50
+ req.timeout = timeout
51
+ req.open(method, endPoint)
52
+ req.onload = () => {
53
+ let response = this.parseJSON(req.responseText)
54
+ callback && callback(response)
55
+ }
56
+ if(ontimeout){ req.ontimeout = ontimeout }
57
+
58
+ // Work around bug in IE9 that requires an attached onprogress handler
59
+ req.onprogress = () => { }
60
+
61
+ req.send(body)
62
+ return req
63
+ }
64
+
65
+ static xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback){
66
+ req.open(method, endPoint, true)
67
+ req.timeout = timeout
68
+ for(let [key, value] of Object.entries(headers)){
69
+ req.setRequestHeader(key, value)
70
+ }
71
+ req.onerror = () => callback && callback(null)
72
+ req.onreadystatechange = () => {
73
+ if(req.readyState === XHR_STATES.complete && callback){
74
+ let response = this.parseJSON(req.responseText)
75
+ callback(response)
76
+ }
77
+ }
78
+ if(ontimeout){ req.ontimeout = ontimeout }
79
+
80
+ req.send(body)
81
+ return req
82
+ }
83
+
84
+ static parseJSON(resp){
85
+ if(!resp || resp === ""){ return null }
86
+
87
+ try {
88
+ return JSON.parse(resp)
89
+ } catch {
90
+ console && console.log("failed to parse JSON response", resp)
91
+ return null
92
+ }
93
+ }
94
+
95
+ static serialize(obj, parentKey){
96
+ let queryStr = []
97
+ for(var key in obj){
98
+ if(!Object.prototype.hasOwnProperty.call(obj, key)){ continue }
99
+ let paramKey = parentKey ? `${parentKey}[${key}]` : key
100
+ let paramVal = obj[key]
101
+ if(typeof paramVal === "object"){
102
+ queryStr.push(this.serialize(paramVal, paramKey))
103
+ } else {
104
+ queryStr.push(encodeURIComponent(paramKey) + "=" + encodeURIComponent(paramVal))
105
+ }
106
+ }
107
+ return queryStr.join("&")
108
+ }
109
+
110
+ static appendParams(url, params){
111
+ if(Object.keys(params).length === 0){ return url }
112
+
113
+ let prefix = url.match(/\?/) ? "&" : "?"
114
+ return `${url}${prefix}${this.serialize(params)}`
115
+ }
116
+ }
@@ -0,0 +1,331 @@
1
+ import {closure} from "./utils"
2
+ import {
3
+ CHANNEL_EVENTS,
4
+ CHANNEL_STATES,
5
+ } from "./constants"
6
+
7
+ import Push from "./push"
8
+ import Timer from "./timer"
9
+
10
+ /**
11
+ * @import Socket from "./socket"
12
+ * @import { ChannelState, Params, ChannelBindingCallback, ChannelOnMessage, ChannelFilterBindings, ChannelOnErrorCallback, ChannelBinding } from "./types"
13
+ */
14
+
15
+ export default class Channel {
16
+ /**
17
+ * @param {string} topic
18
+ * @param {Params | (() => Params)} params
19
+ * @param {Socket} socket
20
+ */
21
+ constructor(topic, params, socket){
22
+ /** @type{ChannelState} */
23
+ this.state = CHANNEL_STATES.closed
24
+ /** @type{string} */
25
+ this.topic = topic
26
+ /** @type{() => Params} */
27
+ this.params = closure(params || {})
28
+ /** @type {Socket} */
29
+ this.socket = socket
30
+ /** @type{ChannelBinding[]} */
31
+ this.bindings = []
32
+ /** @type{number} */
33
+ this.bindingRef = 0
34
+ /** @type{number} */
35
+ this.timeout = this.socket.timeout
36
+ /** @type{boolean} */
37
+ this.joinedOnce = false
38
+ /** @type{Push} */
39
+ this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout)
40
+ /** @type{Push[]} */
41
+ this.pushBuffer = []
42
+ /** @type{string[]} */
43
+ this.stateChangeRefs = []
44
+
45
+ /** @type{Timer} */
46
+ this.rejoinTimer = new Timer(() => {
47
+ if(this.socket.isConnected()){ this.rejoin() }
48
+ }, this.socket.rejoinAfterMs)
49
+ this.stateChangeRefs.push(this.socket.onError(() => this.rejoinTimer.reset()))
50
+ this.stateChangeRefs.push(this.socket.onOpen(() => {
51
+ this.rejoinTimer.reset()
52
+ if(this.isErrored()){ this.rejoin() }
53
+ })
54
+ )
55
+ this.joinPush.receive("ok", () => {
56
+ this.state = CHANNEL_STATES.joined
57
+ this.rejoinTimer.reset()
58
+ this.pushBuffer.forEach(pushEvent => pushEvent.send())
59
+ this.pushBuffer = []
60
+ })
61
+ this.joinPush.receive("error", (reason) => {
62
+ this.state = CHANNEL_STATES.errored
63
+ if(this.socket.hasLogger()) this.socket.log("channel", `error ${this.topic}`, reason)
64
+ if(this.socket.isConnected()){ this.rejoinTimer.scheduleTimeout() }
65
+ })
66
+ this.onClose(() => {
67
+ this.rejoinTimer.reset()
68
+ if(this.socket.hasLogger()) this.socket.log("channel", `close ${this.topic}`)
69
+ this.state = CHANNEL_STATES.closed
70
+ this.socket.remove(this)
71
+ })
72
+ this.onError(reason => {
73
+ if(this.socket.hasLogger()) this.socket.log("channel", `error ${this.topic}`, reason)
74
+ if(this.isJoining()){ this.joinPush.reset() }
75
+ this.state = CHANNEL_STATES.errored
76
+ if(this.socket.isConnected()){ this.rejoinTimer.scheduleTimeout() }
77
+ })
78
+ this.joinPush.receive("timeout", () => {
79
+ if(this.socket.hasLogger()) this.socket.log("channel", `timeout ${this.topic}`, this.joinPush.timeout)
80
+ let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout)
81
+ leavePush.send()
82
+ this.state = CHANNEL_STATES.errored
83
+ this.joinPush.reset()
84
+ if(this.socket.isConnected()){ this.rejoinTimer.scheduleTimeout() }
85
+ })
86
+ this.on(CHANNEL_EVENTS.reply, (payload, ref) => {
87
+ this.trigger(this.replyEventName(ref), payload)
88
+ })
89
+ }
90
+
91
+ /**
92
+ * Join the channel
93
+ * @param {number} timeout
94
+ * @returns {Push}
95
+ */
96
+ join(timeout = this.timeout){
97
+ if(this.joinedOnce){
98
+ throw new Error("tried to join multiple times. 'join' can only be called a single time per channel instance")
99
+ } else {
100
+ this.timeout = timeout
101
+ this.joinedOnce = true
102
+ this.rejoin()
103
+ return this.joinPush
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Teardown the channel.
109
+ *
110
+ * Destroys and stops related timers.
111
+ */
112
+ teardown(){
113
+ this.pushBuffer.forEach((push) => push.destroy())
114
+ this.pushBuffer = []
115
+ this.rejoinTimer.reset()
116
+ this.joinPush.destroy()
117
+ this.state = CHANNEL_STATES.closed
118
+ this.bindings = {}
119
+ }
120
+
121
+ /**
122
+ * Hook into channel close
123
+ * @param {ChannelBindingCallback} callback
124
+ */
125
+ onClose(callback){
126
+ this.on(CHANNEL_EVENTS.close, callback)
127
+ }
128
+
129
+ /**
130
+ * Hook into channel errors
131
+ * @param {ChannelOnErrorCallback} callback
132
+ * @return {number}
133
+ */
134
+ onError(callback){
135
+ return this.on(CHANNEL_EVENTS.error, reason => callback(reason))
136
+ }
137
+
138
+ /**
139
+ * Subscribes on channel events
140
+ *
141
+ * Subscription returns a ref counter, which can be used later to
142
+ * unsubscribe the exact event listener
143
+ *
144
+ * @example
145
+ * const ref1 = channel.on("event", do_stuff)
146
+ * const ref2 = channel.on("event", do_other_stuff)
147
+ * channel.off("event", ref1)
148
+ * // Since unsubscription, do_stuff won't fire,
149
+ * // while do_other_stuff will keep firing on the "event"
150
+ *
151
+ * @param {string} event
152
+ * @param {ChannelBindingCallback} callback
153
+ * @returns {number} ref
154
+ */
155
+ on(event, callback){
156
+ let ref = this.bindingRef++
157
+ this.bindings.push({event, ref, callback})
158
+ return ref
159
+ }
160
+
161
+ /**
162
+ * Unsubscribes off of channel events
163
+ *
164
+ * Use the ref returned from a channel.on() to unsubscribe one
165
+ * handler, or pass nothing for the ref to unsubscribe all
166
+ * handlers for the given event.
167
+ *
168
+ * @example
169
+ * // Unsubscribe the do_stuff handler
170
+ * const ref1 = channel.on("event", do_stuff)
171
+ * channel.off("event", ref1)
172
+ *
173
+ * // Unsubscribe all handlers from event
174
+ * channel.off("event")
175
+ *
176
+ * @param {string} event
177
+ * @param {number} [ref]
178
+ */
179
+ off(event, ref){
180
+ this.bindings = this.bindings.filter((bind) => {
181
+ return !(bind.event === event && (typeof ref === "undefined" || ref === bind.ref))
182
+ })
183
+ }
184
+
185
+ /**
186
+ * @private
187
+ */
188
+ canPush(){ return this.socket.isConnected() && this.isJoined() }
189
+
190
+ /**
191
+ * Sends a message `event` to phoenix with the payload `payload`.
192
+ * Phoenix receives this in the `handle_in(event, payload, socket)`
193
+ * function. if phoenix replies or it times out (default 10000ms),
194
+ * then optionally the reply can be received.
195
+ *
196
+ * @example
197
+ * channel.push("event")
198
+ * .receive("ok", payload => console.log("phoenix replied:", payload))
199
+ * .receive("error", err => console.log("phoenix errored", err))
200
+ * .receive("timeout", () => console.log("timed out pushing"))
201
+ * @param {string} event
202
+ * @param {Object} payload
203
+ * @param {number} [timeout]
204
+ * @returns {Push}
205
+ */
206
+ push(event, payload, timeout = this.timeout){
207
+ payload = payload || {}
208
+ if(!this.joinedOnce){
209
+ throw new Error(`tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`)
210
+ }
211
+ let pushEvent = new Push(this, event, function (){ return payload }, timeout)
212
+ if(this.canPush()){
213
+ pushEvent.send()
214
+ } else {
215
+ pushEvent.startTimeout()
216
+ this.pushBuffer.push(pushEvent)
217
+ }
218
+
219
+ return pushEvent
220
+ }
221
+
222
+ /** Leaves the channel
223
+ *
224
+ * Unsubscribes from server events, and
225
+ * instructs channel to terminate on server
226
+ *
227
+ * Triggers onClose() hooks
228
+ *
229
+ * To receive leave acknowledgements, use the `receive`
230
+ * hook to bind to the server ack, ie:
231
+ *
232
+ * @example
233
+ * channel.leave().receive("ok", () => alert("left!") )
234
+ *
235
+ * @param {number} timeout
236
+ * @returns {Push}
237
+ */
238
+ leave(timeout = this.timeout){
239
+ this.rejoinTimer.reset()
240
+ this.joinPush.cancelTimeout()
241
+
242
+ this.state = CHANNEL_STATES.leaving
243
+ let onClose = () => {
244
+ if(this.socket.hasLogger()) this.socket.log("channel", `leave ${this.topic}`)
245
+ this.trigger(CHANNEL_EVENTS.close, "leave")
246
+ }
247
+ let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout)
248
+ leavePush.receive("ok", () => onClose())
249
+ .receive("timeout", () => onClose())
250
+ leavePush.send()
251
+ if(!this.canPush()){ leavePush.trigger("ok", {}) }
252
+
253
+ return leavePush
254
+ }
255
+
256
+ /**
257
+ * Overridable message hook
258
+ *
259
+ * Receives all events for specialized message handling
260
+ * before dispatching to the channel callbacks.
261
+ *
262
+ * Must return the payload, modified or unmodified
263
+ * @type{ChannelOnMessage}
264
+ */
265
+ onMessage(_event, payload, _ref){ return payload }
266
+
267
+ /**
268
+ * Overridable filter hook
269
+ *
270
+ * If this function returns `true`, `binding`'s callback will be called.
271
+ *
272
+ * @type{ChannelFilterBindings}
273
+ */
274
+ filterBindings(_binding, _payload, _ref){ return true }
275
+
276
+ isMember(topic, event, payload, joinRef){
277
+ if(this.topic !== topic){ return false }
278
+
279
+ if(joinRef && joinRef !== this.joinRef()){
280
+ if(this.socket.hasLogger()) this.socket.log("channel", "dropping outdated message", {topic, event, payload, joinRef})
281
+ return false
282
+ } else {
283
+ return true
284
+ }
285
+ }
286
+
287
+ joinRef(){ return this.joinPush.ref }
288
+
289
+ /**
290
+ * @private
291
+ */
292
+ rejoin(timeout = this.timeout){
293
+ if(this.isLeaving()){ return }
294
+ this.socket.leaveOpenTopic(this.topic)
295
+ this.state = CHANNEL_STATES.joining
296
+ this.joinPush.resend(timeout)
297
+ }
298
+
299
+ /**
300
+ * @param {string} event
301
+ * @param {unknown} [payload]
302
+ * @param {?string} [ref]
303
+ * @param {?string} [joinRef]
304
+ */
305
+ trigger(event, payload, ref, joinRef){
306
+ let handledPayload = this.onMessage(event, payload, ref, joinRef)
307
+ if(payload && !handledPayload){ throw new Error("channel onMessage callbacks must return the payload, modified or unmodified") }
308
+
309
+ let eventBindings = this.bindings.filter(bind => bind.event === event && this.filterBindings(bind, payload, ref))
310
+
311
+ for(let i = 0; i < eventBindings.length; i++){
312
+ let bind = eventBindings[i]
313
+ bind.callback(handledPayload, ref, joinRef || this.joinRef())
314
+ }
315
+ }
316
+
317
+ /**
318
+ * @param {string} ref
319
+ */
320
+ replyEventName(ref){ return `chan_reply_${ref}` }
321
+
322
+ isClosed(){ return this.state === CHANNEL_STATES.closed }
323
+
324
+ isErrored(){ return this.state === CHANNEL_STATES.errored }
325
+
326
+ isJoined(){ return this.state === CHANNEL_STATES.joined }
327
+
328
+ isJoining(){ return this.state === CHANNEL_STATES.joining }
329
+
330
+ isLeaving(){ return this.state === CHANNEL_STATES.leaving }
331
+ }
@@ -0,0 +1,35 @@
1
+ export const globalSelf = typeof self !== "undefined" ? self : null
2
+ export const phxWindow = typeof window !== "undefined" ? window : null
3
+ export const global = globalSelf || phxWindow || globalThis
4
+ export const DEFAULT_VSN = "2.0.0"
5
+ export const DEFAULT_TIMEOUT = 10000
6
+ export const WS_CLOSE_NORMAL = 1000
7
+
8
+ export const SOCKET_STATES = /** @type {const} */ ({connecting: 0, open: 1, closing: 2, closed: 3})
9
+
10
+ export const CHANNEL_STATES = /** @type {const} */ ({
11
+ closed: "closed",
12
+ errored: "errored",
13
+ joined: "joined",
14
+ joining: "joining",
15
+ leaving: "leaving",
16
+ })
17
+
18
+ export const CHANNEL_EVENTS = /** @type {const} */ ({
19
+ close: "phx_close",
20
+ error: "phx_error",
21
+ join: "phx_join",
22
+ reply: "phx_reply",
23
+ leave: "phx_leave"
24
+ })
25
+
26
+ export const TRANSPORTS = /** @type {const} */ ({
27
+ longpoll: "longpoll",
28
+ websocket: "websocket"
29
+ })
30
+
31
+ export const XHR_STATES = /** @type {const} */ ({
32
+ complete: 4
33
+ })
34
+
35
+ export const AUTH_TOKEN_PREFIX = "base64url.bearer.phx."