-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support annotations (rest and realtime)
1 parent
29eb528
commit 71439a3
Showing
19 changed files
with
647 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import EventEmitter from '../util/eventemitter'; | ||
import Logger from '../util/logger'; | ||
import Annotation from '../types/annotation'; | ||
import { actions, flags } from '../types/protocolmessagecommon'; | ||
import { fromValues as protocolMessageFromValues } from '../types/protocolmessage'; | ||
import type { CipherOptions } from '../types/basemessage'; | ||
import ErrorInfo from '../types/errorinfo'; | ||
import RealtimeChannel from './realtimechannel'; | ||
import RestAnnotations, { RestGetAnnotationsParams } from './restannotations'; | ||
import type { PaginatedResult } from './paginatedresource'; | ||
|
||
class RealtimeAnnotations { | ||
private channel: RealtimeChannel; | ||
private logger: Logger; | ||
private subscriptions: EventEmitter; | ||
|
||
constructor(channel: RealtimeChannel) { | ||
this.channel = channel; | ||
this.logger = channel.logger; | ||
this.subscriptions = new EventEmitter(this.logger); | ||
} | ||
|
||
async publish(refSerial: string, refType: string, data: any): Promise<void> { | ||
const channelName = this.channel.name; | ||
const annotation = Annotation.fromValues({ | ||
action: 'annotation.create', | ||
refSerial, | ||
refType, | ||
data, | ||
}); | ||
|
||
// TODO get rid of CipherOptions type assertion, indicates channeloptions types are broken | ||
const wireAnnotation = await annotation.encode(this.channel.channelOptions as CipherOptions); | ||
|
||
this.channel._throwIfUnpublishableState(); | ||
|
||
Logger.logAction( | ||
this.logger, | ||
Logger.LOG_MICRO, | ||
'RealtimeAnnotations.publish()', | ||
'channelName = ' + channelName + ', sending annotation with refSerial = ' + refSerial + ', refType = ' + refType, | ||
); | ||
|
||
const pm = protocolMessageFromValues({ | ||
action: actions.ANNOTATION, | ||
channel: channelName, | ||
annotations: [wireAnnotation], | ||
}); | ||
return this.channel.sendMessage(pm); | ||
} | ||
|
||
async subscribe(..._args: unknown[] /* [refType], listener */): Promise<void> { | ||
const args = RealtimeChannel.processListenerArgs(_args); | ||
const event = args[0]; | ||
const listener = args[1]; | ||
const channel = this.channel; | ||
|
||
if (channel.state === 'failed') { | ||
throw ErrorInfo.fromValues(channel.invalidStateError()); | ||
} | ||
|
||
await channel.attach(); | ||
|
||
if ((this.channel._mode & flags.ANNOTATION_SUBSCRIBE) === 0) { | ||
throw new ErrorInfo( | ||
"You're trying to add an annotation listener, but you haven't requested the annotation_subscribe channel mode in ChannelOptions, so this won't do anything (we only deliver annotations to clients who have explicitly requested them)", | ||
93001, | ||
400, | ||
); | ||
} | ||
|
||
this.subscriptions.on(event, listener); | ||
} | ||
|
||
unsubscribe(..._args: unknown[] /* [event], listener */): void { | ||
const args = RealtimeChannel.processListenerArgs(_args); | ||
const event = args[0]; | ||
const listener = args[1]; | ||
this.subscriptions.off(event, listener); | ||
} | ||
|
||
_processIncoming(annotations: Annotation[]): void { | ||
for (const annotation of annotations) { | ||
this.subscriptions.emit(annotation.refType || '', annotation); | ||
} | ||
} | ||
|
||
async get(serial: string, params: RestGetAnnotationsParams | null): Promise<PaginatedResult<Annotation>> { | ||
return RestAnnotations.prototype.get.call(this, serial, params); | ||
} | ||
} | ||
|
||
export default RealtimeAnnotations; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import * as Utils from '../util/utils'; | ||
import Annotation, { WireAnnotation, _fromEncodedArray } from '../types/annotation'; | ||
import type { CipherOptions } from '../types/basemessage'; | ||
import RestChannel from './restchannel'; | ||
import Defaults from '../util/defaults'; | ||
import PaginatedResource, { PaginatedResult } from './paginatedresource'; | ||
import Resource from './resource'; | ||
|
||
export interface RestGetAnnotationsParams { | ||
limit?: number; | ||
} | ||
|
||
class RestAnnotations { | ||
private channel: RestChannel; | ||
|
||
constructor(channel: RestChannel) { | ||
this.channel = channel; | ||
} | ||
|
||
async publish(refSerial: string, refType: string, data: any): Promise<void> { | ||
const annotation = Annotation.fromValues({ | ||
action: 'annotation.create', | ||
refSerial, | ||
refType, | ||
data, | ||
}); | ||
|
||
// TODO get rid of CipherOptions type assertion, indicates channeloptions types are broken | ||
const wireAnnotation = await annotation.encode(this.channel.channelOptions as CipherOptions); | ||
|
||
const client = this.channel.client, | ||
options = client.options, | ||
format = options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, | ||
headers = Defaults.defaultPostHeaders(client.options, { format }), | ||
params = {}; | ||
|
||
const requestBody = Utils.encodeBody([wireAnnotation], client._MsgPack, format); | ||
|
||
await Resource.post( | ||
client, | ||
client.rest.channelMixin.basePath(this.channel) + '/annotations', | ||
requestBody, | ||
headers, | ||
params, | ||
null, | ||
true, | ||
); | ||
} | ||
|
||
async get(serial: string, params: RestGetAnnotationsParams | null): Promise<PaginatedResult<Annotation>> { | ||
const client = this.channel.client, | ||
format = client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, | ||
envelope = client.http.supportsLinkHeaders ? undefined : format, | ||
headers = Defaults.defaultGetHeaders(client.options, { format }); | ||
|
||
Utils.mixin(headers, client.options.headers); | ||
|
||
return new PaginatedResource( | ||
client, | ||
client.rest.channelMixin.basePath(this.channel) + '/messages/' + serial + '/annotations', | ||
headers, | ||
envelope, | ||
async (body, _, unpacked) => { | ||
const decoded = ( | ||
unpacked ? body : Utils.decodeBody(body, client._MsgPack, format) | ||
) as Utils.Properties<WireAnnotation>[]; | ||
|
||
return _fromEncodedArray(decoded, this.channel); | ||
}, | ||
).get(params as Record<string, unknown>); | ||
} | ||
} | ||
|
||
export default RestAnnotations; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import Logger from '../util/logger'; | ||
import { BaseMessage, encode, decode, wireToJSON, normalizeCipherOptions, CipherOptions, strMsg } from './basemessage'; | ||
import * as API from '../../../../ably'; | ||
import * as Utils from '../util/utils'; | ||
|
||
import type { IUntypedCryptoStatic } from '../../types/ICryptoStatic'; | ||
import type { Properties } from '../util/utils'; | ||
import type RestChannel from '../client/restchannel'; | ||
import type RealtimeChannel from '../client/realtimechannel'; | ||
import type { ChannelOptions } from '../../types/channel'; | ||
type Channel = RestChannel | RealtimeChannel; | ||
|
||
const actions = ['annotation.create', 'annotation.delete']; | ||
|
||
export async function fromEncoded( | ||
logger: Logger, | ||
Crypto: IUntypedCryptoStatic | null, | ||
encoded: WireAnnotation, | ||
inputOptions?: API.ChannelOptions, | ||
): Promise<Annotation> { | ||
const options = normalizeCipherOptions(Crypto, logger, inputOptions ?? null); | ||
const wa = WireAnnotation.fromValues(encoded); | ||
return wa.decode(options, logger); | ||
} | ||
|
||
export async function fromEncodedArray( | ||
logger: Logger, | ||
Crypto: IUntypedCryptoStatic | null, | ||
encodedArray: WireAnnotation[], | ||
options?: API.ChannelOptions, | ||
): Promise<Annotation[]> { | ||
return Promise.all( | ||
encodedArray.map(function (encoded) { | ||
return fromEncoded(logger, Crypto, encoded, options); | ||
}), | ||
); | ||
} | ||
|
||
// these forms of the functions are used internally when we have a channel instance | ||
// already, so don't need to normalise channel options | ||
export async function _fromEncoded(encoded: Properties<WireAnnotation>, channel: Channel): Promise<Annotation> { | ||
return WireAnnotation.fromValues(encoded).decode(channel.channelOptions, channel.logger); | ||
} | ||
|
||
export async function _fromEncodedArray( | ||
encodedArray: Properties<WireAnnotation>[], | ||
channel: Channel, | ||
): Promise<Annotation[]> { | ||
return Promise.all( | ||
encodedArray.map(function (encoded) { | ||
return _fromEncoded(encoded, channel); | ||
}), | ||
); | ||
} | ||
|
||
// for tree-shakability | ||
export function fromValues(values: Properties<Annotation>) { | ||
return Annotation.fromValues(values); | ||
} | ||
|
||
class Annotation extends BaseMessage { | ||
action?: API.AnnotationAction; | ||
serial?: string; | ||
refSerial?: string; | ||
refType?: string; | ||
|
||
async encode(options: CipherOptions): Promise<WireAnnotation> { | ||
const res = Object.assign(new WireAnnotation(), this, { | ||
action: actions.indexOf(this.action || 'annotation.create'), | ||
}); | ||
return encode(res, options); | ||
} | ||
|
||
static fromValues(values: Properties<Annotation>): Annotation { | ||
return Object.assign(new Annotation(), values); | ||
} | ||
|
||
static fromValuesArray(values: Properties<Annotation>[]): Annotation[] { | ||
return values.map((v) => Annotation.fromValues(v)); | ||
} | ||
|
||
toString() { | ||
return strMsg(this, 'Annotation'); | ||
} | ||
} | ||
|
||
export class WireAnnotation extends BaseMessage { | ||
action?: number; | ||
serial?: string; | ||
refSerial?: string; | ||
refType?: string; | ||
|
||
toJSON(...args: any[]) { | ||
return wireToJSON.call(this, ...args); | ||
} | ||
|
||
static fromValues(values: Properties<WireAnnotation>): WireAnnotation { | ||
return Object.assign(new WireAnnotation(), values); | ||
} | ||
|
||
static fromValuesArray(values: Properties<WireAnnotation>[]): WireAnnotation[] { | ||
return values.map((v) => WireAnnotation.fromValues(v)); | ||
} | ||
|
||
async decode(channelOptions: ChannelOptions, logger: Logger): Promise<Annotation> { | ||
const res = Object.assign(new Annotation(), { | ||
...this, | ||
action: actions[this.action!], | ||
}); | ||
try { | ||
await decode(res, channelOptions); | ||
} catch (e) { | ||
Logger.logAction(logger, Logger.LOG_ERROR, 'WireAnnotation.decode()', Utils.inspectError(e)); | ||
} | ||
return res; | ||
} | ||
|
||
toString() { | ||
return strMsg(this, 'WireAnnotation'); | ||
} | ||
} | ||
|
||
export default Annotation; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import * as API from '../../../../ably'; | ||
import Logger from '../util/logger'; | ||
import Annotation, { fromEncoded, fromEncodedArray, WireAnnotation } from './annotation'; | ||
import Platform from 'common/platform'; | ||
import type { Properties } from '../util/utils'; | ||
|
||
/** | ||
`DefaultAnnotation` is the class returned by `DefaultRest` and `DefaultRealtime`’s `Annotation` static property. It introduces the static methods described in the `AnnotationStatic` interface of the public API of the non tree-shakable version of the library. | ||
*/ | ||
export class DefaultAnnotation extends Annotation { | ||
static async fromEncoded(encoded: unknown, inputOptions?: API.ChannelOptions): Promise<Annotation> { | ||
return fromEncoded(Logger.defaultLogger, Platform.Crypto, encoded as WireAnnotation, inputOptions); | ||
} | ||
|
||
static async fromEncodedArray(encodedArray: Array<unknown>, options?: API.ChannelOptions): Promise<Annotation[]> { | ||
return fromEncodedArray(Logger.defaultLogger, Platform.Crypto, encodedArray as WireAnnotation[], options); | ||
} | ||
|
||
static fromValues(values: Properties<Annotation>): Annotation { | ||
return Annotation.fromValues(values); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters