Skip to content

Commit

Permalink
feat: Add private channel CRUD operations (#280)
Browse files Browse the repository at this point in the history
* feat: Add channel creation endpoint

* Fix the test for createChannel.

* improve tests

* add apikey to url

* add other crud operations

* * add strong random crypto for channel name generation
* abstract http endpoint definition from socket url to single function

* remove random name generation

* change update request to patch; auth made with header

---------

Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
  • Loading branch information
2 people authored and w3b6x9 committed Apr 3, 2024
1 parent a1d4932 commit 5df4572
Show file tree
Hide file tree
Showing 6 changed files with 968 additions and 688 deletions.
88 changes: 70 additions & 18 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,4 @@
"typedoc": "^0.22.16",
"typescript": "^4.0.3"
}
}
}
11 changes: 3 additions & 8 deletions src/RealtimeChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
RealtimePresenceState,
} from './RealtimePresence'
import * as Transformers from './lib/transformers'

import { httpEndpointURL } from './lib/transformers'
export type RealtimeChannelOptions = {
config: {
/**
Expand Down Expand Up @@ -191,7 +191,8 @@ export default class RealtimeChannel {

this.presence = new RealtimePresence(this)

this.broadcastEndpointURL = this._broadcastEndpointURL()
this.broadcastEndpointURL =
httpEndpointURL(this.socket.endPoint) + '/api/broadcast'
}

/** Subscribe registers your client with the server */
Expand Down Expand Up @@ -528,12 +529,6 @@ export default class RealtimeChannel {
}

/** @internal */
_broadcastEndpointURL(): string {
let url = this.socket.endPoint
url = url.replace(/^ws/i, 'http')
url = url.replace(/(\/socket\/websocket|\/socket|\/websocket)\/?$/i, '')
return url.replace(/\/+$/, '') + '/api/broadcast'
}

async _fetchWithTimeout(
url: string,
Expand Down
106 changes: 97 additions & 9 deletions src/RealtimeClient.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import type { WebSocket as WSWebSocket } from 'ws'

import {
VSN,
CHANNEL_EVENTS,
TRANSPORTS,
SOCKET_STATES,
CONNECTION_STATE,
DEFAULT_HEADERS,
DEFAULT_TIMEOUT,
SOCKET_STATES,
TRANSPORTS,
VSN,
WS_CLOSE_NORMAL,
DEFAULT_HEADERS,
CONNECTION_STATE,
} from './lib/constants'
import Timer from './lib/timer'
import Serializer from './lib/serializer'
import Timer from './lib/timer'

import { httpEndpointURL } from './lib/transformers'
import RealtimeChannel from './RealtimeChannel'
import type { RealtimeChannelOptions } from './RealtimeChannel'

import type { WebSocket as WSWebSocket } from 'ws'

type Fetch = typeof fetch

export type RealtimeClientOptions = {
Expand Down Expand Up @@ -66,6 +68,7 @@ export default class RealtimeClient {
apiKey: string | null = null
channels: RealtimeChannel[] = []
endPoint: string = ''
httpEndpoint: string = ''
headers?: { [key: string]: string } = DEFAULT_HEADERS
params?: { [key: string]: string } = {}
timeout: number = DEFAULT_TIMEOUT
Expand Down Expand Up @@ -99,6 +102,7 @@ export default class RealtimeClient {
* Initializes the Socket.
*
* @param endPoint The string WebSocket endpoint, ie, "ws://example.com/socket", "wss://example.com", "/socket" (inherited host & protocol)
* @param httpEndpoint The string HTTP endpoint, ie, "https://example.com", "/" (inherited host & protocol)
* @param options.transport The Websocket Transport, for example WebSocket.
* @param options.timeout The default timeout in milliseconds to trigger push timeouts.
* @param options.params The optional params to pass when connecting.
Expand All @@ -111,7 +115,7 @@ export default class RealtimeClient {
*/
constructor(endPoint: string, options?: RealtimeClientOptions) {
this.endPoint = `${endPoint}/${TRANSPORTS.websocket}`

this.httpEndpoint = httpEndpointURL(endPoint)
if (options?.transport) {
this.transport = options.transport
} else {
Expand Down Expand Up @@ -317,6 +321,90 @@ export default class RealtimeClient {
})
}

/**
* Creates a private channel to be used with the provided name.
*
* @param name Channel name to create
*/
createPrivateChannel(name: string): Promise<string> {
const url = `${this.httpEndpoint}/channels`
return this.fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.accessToken}`,
},
body: JSON.stringify({ name }),
}).then((response) => {
if (!response.ok && response.status !== 200) {
throw new Error(response.statusText)
}
return name
})
}

/**
* Deletes a private channel
*
* @param name Channel name to delete.
*/
deletePrivateChannel(name: string): Promise<boolean> {
const url = `${this.httpEndpoint}/channels/${name}`
return this.fetch(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.accessToken}`,
},
body: JSON.stringify({ name }),
}).then((response) => {
if (!response.ok && response.status !== 202) {
throw new Error(response.statusText)
}
return true
})
}

/**
* Update a private channel
*
* @param name Channel name to update.
* @param new_name New channel name.
*/
updatePrivateChannel(name: string, new_name: string): Promise<string> {
const url = `${this.httpEndpoint}/channels/${name}`
return this.fetch(url, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.accessToken}`,
},
body: JSON.stringify({ name: new_name }),
}).then((response) => {
if (!response.ok && response.status !== 202) {
throw new Error(response.statusText)
}
return new_name
})
}

/**
* Lists private channels
*/
listPrivateChannels(): Promise<string[]> {
const url = `${this.httpEndpoint}/channels`
return this.fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${this.accessToken}`,
},
}).then((response) => {
if (!response.ok && response.status !== 200) {
throw new Error(response.statusText)
}
return response.json()
})
}
/**
* Use either custom fetch, if provided, or default fetch to make HTTP requests
*
Expand Down
7 changes: 7 additions & 0 deletions src/lib/transformers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,10 @@ export const toTimestampString = (value: RecordValue): RecordValue => {

return value
}

export const httpEndpointURL = (socketUrl: string): string => {
let url = socketUrl
url = url.replace(/^ws/i, 'http')
url = url.replace(/(\/socket\/websocket|\/socket|\/websocket)\/?$/i, '')
return url.replace(/\/+$/, '')
}
Loading

0 comments on commit 5df4572

Please sign in to comment.