@quiltt/core 2.1.0 → 2.1.2

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 (67) hide show
  1. package/CHANGELOG.md +352 -0
  2. package/README.md +32 -9
  3. package/dist/{VersionLink-2203a632.d.ts → VersionLink-b7dfbaba.d.ts} +1 -1
  4. package/dist/api/graphql/index.cjs +4 -4
  5. package/dist/api/graphql/index.cjs.map +1 -1
  6. package/dist/api/graphql/index.d.ts +1 -1
  7. package/dist/api/graphql/index.js +3 -3
  8. package/dist/api/graphql/index.js.map +1 -1
  9. package/dist/api/graphql/links/actioncable/index.cjs +4 -4
  10. package/dist/api/graphql/links/actioncable/index.cjs.map +1 -1
  11. package/dist/api/graphql/links/actioncable/index.js +2 -2
  12. package/dist/api/graphql/links/actioncable/index.js.map +1 -1
  13. package/dist/api/graphql/links/index.cjs +2 -2
  14. package/dist/api/graphql/links/index.cjs.map +1 -1
  15. package/dist/api/graphql/links/index.d.ts +1 -1
  16. package/dist/api/graphql/links/index.js +2 -2
  17. package/dist/api/graphql/links/index.js.map +1 -1
  18. package/dist/api/index.cjs +1 -1
  19. package/dist/api/index.cjs.map +1 -1
  20. package/dist/api/index.d.ts +1 -1
  21. package/dist/api/index.js +1 -1
  22. package/dist/api/index.js.map +1 -1
  23. package/dist/api/rest/index.cjs +1 -1
  24. package/dist/api/rest/index.cjs.map +1 -1
  25. package/dist/api/rest/index.js +1 -1
  26. package/dist/api/rest/index.js.map +1 -1
  27. package/dist/index.cjs +5 -5
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.ts +1 -1
  30. package/dist/index.js +2 -2
  31. package/dist/index.js.map +1 -1
  32. package/package.json +10 -2
  33. package/src/JsonWebToken.ts +48 -0
  34. package/src/Observable.ts +39 -0
  35. package/src/Storage/Local.ts +92 -0
  36. package/src/Storage/Memory.ts +43 -0
  37. package/src/Storage/index.ts +93 -0
  38. package/src/Timeoutable.ts +32 -0
  39. package/src/api/graphql/client.ts +48 -0
  40. package/src/api/graphql/index.ts +2 -0
  41. package/src/api/graphql/links/ActionCableLink.ts +93 -0
  42. package/src/api/graphql/links/AuthLink.ts +32 -0
  43. package/src/api/graphql/links/BatchHttpLink.ts +12 -0
  44. package/src/api/graphql/links/ErrorLink.ts +23 -0
  45. package/src/api/graphql/links/ForwardableLink.ts +5 -0
  46. package/src/api/graphql/links/HttpLink.ts +12 -0
  47. package/src/api/graphql/links/RetryLink.ts +10 -0
  48. package/src/api/graphql/links/SubscriptionLink.ts +11 -0
  49. package/src/api/graphql/links/TerminatingLink.ts +5 -0
  50. package/src/api/graphql/links/VersionLink.ts +15 -0
  51. package/src/api/graphql/links/actioncable/adapters.ts +4 -0
  52. package/src/api/graphql/links/actioncable/connection.ts +191 -0
  53. package/src/api/graphql/links/actioncable/connection_monitor.ts +139 -0
  54. package/src/api/graphql/links/actioncable/consumer.ts +87 -0
  55. package/src/api/graphql/links/actioncable/index.ts +35 -0
  56. package/src/api/graphql/links/actioncable/internal.ts +19 -0
  57. package/src/api/graphql/links/actioncable/logger.ts +16 -0
  58. package/src/api/graphql/links/actioncable/subscription.ts +44 -0
  59. package/src/api/graphql/links/actioncable/subscription_guarantor.ts +54 -0
  60. package/src/api/graphql/links/actioncable/subscriptions.ts +113 -0
  61. package/src/api/graphql/links/index.ts +9 -0
  62. package/src/api/index.ts +2 -0
  63. package/src/api/rest/AuthAPI.ts +115 -0
  64. package/src/api/rest/index.ts +1 -0
  65. package/src/configuration.ts +45 -0
  66. package/src/index.ts +6 -0
  67. package/src/types.ts +10 -0
@@ -0,0 +1,93 @@
1
+ import type { Maybe } from '../types'
2
+
3
+ import type { Observer } from '../Observable'
4
+ import { LocalStorage } from './Local'
5
+ import { MemoryStorage } from './Memory'
6
+
7
+ /**
8
+ * This is wraps both local and memory storage to create a unified interface, that
9
+ * allows you to subscribe to all either changes made within this window, or changes
10
+ * made by other windows.
11
+ */
12
+ export class Storage<T> {
13
+ private memoryStore = new MemoryStorage<T>()
14
+ private localStore = new LocalStorage<T>()
15
+ private observers: { [key: string]: Observer<T>[] } = {}
16
+ private monitors: Set<string> = new Set()
17
+
18
+ /**
19
+ * Checks memoryStorage before falling back to localStorage.
20
+ */
21
+ get = (key: string): Maybe<T> | undefined => {
22
+ this.monitorLocalStorageChanges(key)
23
+
24
+ let state = this.memoryStore.get(key)
25
+
26
+ if (state === undefined) {
27
+ state = this.localStore.get(key)
28
+ }
29
+
30
+ return state
31
+ }
32
+
33
+ /**
34
+ * We don't trust localStorage to always be present, so we can't rely on it to
35
+ * update memoryStorage based on emitted changes. So we manage our own
36
+ * emitting while using the underlying events to keep memoryStore in sync with
37
+ * localStore.
38
+ */
39
+ set = (key: string, newState: Maybe<T> | undefined) => {
40
+ this.monitorLocalStorageChanges(key)
41
+
42
+ this.localStore.set(key, newState)
43
+ this.memoryStore.set(key, newState)
44
+
45
+ this.observers[key]?.forEach((update) => update(newState))
46
+ }
47
+
48
+ /**
49
+ * Allows you to subscribe to all changes in memory or local storage as a
50
+ * single event.
51
+ */
52
+ subscribe = (key: string, observer: Observer<T>) => {
53
+ if (!this.observers[key]) this.observers[key] = []
54
+
55
+ this.observers[key].push(observer)
56
+ }
57
+
58
+ unsubscribe = (key: string, observer: Observer<T>) => {
59
+ this.observers[key] = this.observers[key]?.filter((update) => update !== observer)
60
+ }
61
+
62
+ /**
63
+ * Sets bubble the changes down the stack starting with memoryStore and then
64
+ * localStore. memoryStore will emit changes to everything within the current
65
+ * window context, while localStore will emit changes to every other window
66
+ * context.
67
+ *
68
+ * To ensure that the other windows are updated correctly, changes to localStore
69
+ * need to be subscribed and updated to in memory store, which then may be subscribed
70
+ * to outside of storage.
71
+ */
72
+ private monitorLocalStorageChanges = (key: string) => {
73
+ if (this.monitors.has(key)) return
74
+ this.monitors.add(key)
75
+
76
+ this.localStore.subscribe(key, (nextState) => {
77
+ const prevValue = this.memoryStore.get(key)
78
+ const newState = nextState instanceof Function ? nextState(prevValue) : nextState
79
+
80
+ this.memoryStore.set(key, newState)
81
+ this.observers[key]?.forEach((update) => update(newState))
82
+ })
83
+ }
84
+ }
85
+
86
+ export * from './Local'
87
+ export * from './Memory'
88
+
89
+ /**
90
+ * This is an singleton to share the memory states across all instances; This
91
+ * basically acts like shared memory when there is no localStorage.
92
+ */
93
+ export const GlobalStorage = new Storage<any>()
@@ -0,0 +1,32 @@
1
+ import type { Observer } from './Observable'
2
+
3
+ /**
4
+ * This is designed to support singletons to timeouts that can broadcast
5
+ * to any observers, preventing race conditions with multiple timeouts.
6
+ */
7
+ export class Timeoutable {
8
+ private timeout?: ReturnType<typeof setTimeout>
9
+ private observers: Observer<void>[] = []
10
+
11
+ set = (callback: () => void, delay: number | undefined) => {
12
+ if (this.timeout) {
13
+ clearTimeout(this.timeout)
14
+ }
15
+
16
+ this.observers.push(callback)
17
+ this.timeout = setTimeout(this.broadcast.bind(this), delay)
18
+ }
19
+
20
+ clear = (observer: Observer<void>) => {
21
+ this.observers = this.observers.filter((callback) => callback !== observer)
22
+ }
23
+
24
+ // Only sends to the 1st listener, but ensures that someone is notified
25
+ private broadcast = () => {
26
+ if (this.observers.length === 0) return
27
+
28
+ this.observers[0](undefined)
29
+ }
30
+ }
31
+
32
+ export default Timeoutable
@@ -0,0 +1,48 @@
1
+ import type { ApolloClientOptions, Operation } from '@apollo/client/index.js'
2
+ import { ApolloClient, ApolloLink } from '@apollo/client/index.js'
3
+
4
+ import { debugging } from '../../configuration'
5
+ import {
6
+ AuthLink,
7
+ BatchHttpLink,
8
+ ErrorLink,
9
+ ForwardableLink,
10
+ HttpLink,
11
+ RetryLink,
12
+ SubscriptionLink,
13
+ VersionLink,
14
+ } from './links'
15
+
16
+ export type QuilttClientOptions<T> = Omit<ApolloClientOptions<T>, 'link'>
17
+
18
+ export class QuilttClient<T> extends ApolloClient<T> {
19
+ constructor(options: QuilttClientOptions<T>) {
20
+ if (!options.connectToDevTools) options.connectToDevTools = debugging
21
+
22
+ const isSubscriptionOperation = (operation: Operation) => {
23
+ return operation.query.definitions.some(
24
+ // @ts-ignore
25
+ ({ kind, operation }) => kind === 'OperationDefinition' && operation === 'subscription'
26
+ )
27
+ }
28
+
29
+ const isBatchable = (operation: Operation) => {
30
+ return operation.getContext().batchable ?? true
31
+ }
32
+
33
+ const authLink = new AuthLink()
34
+ const subscriptionsLink = new SubscriptionLink()
35
+
36
+ const quilttLink = ApolloLink.from([VersionLink, authLink, ErrorLink, RetryLink])
37
+ .split(isSubscriptionOperation, subscriptionsLink, ForwardableLink)
38
+ .split(isBatchable, BatchHttpLink, HttpLink)
39
+
40
+ super({
41
+ link: quilttLink,
42
+ ...options,
43
+ })
44
+ }
45
+ }
46
+
47
+ export { InMemoryCache, gql, useMutation, useQuery, useSubscription } from '@apollo/client/index.js'
48
+ export type { NormalizedCacheObject, OperationVariables } from '@apollo/client/index.js'
@@ -0,0 +1,2 @@
1
+ export * from './links'
2
+ export * from './client'
@@ -0,0 +1,93 @@
1
+ import { GlobalStorage } from '@/Storage'
2
+ import type { FetchResult, NextLink, Operation } from '@apollo/client/core/index.js'
3
+ import { ApolloLink, Observable } from '@apollo/client/core/index.js'
4
+ import { print } from 'graphql'
5
+ import { endpointWebsockets } from '../../../configuration'
6
+ import type { Consumer } from './actioncable'
7
+ import { createConsumer } from './actioncable'
8
+
9
+ type RequestResult = FetchResult<
10
+ { [key: string]: unknown },
11
+ Record<string, unknown>,
12
+ Record<string, unknown>
13
+ >
14
+ type ConnectionParams = object | ((operation: Operation) => object)
15
+
16
+ class ActionCableLink extends ApolloLink {
17
+ cables: { [id: string]: Consumer }
18
+ channelName: string
19
+ actionName: string
20
+ connectionParams: ConnectionParams
21
+
22
+ constructor(options: {
23
+ channelName?: string
24
+ actionName?: string
25
+ connectionParams?: ConnectionParams
26
+ }) {
27
+ super()
28
+ this.cables = {}
29
+ this.channelName = options.channelName || 'GraphqlChannel'
30
+ this.actionName = options.actionName || 'execute'
31
+ this.connectionParams = options.connectionParams || {}
32
+ }
33
+
34
+ // Interestingly, this link does _not_ call through to `next` because
35
+ // instead, it sends the request to ActionCable.
36
+ request(operation: Operation, _next: NextLink): Observable<RequestResult> | null {
37
+ const token = GlobalStorage.get('session')
38
+
39
+ if (!token) {
40
+ console.warn(`QuilttLink attempted to send an unauthenticated Subscription`)
41
+ return null
42
+ }
43
+
44
+ if (!this.cables[token]) {
45
+ this.cables[token] = createConsumer(endpointWebsockets + (token ? `?token=${token}` : ''))
46
+ }
47
+
48
+ return new Observable((observer) => {
49
+ const channelId = Math.round(Date.now() + Math.random() * 100000).toString(16)
50
+ const actionName = this.actionName
51
+ const connectionParams =
52
+ typeof this.connectionParams === 'function'
53
+ ? this.connectionParams(operation)
54
+ : this.connectionParams
55
+
56
+ const channel = this.cables[token].subscriptions.create(
57
+ Object.assign(
58
+ {},
59
+ {
60
+ channel: this.channelName,
61
+ channelId: channelId,
62
+ },
63
+ connectionParams
64
+ ),
65
+ {
66
+ connected: () => {
67
+ channel.perform(actionName, {
68
+ query: operation.query ? print(operation.query) : null,
69
+ variables: operation.variables,
70
+ // This is added for persisted operation support:
71
+ operationId: (operation as { operationId?: string }).operationId,
72
+ operationName: operation.operationName,
73
+ })
74
+ },
75
+
76
+ received: (payload: { result: RequestResult; more: any }) => {
77
+ if (payload?.result?.data || payload?.result?.errors) {
78
+ observer.next(payload.result)
79
+ }
80
+
81
+ if (!payload.more) {
82
+ observer.complete()
83
+ }
84
+ },
85
+ }
86
+ )
87
+ // Make the ActionCable subscription behave like an Apollo subscription
88
+ return Object.assign(channel, { closed: false })
89
+ })
90
+ }
91
+ }
92
+
93
+ export default ActionCableLink
@@ -0,0 +1,32 @@
1
+ import type { FetchResult, NextLink, Observable, Operation } from '@apollo/client/index.js'
2
+ import { ApolloLink } from '@apollo/client/index.js'
3
+
4
+ import { GlobalStorage } from '@/Storage'
5
+
6
+ /**
7
+ * unauthorizedCallback only triggers in the event the token is present, and
8
+ * returns the token; This allows sessions to be forgotten without race conditions
9
+ * causing null sessions to kill valid sessions, or invalid sessions for killing
10
+ * valid sessions during rotation and networking weirdness.
11
+ */
12
+ export class AuthLink extends ApolloLink {
13
+ request(operation: Operation, forward: NextLink): Observable<FetchResult> | null {
14
+ const token = GlobalStorage.get('session')
15
+
16
+ if (!token) {
17
+ console.warn(`QuilttLink attempted to send an unauthenticated Query`)
18
+ return null
19
+ }
20
+
21
+ operation.setContext(({ headers = {} }) => ({
22
+ headers: {
23
+ ...headers,
24
+ authorization: `Bearer ${token}`,
25
+ },
26
+ }))
27
+
28
+ return forward(operation)
29
+ }
30
+ }
31
+
32
+ export default AuthLink
@@ -0,0 +1,12 @@
1
+ import { BatchHttpLink as ApolloHttpLink } from '@apollo/client/link/batch-http/index.js'
2
+
3
+ import fetch from 'cross-fetch'
4
+
5
+ import { endpointGraphQL } from '../../../configuration'
6
+
7
+ export const BatchHttpLink = new ApolloHttpLink({
8
+ uri: endpointGraphQL,
9
+ fetch,
10
+ })
11
+
12
+ export default BatchHttpLink
@@ -0,0 +1,23 @@
1
+ import { GlobalStorage } from '@/Storage'
2
+
3
+ import type { ServerError } from '@apollo/client/index.js'
4
+ import { onError } from '@apollo/client/link/error/index.js'
5
+
6
+ export const ErrorLink = onError(({ graphQLErrors, networkError }) => {
7
+ if (graphQLErrors) {
8
+ graphQLErrors.forEach(({ message, locations, path }) => {
9
+ console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`)
10
+ })
11
+ }
12
+
13
+ if (networkError) {
14
+ if ((networkError as ServerError).statusCode === 401) {
15
+ console.warn('[Authentication error]:', networkError)
16
+ GlobalStorage.set('session', null)
17
+ } else {
18
+ console.warn('[Network error]:', networkError)
19
+ }
20
+ }
21
+ })
22
+
23
+ export default ErrorLink
@@ -0,0 +1,5 @@
1
+ import { ApolloLink } from '@apollo/client/index.js'
2
+
3
+ export const ForwardableLink = new ApolloLink((operation, forward) => forward(operation))
4
+
5
+ export default ForwardableLink
@@ -0,0 +1,12 @@
1
+ import { HttpLink as ApolloHttpLink } from '@apollo/client/link/http/index.js'
2
+
3
+ import fetch from 'cross-fetch'
4
+
5
+ import { endpointGraphQL } from '../../../configuration'
6
+
7
+ export const HttpLink = new ApolloHttpLink({
8
+ uri: endpointGraphQL,
9
+ fetch,
10
+ })
11
+
12
+ export default HttpLink
@@ -0,0 +1,10 @@
1
+ import type { ServerError } from '@apollo/client/index.js'
2
+ import { RetryLink as ApolloRetryLink } from '@apollo/client/link/retry/index.js'
3
+
4
+ export const RetryLink = new ApolloRetryLink({
5
+ attempts: {
6
+ retryIf: (error: ServerError, _operation) => error.statusCode >= 500,
7
+ },
8
+ })
9
+
10
+ export default RetryLink
@@ -0,0 +1,11 @@
1
+ 'use client'
2
+
3
+ import ActionCableLink from './ActionCableLink'
4
+
5
+ export class SubscriptionLink extends ActionCableLink {
6
+ constructor() {
7
+ super({ channelName: 'GraphQLChannel' })
8
+ }
9
+ }
10
+
11
+ export default SubscriptionLink
@@ -0,0 +1,5 @@
1
+ import { ApolloLink } from '@apollo/client/index.js'
2
+
3
+ export const TerminatingLink = new ApolloLink(() => null)
4
+
5
+ export default TerminatingLink
@@ -0,0 +1,15 @@
1
+ import { ApolloLink } from '@apollo/client/index.js'
2
+
3
+ import { version } from '../../../configuration'
4
+
5
+ export const VersionLink = new ApolloLink((operation, forward) => {
6
+ operation.setContext(({ headers = {} }) => ({
7
+ headers: {
8
+ ...headers,
9
+ 'Quiltt-Client-Version': version,
10
+ },
11
+ }))
12
+ return forward(operation)
13
+ })
14
+
15
+ export default VersionLink
@@ -0,0 +1,4 @@
1
+ export default {
2
+ logger: typeof globalThis !== 'undefined' ? globalThis.console : undefined,
3
+ WebSocket: typeof globalThis !== 'undefined' ? globalThis.WebSocket : undefined,
4
+ }
@@ -0,0 +1,191 @@
1
+ // @ts-nocheck
2
+ import adapters from './adapters'
3
+ import ConnectionMonitor from './connection_monitor'
4
+ import INTERNAL from './internal'
5
+ import logger from './logger'
6
+
7
+ // Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation.
8
+
9
+ const { message_types, protocols } = INTERNAL
10
+ const supportedProtocols = protocols.slice(0, protocols.length - 1)
11
+
12
+ const indexOf = [].indexOf
13
+
14
+ class Connection {
15
+ constructor(consumer) {
16
+ this.open = this.open.bind(this)
17
+ this.consumer = consumer
18
+ this.subscriptions = this.consumer.subscriptions
19
+ this.monitor = new ConnectionMonitor(this)
20
+ this.disconnected = true
21
+ }
22
+
23
+ send(data) {
24
+ if (this.isOpen()) {
25
+ this.webSocket.send(JSON.stringify(data))
26
+ return true
27
+ } else {
28
+ return false
29
+ }
30
+ }
31
+
32
+ open() {
33
+ if (this.isActive()) {
34
+ logger.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`)
35
+ return false
36
+ } else {
37
+ const socketProtocols = [...protocols, ...(this.consumer.subprotocols || [])]
38
+ logger.log(
39
+ `Opening WebSocket, current state is ${this.getState()}, subprotocols: ${socketProtocols}`
40
+ )
41
+ if (this.webSocket) {
42
+ this.uninstallEventHandlers()
43
+ }
44
+ this.webSocket = new adapters.WebSocket(this.consumer.url, socketProtocols)
45
+ this.installEventHandlers()
46
+ this.monitor.start()
47
+ return true
48
+ }
49
+ }
50
+
51
+ close({ allowReconnect } = { allowReconnect: true }) {
52
+ if (!allowReconnect) {
53
+ this.monitor.stop()
54
+ }
55
+ // Avoid closing websockets in a "connecting" state due to Safari 15.1+ bug. See: https://github.com/rails/rails/issues/43835#issuecomment-1002288478
56
+ if (this.isOpen()) {
57
+ return this.webSocket.close()
58
+ }
59
+ }
60
+
61
+ reopen() {
62
+ logger.log(`Reopening WebSocket, current state is ${this.getState()}`)
63
+ if (this.isActive()) {
64
+ try {
65
+ return this.close()
66
+ } catch (error) {
67
+ logger.log('Failed to reopen WebSocket', error)
68
+ } finally {
69
+ logger.log(`Reopening WebSocket in ${this.constructor.reopenDelay}ms`)
70
+ setTimeout(this.open, this.constructor.reopenDelay)
71
+ }
72
+ } else {
73
+ return this.open()
74
+ }
75
+ }
76
+
77
+ getProtocol() {
78
+ if (this.webSocket) {
79
+ return this.webSocket.protocol
80
+ }
81
+ }
82
+
83
+ isOpen() {
84
+ return this.isState('open')
85
+ }
86
+
87
+ isActive() {
88
+ return this.isState('open', 'connecting')
89
+ }
90
+
91
+ triedToReconnect() {
92
+ return this.monitor.reconnectAttempts > 0
93
+ }
94
+
95
+ // Private
96
+
97
+ isProtocolSupported() {
98
+ return indexOf.call(supportedProtocols, this.getProtocol()) >= 0
99
+ }
100
+
101
+ isState(...states) {
102
+ return indexOf.call(states, this.getState()) >= 0
103
+ }
104
+
105
+ getState() {
106
+ if (this.webSocket) {
107
+ for (const state in adapters.WebSocket) {
108
+ if (adapters.WebSocket[state] === this.webSocket.readyState) {
109
+ return state.toLowerCase()
110
+ }
111
+ }
112
+ }
113
+ return null
114
+ }
115
+
116
+ installEventHandlers() {
117
+ for (const eventName in this.events) {
118
+ const handler = this.events[eventName].bind(this)
119
+ this.webSocket[`on${eventName}`] = handler
120
+ }
121
+ }
122
+
123
+ uninstallEventHandlers() {
124
+ for (const eventName in this.events) {
125
+ this.webSocket[`on${eventName}`] = function () {}
126
+ }
127
+ }
128
+ }
129
+
130
+ Connection.reopenDelay = 500
131
+
132
+ Connection.prototype.events = {
133
+ message(event) {
134
+ if (!this.isProtocolSupported()) {
135
+ return
136
+ }
137
+ const { identifier, message, reason, reconnect, type } = JSON.parse(event.data)
138
+ switch (type) {
139
+ case message_types.welcome:
140
+ if (this.triedToReconnect()) {
141
+ this.reconnectAttempted = true
142
+ }
143
+ this.monitor.recordConnect()
144
+ return this.subscriptions.reload()
145
+ case message_types.disconnect:
146
+ logger.log(`Disconnecting. Reason: ${reason}`)
147
+ return this.close({ allowReconnect: reconnect })
148
+ case message_types.ping:
149
+ return this.monitor.recordPing()
150
+ case message_types.confirmation:
151
+ this.subscriptions.confirmSubscription(identifier)
152
+ if (this.reconnectAttempted) {
153
+ this.reconnectAttempted = false
154
+ return this.subscriptions.notify(identifier, 'connected', { reconnected: true })
155
+ } else {
156
+ return this.subscriptions.notify(identifier, 'connected', { reconnected: false })
157
+ }
158
+ case message_types.rejection:
159
+ return this.subscriptions.reject(identifier)
160
+ default:
161
+ return this.subscriptions.notify(identifier, 'received', message)
162
+ }
163
+ },
164
+
165
+ open() {
166
+ logger.log(`WebSocket onopen event, using '${this.getProtocol()}' subprotocol`)
167
+ this.disconnected = false
168
+ if (!this.isProtocolSupported()) {
169
+ logger.log('Protocol is unsupported. Stopping monitor and disconnecting.')
170
+ return this.close({ allowReconnect: false })
171
+ }
172
+ },
173
+
174
+ close(_event) {
175
+ logger.log('WebSocket onclose event')
176
+ if (this.disconnected) {
177
+ return
178
+ }
179
+ this.disconnected = true
180
+ this.monitor.recordDisconnect()
181
+ return this.subscriptions.notifyAll('disconnected', {
182
+ willAttemptReconnect: this.monitor.isRunning(),
183
+ })
184
+ },
185
+
186
+ error() {
187
+ logger.log('WebSocket onerror event')
188
+ },
189
+ }
190
+
191
+ export default Connection