Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ably connector #1

Merged
merged 112 commits into from
Jul 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
112 commits
Select commit Hold shift + click to select a range
dbb6090
Add Ably as dev dependency
owenpearson May 4, 2022
bb6c747
Add typing to interact with window.Ably
owenpearson May 4, 2022
404456c
Add AblyChannel implementation
owenpearson May 4, 2022
e4279b6
Add AblyPresenceChannel implementation
owenpearson May 4, 2022
96c44c4
Add AblyConnector implementation
owenpearson May 4, 2022
376cce4
Type Ably as any
owenpearson May 4, 2022
8b2a970
Accept 'ably' as options.broadcaster
owenpearson May 4, 2022
02eb2d6
Remove unneeded ts declaration
owenpearson May 5, 2022
b6f86a8
Refactored channels, added proper namespaces to channels
sacOO7 May 29, 2022
d6b94a0
Refactored namespace for channel leave
sacOO7 May 29, 2022
b88bb01
refactored ablyconnector, removed unnecessary encrypted channel
sacOO7 May 31, 2022
b1210e9
Refactored ably-channel, updated subscribe and error listeners
sacOO7 May 31, 2022
ef7b636
Added ably private channel
sacOO7 May 31, 2022
a6cba23
Added TODO for AblyPresence Channle whisper
sacOO7 May 31, 2022
d2c3884
Fixed methods for creating channels
sacOO7 May 31, 2022
322f81b
Updated presence channel, reformatted code
sacOO7 May 31, 2022
0e91140
Reformatted ablyChannel class file
sacOO7 May 31, 2022
441f7ef
Fixed error listener for AblyChannel
sacOO7 May 31, 2022
7a8b28a
Fixed ably imports for ably-connector
sacOO7 May 31, 2022
28fb62a
Added basic auth JWT utils
sacOO7 May 31, 2022
d8da3d2
Added patch for executing channel auth before channel attach
sacOO7 May 31, 2022
170b0c9
Added mock server for generating JWT for given channelName
sacOO7 May 31, 2022
e46b474
Added sequential token request executer
sacOO7 May 31, 2022
0191ac4
Added auth related methods for authz of private/presence channels
sacOO7 May 31, 2022
c31be4d
Added channel auth error handler on failed channel state
sacOO7 May 31, 2022
3ad6972
Extracted private method for publishing errors in AblyChannel class
sacOO7 May 31, 2022
afbc3fc
Fixed auth exports, updated ably-connector to apply authz for channels
sacOO7 May 31, 2022
9655563
updated sequential token executer, update auth into a class
sacOO7 Jun 1, 2022
7e29ad0
Added extra AblyAuth param to ably private/presence channel constuctors
sacOO7 Jun 1, 2022
af13ddd
Updated AblyConnector with AblyAuth class
sacOO7 Jun 1, 2022
4db8709
Refactored channel attach, added flag to make sure it's executed only…
sacOO7 Jun 1, 2022
ab29e15
Updated utils with base64 implementation
sacOO7 Jun 1, 2022
a84e195
Moved mock-auth-server under tests
sacOO7 Jun 1, 2022
ad69c0f
Refactored utils, added proper types
sacOO7 Jun 1, 2022
ecbfc14
Added proper exception handling for auth failures
sacOO7 Jun 1, 2022
64e3d89
Refactored utils atob and btoa functions
sacOO7 Jun 1, 2022
4af15a1
Added sandbox test-app-setup script
sacOO7 Jun 1, 2022
83fe4dc
Added sandbox file to create and delete test app
sacOO7 Jun 1, 2022
596ef8d
Refactored ably utils, applied constants
sacOO7 Jun 1, 2022
0bf35b7
Refactored code, added basic sandbox test
sacOO7 Jun 1, 2022
4d8662f
Fixed failing ably sandbox test
sacOO7 Jun 1, 2022
b5dc732
Refactored mock-auth server, added class wrapper
sacOO7 Jun 1, 2022
44cb7d3
Removed unnecessary constructor from ably-connector
sacOO7 Jun 1, 2022
6bd8b79
Added ably channel and connection test
sacOO7 Jun 1, 2022
8577827
Updated channel capability check for auth channels before attaching
sacOO7 Jun 2, 2022
2392528
Updated mock-auth server code with capabilities as map
sacOO7 Jun 3, 2022
f02375a
Updated parseJwt, reduced unnecessary tokenDetails size
sacOO7 Jun 6, 2022
061321d
Updated mockauthserver with updated parseJwt
sacOO7 Jun 6, 2022
f55f564
Added utils test for pasing JWT token and converting to tokenDetails
sacOO7 Jun 6, 2022
975cd87
Skipped ably-connection test
sacOO7 Jun 6, 2022
5227ba2
Refactored utils and auth file
sacOO7 Jun 7, 2022
b31e1a3
Fixed mock-auth server, Added proper parsing and serialization to fields
sacOO7 Jun 7, 2022
cbe3775
Refactored utils, updated mock-server and related tests
sacOO7 Jun 7, 2022
37e8c87
Added test to check for the basic working of the sandbox app
sacOO7 Jun 8, 2022
8c4c28c
Fixed AblyConnection test, added checks for connection states
sacOO7 Jun 8, 2022
14f631c
Fixed listening to the event using specified namespace
sacOO7 Jun 8, 2022
ec7ca31
Fixed ably-channel listen and listen-to-all methods
sacOO7 Jun 8, 2022
5d45949
Updated mock-auth-server, added env. sandbox and broadcast functionality
sacOO7 Jun 8, 2022
0295997
Added safe assert util for testing async done based assertions
sacOO7 Jun 8, 2022
7cafe3c
Added ably-channel tests for listening to the event
sacOO7 Jun 8, 2022
2bfa7b8
Refactored safeAssert, added check for if it's a final assertion
sacOO7 Jun 8, 2022
d51ff5e
Added explicit test for listening to client-whisper and notification
sacOO7 Jun 8, 2022
384e0d4
Removed commented testcode from sandbox
sacOO7 Jun 8, 2022
6c079a7
Updated errorListener to log whole stateChange with error instead of …
sacOO7 Jun 8, 2022
28099b0
Refactored ably-channel and auth to handle errors properly
sacOO7 Jun 8, 2022
40c787a
Refactored mock-auth-server for short-lived and banned channels
sacOO7 Jun 8, 2022
7513e3c
Added private channel test for listening to subscription error
sacOO7 Jun 8, 2022
d934aa5
Added tests for stop listening to the event/client-event
sacOO7 Jun 8, 2022
2ba22dd
Refactored publish error, added subscribe and error remove callbacks
sacOO7 Jun 9, 2022
97160a9
Added tests related to error callback for channels
sacOO7 Jun 9, 2022
e426701
Added tests for stop listening to event, updated test-utils
sacOO7 Jun 9, 2022
a91e19c
Added whisper send and receive test to the private-channel
sacOO7 Jun 9, 2022
65f892e
Added basic presence-channel-test, updated unsubscribe method to remo…
sacOO7 Jun 9, 2022
3543b24
Refactored publishError name to _alertErrorListeners
sacOO7 Jun 9, 2022
0af4170
Overriden unsubscribe inside presence channel, removed presence liste…
sacOO7 Jun 9, 2022
ca5e161
refactored unregister subscribe and error callbacks
sacOO7 Jun 9, 2022
7b54c5e
Added attached listener to immedicately get presence when joined the …
sacOO7 Jun 9, 2022
383c720
Updated mock-auth-server to return extra-info for presence channel
sacOO7 Jun 10, 2022
52c2c3e
Updated token-request/auth to handle new response, refactored channel…
sacOO7 Jun 10, 2022
ec56262
Refactored hereListeners, enter channel on attach
sacOO7 Jun 11, 2022
286428e
Added test for channel members on update and channel join
sacOO7 Jun 11, 2022
d078790
Added missing presence test, updated auth requestToken method to get
sacOO7 Jun 21, 2022
577a92b
Updated package.json scripts, removed local path
sacOO7 Jun 22, 2022
318da77
updated authHost to get plain hostname, rather than port
sacOO7 Jun 22, 2022
d007b13
Added auth-protocol (http/https) support for rest request, added json…
sacOO7 Jun 22, 2022
15fb2c0
Added option to set authProtocol externally as a part of options
sacOO7 Jun 24, 2022
66ffa46
Refactored comments for channel attach
sacOO7 Jun 24, 2022
fda93ba
Added extra attach check before publishing subscribe callback to avoi…
sacOO7 Jul 7, 2022
bc32238
Added exit check to avoid multiple authorization after calling attach…
sacOO7 Jul 7, 2022
1660c79
Added separate typings file to avoid ably imports in js dist
sacOO7 Jul 11, 2022
65db5c5
Added separate typings imports in required files
sacOO7 Jul 11, 2022
e37b666
Increased sleep timeout of a ably channel integration test
sacOO7 Jul 11, 2022
28a6234
updated typings for global lib type declarations
sacOO7 Jul 15, 2022
4f43bef
Added test for leaving the public, private and presence channel
sacOO7 Jul 15, 2022
1b65765
Added token expiry check using ably server time
sacOO7 Jul 18, 2022
9e691f0
Added missing declarations for Vue, Axios and jQuery inside global
sacOO7 Jul 21, 2022
f7ef2d4
Removed unnecessary test-app-setup.json file, updated sandbox
sacOO7 Jul 21, 2022
e077e70
Simplified sandbox implementation, testSetup made easy, refactored tests
sacOO7 Jul 21, 2022
25c62e8
Removed unnecessary http request code from utils, updated auth file
sacOO7 Jul 21, 2022
a5d5b4f
removed sleep statements from channel tests
sacOO7 Jul 21, 2022
2a83224
Added browser check to avoid setting content-type header for XHR request
sacOO7 Jul 21, 2022
59a38ab
Declared new type Task synonomous to Function
sacOO7 Jul 22, 2022
05a7a2d
Refactored presence-channel with member update
sacOO7 Jul 23, 2022
9891d31
Refactored code, removed unnecessary TODO's and added newline at the …
sacOO7 Jul 23, 2022
443433b
Refactored code, updated class with function methods
sacOO7 Jul 24, 2022
10120c1
Updated tests with class methods
sacOO7 Jul 24, 2022
708293e
Revert "Updated tests with class methods"
sacOO7 Jul 25, 2022
a1facdd
Fixed subscribe/unsubscribe modified callback issue
sacOO7 Jul 26, 2022
32b5b50
Updated presence callbacks for joining and leaving to return publishe…
sacOO7 Jul 26, 2022
d23beca
Fixed linting errors from the code
sacOO7 Jul 26, 2022
a6c4c90
Updated auth and token-request to use class methods instead of functions
sacOO7 Jul 26, 2022
e2de8ca
Merge pull request #2 from sac-fork/ably-connector
sacOO7 Jul 27, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
"types": "dist/echo.d.ts",
"scripts": {
"build": "npm run compile && npm run declarations",
"compile": "./node_modules/.bin/rollup -c",
"declarations": "./node_modules/.bin/tsc --emitDeclarationOnly",
"compile": "rollup -c",
"declarations": "tsc --emitDeclarationOnly",
"lint": "eslint --ext .js,.ts ./src ./tests",
"prepublish": "npm run build",
"release": "npm run test && standard-version && git push --follow-tags && npm publish",
Expand All @@ -41,14 +41,18 @@
"@types/node": "^17.0.21",
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.14.0",
"ably": "^1.2.20",
"eslint": "^8.11.0",
"got": "^11.8.3",
"jest": "^27.5.1",
"jsonwebtoken": "^8.5.1",
"rollup": "^2.70.1",
"rollup-plugin-typescript2": "^0.31.2",
"standard-version": "^9.3.2",
"ts-jest": "^27.1.3",
"tslib": "^2.3.1",
"typescript": "^4.6.2"
"typescript": "^4.6.2",
"wait-for-expect": "^3.0.2"
},
"engines": {
"node": ">=10"
Expand Down
201 changes: 201 additions & 0 deletions src/channel/ably-channel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import { AblyRealtime, AblyRealtimeChannel } from '../../typings/ably';
import { EventFormatter } from '../util';
import { Channel } from './channel';

/**
* This class represents an Ably channel.
*/
export class AblyChannel extends Channel {
/**
* The Ably client instance.
*/
ably: AblyRealtime;

/**
* The name of the channel.
*/
name: string;

/**
* Channel options.
*/
options: any;

/**
* The event formatter.
*/
eventFormatter: EventFormatter;

/**
* The subscription of the channel.
*/
channel: AblyRealtimeChannel;

/**
* An array containing all registered subscribed listeners.
*/
subscribedListeners: Function[];

/**
* An array containing all registered error listeners.
*/
errorListeners: Function[];

/**
* Channel event subscribe callbacks, maps callback to modified implementation.
*/
callbacks: Map<Function, Function>;

/**
* Create a new class instance.
*/
constructor(ably: any, name: string, options: any, autoSubscribe = true) {
super();

this.name = name;
this.ably = ably;
this.options = options;
this.eventFormatter = new EventFormatter(this.options.namespace);
this.subscribedListeners = [];
this.errorListeners = [];
this.channel = ably.channels.get(name);
this.callbacks = new Map();

if (autoSubscribe) {
this.subscribe();
}
}

/**
* Subscribe to an Ably channel.
*/
subscribe(): any {
this.channel.on(stateChange => {
const { previous, current, reason } = stateChange;
if (previous !== 'attached' && current == 'attached') {
this.subscribedListeners.forEach(listener => listener());
} else if (reason) {
this._alertErrorListeners(stateChange);
}
});
this.channel.attach(this._alertErrorListeners);
}

/**
* Unsubscribe from an Ably channel, unregister all callbacks and finally detach the channel
*/
unsubscribe(): void {
this.channel.unsubscribe();
this.callbacks.clear();
this.unregisterError();
this.unregisterSubscribed();
this.channel.off();
this.channel.detach();
}

/**
* Listen for an event on the channel instance.
*/
listen(event: string, callback: Function): AblyChannel {
this.callbacks.set(callback, ({ data, ...metaData }) => callback(data, metaData));
this.channel.subscribe(this.eventFormatter.format(event), this.callbacks.get(callback) as any);
return this;
}

/**
* Listen for all events on the channel instance.
*/
listenToAll(callback: Function): AblyChannel {
this.callbacks.set(callback, ({ name, data, ...metaData }) => {
let namespace = this.options.namespace.replace(/\./g, '\\');

let formattedEvent = name.startsWith(namespace) ? name.substring(namespace.length + 1) : '.' + name;

callback(formattedEvent, data, metaData);
});
this.channel.subscribe(this.callbacks.get(callback) as any);
return this;
}

/**
* Stop listening for an event on the channel instance.
*/
stopListening(event: string, callback?: Function): AblyChannel {
if (callback) {
this.channel.unsubscribe(this.eventFormatter.format(event), this.callbacks.get(callback) as any);
this.callbacks.delete(callback);
} else {
this.channel.unsubscribe(this.eventFormatter.format(event));
}

return this;
}

/**
* Stop listening for all events on the channel instance.
*/
stopListeningToAll(callback?: Function): AblyChannel {
if (callback) {
this.channel.unsubscribe(this.callbacks.get(callback) as any);
this.callbacks.delete(callback);
} else {
this.channel.unsubscribe();
}

return this;
}

/**
* Register a callback to be called anytime a subscription succeeds.
*/
subscribed(callback: Function): AblyChannel {
this.subscribedListeners.push(callback);

return this;
}

/**
* Register a callback to be called anytime a subscription error occurs.
*/
error(callback: Function): AblyChannel {
this.errorListeners.push(callback);

return this;
}

/**
* Unregisters given error callback from the listeners.
* @param callback
* @returns AblyChannel
*/
unregisterSubscribed(callback?: Function): AblyChannel {
if (callback) {
this.subscribedListeners = this.subscribedListeners.filter(s => s != callback);
} else {
this.subscribedListeners = [];
}

return this;
}

/**
* Unregisters given error callback from the listeners.
* @param callback
* @returns AblyChannel
*/
unregisterError(callback?: Function): AblyChannel {
if (callback) {
this.errorListeners = this.errorListeners.filter(e => e != callback);
} else {
this.errorListeners = [];
}

return this;
}

_alertErrorListeners = (err: any) => {
if (err) {
this.errorListeners.forEach(listener => listener(err));
}
}
}
102 changes: 102 additions & 0 deletions src/channel/ably-presence-channel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { AblyChannel } from './ably-channel';
import { AblyAuth } from './ably/auth';
import { PresenceChannel } from './presence-channel';

/**
* This class represents an Ably presence channel.
*/
export class AblyPresenceChannel extends AblyChannel implements PresenceChannel {

presenceData: any;

constructor(ably: any, name: string, options: any, auth: AblyAuth) {
super(ably, name, options, false);
this.channel.on("failed", auth.onChannelFailed(this));
this.channel.on("attached", () => this.enter(this.presenceData, this._alertErrorListeners));
this.subscribe();
}

unsubscribe(): void {
this.leave(this.presenceData, this._alertErrorListeners);
this.channel.presence.unsubscribe();
super.unsubscribe();
}

/**
* Register a callback to be called anytime the member list changes.
*/
here(callback: Function): AblyPresenceChannel {
this.channel.presence.subscribe(['enter', 'update', 'leave'], () =>
this.channel.presence.get((err, members) => callback(members, err)// returns local sync copy of updated members
));
return this;
}

/**
* Listen for someone joining the channel.
*/
joining(callback: Function): AblyPresenceChannel {
this.channel.presence.subscribe(['enter', 'update'], ({ data, ...metaData }) => {
callback(data, metaData);
});

return this;
}

/**
* Listen for someone leaving the channel.
*/
leaving(callback: Function): AblyPresenceChannel {
this.channel.presence.subscribe('leave', ({ data, ...metaData }) => {
callback(data, metaData);
});

return this;
}

/**
* Enter presence
* @param data - Data to be published while entering the channel
* @param callback - success/error callback (err) => {}
* @returns AblyPresenceChannel
*/
enter(data: any, callback: Function): AblyPresenceChannel {
this.channel.presence.enter(data, callback as any);

return this;
}

/**
* Leave presence
* @param data - Data to be published while leaving the channel
* @param callback - success/error callback (err) => {}
* @returns AblyPresenceChannel
*/
leave(data: any, callback?: Function): AblyPresenceChannel {
this.channel.presence.leave(data, callback as any);

return this;
}

/**
* Update presence
* @param data - Update presence with data
* @param callback - success/error callback (err) => {}
* @returns AblyPresenceChannel
*/
update(data: any, callback: Function): AblyPresenceChannel {
this.channel.presence.update(data, callback as any);

return this;
}

/**
* Trigger client event on the channel.
*/
whisper(eventName: string, data: any): AblyPresenceChannel {
this.channel.publish(`client-${eventName}`, data);

return this;
}

}
19 changes: 19 additions & 0 deletions src/channel/ably-private-channel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { AblyChannel } from './ably-channel';
import { AblyAuth } from './ably/auth';

export class AblyPrivateChannel extends AblyChannel {

constructor(ably: any, name: string, options: any, auth: AblyAuth) {
super(ably, name, options, false);
this.channel.on("failed", auth.onChannelFailed(this));
this.subscribe();
}
/**
* Trigger client event on the channel.
*/
whisper(eventName: string, data: any): AblyPrivateChannel {
this.channel.publish(`client-${eventName}`, data);

return this;
}
}
37 changes: 37 additions & 0 deletions src/channel/ably/attach.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { isNullOrUndefined } from './utils';

let channelAttachAuthorized = false;

/**
* Modifies existing channel attach with custom authz implementation
*/
export const beforeChannelAttach = (ablyClient, authorize: Function) => {
const dummyRealtimeChannel = ablyClient.channels.get("dummy");
if (channelAttachAuthorized) { //Only once all ably instance
return;
}
const internalAttach = dummyRealtimeChannel.__proto__._attach; // get parent class method inferred from object, store it in temp. variable
if (isNullOrUndefined(internalAttach)) {
console.warn("channel internal attach function not found, please check for right library version")
return;
}
function customInternalAttach(forceReattach, attachReason, errCallback) {// Define new function that needs to be added
if (this.state === 'attached' || this.authorizing) {
return;
}
this.authorizing = true;
const bindedInternalAttach = internalAttach.bind(this); // bind object instance at runtime
// custom logic before attach
authorize(this, (error) => {
this.authorizing = false;
if (error) {
errCallback(error);
return;
} else {
bindedInternalAttach(forceReattach, attachReason, errCallback);// call internal function here
}
})
}
dummyRealtimeChannel.__proto__._attach = customInternalAttach; // add updated extension method to parent class, auto binded
channelAttachAuthorized = true;
}
Loading