@feathersjs/transport-commons
Advanced tools
Comparing version 5.0.0-pre.22 to 5.0.0-pre.23
@@ -6,2 +6,13 @@ # Change Log | ||
# [5.0.0-pre.23](https://github.com/feathersjs/feathers/compare/v5.0.0-pre.22...v5.0.0-pre.23) (2022-06-06) | ||
### Features | ||
* **client:** Improve client side custom method support ([#2654](https://github.com/feathersjs/feathers/issues/2654)) ([c138acf](https://github.com/feathersjs/feathers/commit/c138acf50affbe6b66177d084d3c7a3e9220f09f)) | ||
# [5.0.0-pre.22](https://github.com/feathersjs/feathers/compare/v5.0.0-pre.21...v5.0.0-pre.22) (2022-05-24) | ||
@@ -8,0 +19,0 @@ |
@@ -1,1 +0,1 @@ | ||
export * from './lib/client'; | ||
export * from './lib/client' |
@@ -15,3 +15,3 @@ "use strict"; | ||
leave(...connections) { | ||
connections.forEach(current => { | ||
connections.forEach((current) => { | ||
if (typeof current === 'function') { | ||
@@ -34,3 +34,3 @@ const callback = current; | ||
join(...connections) { | ||
connections.forEach(connection => { | ||
connections.forEach((connection) => { | ||
if (connection && this.connections.indexOf(connection) === -1) { | ||
@@ -37,0 +37,0 @@ this.connections.push(connection); |
@@ -8,4 +8,4 @@ "use strict"; | ||
const connections = []; | ||
children.forEach(channel => { | ||
channel.connections.forEach(connection => { | ||
children.forEach((channel) => { | ||
channel.connections.forEach((connection) => { | ||
if (!mappings.has(connection)) { | ||
@@ -12,0 +12,0 @@ connections.push(connection); |
@@ -44,3 +44,3 @@ "use strict"; | ||
// In the following precedence | ||
const publisher = ( | ||
const publisher = | ||
// 1. Service publisher for a specific event | ||
@@ -55,9 +55,10 @@ servicePublishers[event] || | ||
// 5. No publisher | ||
lodash_1.noop); | ||
lodash_1.noop; | ||
try { | ||
Promise.resolve(publisher(data, hook)).then((result) => { | ||
Promise.resolve(publisher(data, hook)) | ||
.then((result) => { | ||
if (!result) { | ||
return; | ||
} | ||
const results = (Array.isArray(result) ? (0, lodash_1.compact)((0, lodash_1.flattenDeep)(result)) : [result]); | ||
const results = Array.isArray(result) ? (0, lodash_1.compact)((0, lodash_1.flattenDeep)(result)) : [result]; | ||
const channel = new combined_1.CombinedChannel(results); | ||
@@ -70,3 +71,4 @@ if (channel && channel.length > 0) { | ||
} | ||
}).catch(logError); | ||
}) | ||
.catch(logError); | ||
} | ||
@@ -73,0 +75,0 @@ catch (error) { |
@@ -18,3 +18,3 @@ import { Application, HookContext } from '@feathersjs/feathers'; | ||
export declare function channelMixin(): ChannelMixin; | ||
export declare type Event = string | (typeof ALL_EVENTS); | ||
export declare type Event = string | typeof ALL_EVENTS; | ||
export declare type Publisher<T = any, A = Application, S = any> = (data: T, context: HookContext<A, S>) => Channel | Channel[] | void | Promise<Channel | Channel[] | void>; | ||
@@ -21,0 +21,0 @@ export interface PublishMixin<T = any> { |
@@ -41,3 +41,3 @@ "use strict"; | ||
} | ||
const channels = names.map(name => this.channel(name)); | ||
const channels = names.map((name) => this.channel(name)); | ||
return new combined_1.CombinedChannel(channels); | ||
@@ -44,0 +44,0 @@ } |
@@ -8,4 +8,4 @@ import { Id, NullableId, Params, ServiceInterface } from '@feathersjs/feathers'; | ||
} | ||
export declare type SocketService<T = any, D = Partial<any>> = Service<T, D>; | ||
export declare class Service<T = any, D = Partial<T>> implements ServiceInterface<T, D> { | ||
export declare type SocketService<T = any, D = Partial<any>, P extends Params = Params> = Service<T, D, P>; | ||
export declare class Service<T = any, D = Partial<T>, P extends Params = Params> implements ServiceInterface<T, D, P> { | ||
events: string[]; | ||
@@ -12,0 +12,0 @@ path: string; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Service = void 0; | ||
/* eslint-disable @typescript-eslint/ban-ts-comment */ | ||
const errors_1 = require("@feathersjs/errors"); | ||
@@ -19,9 +20,5 @@ const commons_1 = require("@feathersjs/commons"); | ||
]; | ||
const otherEmitterMethods = [ | ||
'eventNames', | ||
'getMaxListeners', | ||
'setMaxListeners' | ||
]; | ||
const otherEmitterMethods = ['eventNames', 'getMaxListeners', 'setMaxListeners']; | ||
const addEmitterMethods = (service) => { | ||
otherEmitterMethods.forEach(method => { | ||
otherEmitterMethods.forEach((method) => { | ||
service[method] = function (...args) { | ||
@@ -35,3 +32,3 @@ if (typeof this.connection[method] !== 'function') { | ||
// Methods that should add the namespace (service path) | ||
namespacedEmitterMethods.forEach(method => { | ||
namespacedEmitterMethods.forEach((method) => { | ||
service[method] = function (name, ...args) { | ||
@@ -42,4 +39,3 @@ if (typeof this.connection[method] !== 'function') { | ||
const eventName = `${this.path} ${name}`; | ||
debug(`Calling emitter method ${method} with ` + | ||
`namespaced event '${eventName}'`); | ||
debug(`Calling emitter method ${method} with ` + `namespaced event '${eventName}'`); | ||
const result = this.connection[method](eventName, ...args); | ||
@@ -69,3 +65,3 @@ return result === this.connection ? this : result; | ||
methods(...names) { | ||
names.forEach(name => { | ||
names.forEach((name) => { | ||
this[name] = function (data, params = {}) { | ||
@@ -72,0 +68,0 @@ return this.send(name, data, params.query || {}); |
@@ -25,3 +25,3 @@ "use strict"; | ||
const placeholderName = current.substring(1); | ||
let placeholder = this.placeholders.find(p => p.name === placeholderName); | ||
let placeholder = this.placeholders.find((p) => p.name === placeholderName); | ||
if (!placeholder) { | ||
@@ -39,6 +39,8 @@ placeholder = new RouteNode(placeholderName, nextDepth); | ||
if (path.length === this.depth) { | ||
return this.data === undefined ? null : { | ||
...info, | ||
data: this.data | ||
}; | ||
return this.data === undefined | ||
? null | ||
: { | ||
...info, | ||
data: this.data | ||
}; | ||
} | ||
@@ -45,0 +47,0 @@ const current = path[this.depth]; |
@@ -29,8 +29,8 @@ "use strict"; | ||
// `connection` event | ||
done.then(provider => provider.on('connection', (connection) => app.emit('connection', getParams(connection)))); | ||
done.then((provider) => provider.on('connection', (connection) => app.emit('connection', getParams(connection)))); | ||
// `socket.emit('methodName', 'serviceName', ...args)` handlers | ||
done.then(provider => provider.on('connection', (connection) => { | ||
done.then((provider) => provider.on('connection', (connection) => { | ||
const methodHandlers = Object.keys(app.services).reduce((result, name) => { | ||
const { methods } = (0, feathers_1.getServiceOptions)(app.service(name)); | ||
methods.forEach(method => { | ||
methods.forEach((method) => { | ||
if (!result[method]) { | ||
@@ -46,3 +46,3 @@ result[method] = (...args) => { | ||
}, {}); | ||
Object.keys(methodHandlers).forEach(key => connection.on(key, methodHandlers[key])); | ||
Object.keys(methodHandlers).forEach((key) => connection.on(key, methodHandlers[key])); | ||
})); | ||
@@ -49,0 +49,0 @@ }; |
@@ -22,3 +22,3 @@ "use strict"; | ||
if (!hasToJSON) { | ||
Object.getOwnPropertyNames(e).forEach(key => { | ||
Object.getOwnPropertyNames(e).forEach((key) => { | ||
result[key] = e[key]; | ||
@@ -37,3 +37,3 @@ }); | ||
debug(`Dispatching '${event}' to ${channel.length} connections`); | ||
channel.connections.forEach(connection => { | ||
channel.connections.forEach((connection) => { | ||
// The reference between connection and socket is set in `app.setup` | ||
@@ -47,3 +47,3 @@ const socket = socketKey ? connection[socketKey] : socketMap.get(connection); | ||
if (!Array.isArray(data) && Array.isArray(context.result) && Array.isArray(result)) { | ||
result = context.result.find(resultData => (0, isEqual_1.default)(resultData, data)); | ||
result = context.result.find((resultData) => (0, isEqual_1.default)(resultData, data)); | ||
} | ||
@@ -60,4 +60,5 @@ debug(`Dispatching '${eventName}' to Socket ${socket.id} with`, result); | ||
const methodArgs = args.slice(0); | ||
const callback = typeof methodArgs[methodArgs.length - 1] === 'function' | ||
? methodArgs.pop() : function () { }; | ||
const callback = | ||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
typeof methodArgs[methodArgs.length - 1] === 'function' ? methodArgs.pop() : function () { }; | ||
debug(`Running ${trace}`, connection, args); | ||
@@ -85,3 +86,3 @@ const handleError = (error) => { | ||
// `params` is always the last parameter. Error if we got more arguments. | ||
if (methodArgs.length > (position + 1)) { | ||
if (methodArgs.length > position + 1) { | ||
throw new errors_1.BadRequest(`Too many arguments for '${method}' method`); | ||
@@ -88,0 +89,0 @@ } |
{ | ||
"name": "@feathersjs/transport-commons", | ||
"description": "Shared functionality for websocket providers", | ||
"version": "5.0.0-pre.22", | ||
"version": "5.0.0-pre.23", | ||
"homepage": "https://feathersjs.com", | ||
@@ -56,5 +56,5 @@ "main": "lib/", | ||
"dependencies": { | ||
"@feathersjs/commons": "^5.0.0-pre.22", | ||
"@feathersjs/errors": "^5.0.0-pre.22", | ||
"@feathersjs/feathers": "^5.0.0-pre.22", | ||
"@feathersjs/commons": "^5.0.0-pre.23", | ||
"@feathersjs/errors": "^5.0.0-pre.23", | ||
"@feathersjs/feathers": "^5.0.0-pre.23", | ||
"encodeurl": "^1.0.2", | ||
@@ -67,9 +67,9 @@ "lodash": "^4.17.21" | ||
"@types/mocha": "^9.1.1", | ||
"@types/node": "^17.0.31", | ||
"@types/node": "^17.0.40", | ||
"mocha": "^10.0.0", | ||
"shx": "^0.3.4", | ||
"ts-node": "^10.7.0", | ||
"typescript": "^4.6.4" | ||
"ts-node": "^10.8.1", | ||
"typescript": "^4.7.3" | ||
}, | ||
"gitHead": "e452e02063e6d8943a9cae2315ab585bc4f82fb6" | ||
"gitHead": "a60910bd730b88053ca6648337095f1ca1e3b39f" | ||
} |
@@ -1,61 +0,61 @@ | ||
import { EventEmitter } from 'events'; | ||
import { EventEmitter } from 'events' | ||
export interface RealTimeConnection { | ||
[key: string]: any; | ||
[key: string]: any | ||
} | ||
export class Channel extends EventEmitter { | ||
connections: RealTimeConnection[]; | ||
data: any; | ||
connections: RealTimeConnection[] | ||
data: any | ||
constructor (connections: RealTimeConnection[] = [], data: any = null) { | ||
super(); | ||
constructor(connections: RealTimeConnection[] = [], data: any = null) { | ||
super() | ||
this.connections = connections; | ||
this.data = data; | ||
this.connections = connections | ||
this.data = data | ||
} | ||
get length () { | ||
return this.connections.length; | ||
get length() { | ||
return this.connections.length | ||
} | ||
leave (...connections: RealTimeConnection[]) { | ||
connections.forEach(current => { | ||
leave(...connections: RealTimeConnection[]) { | ||
connections.forEach((current) => { | ||
if (typeof current === 'function') { | ||
const callback = current as (connection: RealTimeConnection) => boolean; | ||
const callback = current as (connection: RealTimeConnection) => boolean | ||
this.leave(...this.connections.filter(callback)); | ||
this.leave(...this.connections.filter(callback)) | ||
} else { | ||
const index = this.connections.indexOf(current); | ||
const index = this.connections.indexOf(current) | ||
if (index !== -1) { | ||
this.connections.splice(index, 1); | ||
this.connections.splice(index, 1) | ||
} | ||
} | ||
}); | ||
}) | ||
if (this.length === 0) { | ||
this.emit('empty'); | ||
this.emit('empty') | ||
} | ||
return this; | ||
return this | ||
} | ||
join (...connections: RealTimeConnection[]) { | ||
connections.forEach(connection => { | ||
join(...connections: RealTimeConnection[]) { | ||
connections.forEach((connection) => { | ||
if (connection && this.connections.indexOf(connection) === -1) { | ||
this.connections.push(connection); | ||
this.connections.push(connection) | ||
} | ||
}); | ||
}) | ||
return this; | ||
return this | ||
} | ||
filter (fn: (connection: RealTimeConnection) => boolean) { | ||
return new Channel(this.connections.filter(fn), this.data); | ||
filter(fn: (connection: RealTimeConnection) => boolean) { | ||
return new Channel(this.connections.filter(fn), this.data) | ||
} | ||
send (data: any) { | ||
return new Channel(this.connections, data); | ||
send(data: any) { | ||
return new Channel(this.connections, data) | ||
} | ||
} |
@@ -1,56 +0,56 @@ | ||
import { Channel, RealTimeConnection } from './base'; | ||
import { Channel, RealTimeConnection } from './base' | ||
function collectConnections (children: Channel[]) { | ||
const mappings = new WeakMap<RealTimeConnection, any>(); | ||
const connections: RealTimeConnection[] = []; | ||
function collectConnections(children: Channel[]) { | ||
const mappings = new WeakMap<RealTimeConnection, any>() | ||
const connections: RealTimeConnection[] = [] | ||
children.forEach(channel => { | ||
channel.connections.forEach(connection => { | ||
children.forEach((channel) => { | ||
channel.connections.forEach((connection) => { | ||
if (!mappings.has(connection)) { | ||
connections.push(connection); | ||
mappings.set(connection, channel.data); | ||
connections.push(connection) | ||
mappings.set(connection, channel.data) | ||
} | ||
}); | ||
}); | ||
}) | ||
}) | ||
return { connections, mappings }; | ||
return { connections, mappings } | ||
} | ||
export class CombinedChannel extends Channel { | ||
children: Channel[]; | ||
mappings: WeakMap<RealTimeConnection, any>; | ||
children: Channel[] | ||
mappings: WeakMap<RealTimeConnection, any> | ||
constructor (children: Channel[], data: any = null) { | ||
const { mappings, connections } = collectConnections(children); | ||
constructor(children: Channel[], data: any = null) { | ||
const { mappings, connections } = collectConnections(children) | ||
super(connections, data); | ||
super(connections, data) | ||
this.children = children; | ||
this.mappings = mappings; | ||
this.children = children | ||
this.mappings = mappings | ||
} | ||
refresh () { | ||
const collected = collectConnections(this.children); | ||
refresh() { | ||
const collected = collectConnections(this.children) | ||
return Object.assign(this, collected); | ||
return Object.assign(this, collected) | ||
} | ||
leave (...connections: RealTimeConnection[]) { | ||
return this.callChildren('leave', connections); | ||
leave(...connections: RealTimeConnection[]) { | ||
return this.callChildren('leave', connections) | ||
} | ||
join (...connections: RealTimeConnection[]) { | ||
return this.callChildren('join', connections); | ||
join(...connections: RealTimeConnection[]) { | ||
return this.callChildren('join', connections) | ||
} | ||
dataFor (connection: RealTimeConnection) { | ||
return this.mappings.get(connection); | ||
dataFor(connection: RealTimeConnection) { | ||
return this.mappings.get(connection) | ||
} | ||
private callChildren (method: string, connections: RealTimeConnection[]) { | ||
this.children.forEach((child: any) => child[method](...connections)); | ||
this.refresh(); | ||
private callChildren(method: string, connections: RealTimeConnection[]) { | ||
this.children.forEach((child: any) => child[method](...connections)) | ||
this.refresh() | ||
return this; | ||
return this | ||
} | ||
} |
@@ -1,62 +0,64 @@ | ||
import { Application, FeathersService, getServiceOptions } from '@feathersjs/feathers'; | ||
import { createDebug } from '@feathersjs/commons'; | ||
import { compact, flattenDeep, noop } from 'lodash'; | ||
import { Channel, RealTimeConnection } from './channel/base'; | ||
import { CombinedChannel } from './channel/combined'; | ||
import { channelMixin, publishMixin, keys, PublishMixin, Event, Publisher } from './mixins'; | ||
import EventEmitter from 'events'; | ||
import { Application, FeathersService, getServiceOptions } from '@feathersjs/feathers' | ||
import { createDebug } from '@feathersjs/commons' | ||
import { compact, flattenDeep, noop } from 'lodash' | ||
import { Channel, RealTimeConnection } from './channel/base' | ||
import { CombinedChannel } from './channel/combined' | ||
import { channelMixin, publishMixin, keys, PublishMixin, Event, Publisher } from './mixins' | ||
import EventEmitter from 'events' | ||
const debug = createDebug('@feathersjs/transport-commons/channels'); | ||
const { CHANNELS } = keys; | ||
const debug = createDebug('@feathersjs/transport-commons/channels') | ||
const { CHANNELS } = keys | ||
declare module '@feathersjs/feathers/lib/declarations' { | ||
interface ServiceAddons<A, S> extends EventEmitter { // eslint-disable-line | ||
publish (publisher: Publisher<ServiceGenericType<S>, A, this>): this; | ||
publish (event: Event, publisher: Publisher<ServiceGenericType<S>, A, this>): this; | ||
interface ServiceAddons<A, S> extends EventEmitter { | ||
// eslint-disable-line | ||
publish(publisher: Publisher<ServiceGenericType<S>, A, this>): this | ||
publish(event: Event, publisher: Publisher<ServiceGenericType<S>, A, this>): this | ||
registerPublisher (publisher: Publisher<ServiceGenericType<S>, A, this>): this; | ||
registerPublisher (event: Event, publisher: Publisher<ServiceGenericType<S>, A, this>): this; | ||
registerPublisher(publisher: Publisher<ServiceGenericType<S>, A, this>): this | ||
registerPublisher(event: Event, publisher: Publisher<ServiceGenericType<S>, A, this>): this | ||
} | ||
interface Application<Services, Settings> { // eslint-disable-line | ||
channels: string[]; | ||
interface Application<Services, Settings> { | ||
// eslint-disable-line | ||
channels: string[] | ||
channel (name: string | string[]): Channel; | ||
channel (...names: string[]): Channel; | ||
channel(name: string | string[]): Channel | ||
channel(...names: string[]): Channel | ||
publish<T> (publisher: Publisher<T, this>): this; | ||
publish<T> (event: Event, publisher: Publisher<T, this>): this; | ||
publish<T>(publisher: Publisher<T, this>): this | ||
publish<T>(event: Event, publisher: Publisher<T, this>): this | ||
registerPublisher<T> (publisher: Publisher<T, this>): this; | ||
registerPublisher<T> (event: Event, publisher: Publisher<T, this>): this; | ||
registerPublisher<T>(publisher: Publisher<T, this>): this | ||
registerPublisher<T>(event: Event, publisher: Publisher<T, this>): this | ||
} | ||
interface Params { | ||
connection?: RealTimeConnection; | ||
connection?: RealTimeConnection | ||
} | ||
} | ||
export { keys }; | ||
export { keys } | ||
export function channels () { | ||
export function channels() { | ||
return (app: Application) => { | ||
if (typeof app.channel === 'function' && typeof app.publish === 'function') { | ||
return; | ||
return | ||
} | ||
Object.assign(app, channelMixin(), publishMixin()); | ||
Object.assign(app, channelMixin(), publishMixin()) | ||
Object.defineProperty(app, 'channels', { | ||
get () { | ||
return Object.keys(this[CHANNELS]); | ||
get() { | ||
return Object.keys(this[CHANNELS]) | ||
} | ||
}); | ||
}) | ||
app.mixins.push((service: FeathersService, path: string) => { | ||
const { serviceEvents } = getServiceOptions(service); | ||
const { serviceEvents } = getServiceOptions(service) | ||
if (typeof service.publish === 'function') { | ||
return; | ||
return | ||
} | ||
Object.assign(service, publishMixin()); | ||
Object.assign(service, publishMixin()) | ||
@@ -67,13 +69,13 @@ serviceEvents.forEach((event: string) => { | ||
// Fake hook for custom events | ||
hook = { path, service, app, result: data }; | ||
hook = { path, service, app, result: data } | ||
} | ||
debug('Publishing event', event, hook.path); | ||
debug('Publishing event', event, hook.path) | ||
const logError = (error: any) => debug(`Error in '${hook.path} ${event}' publisher`, error); | ||
const servicePublishers = (service as unknown as PublishMixin)[keys.PUBLISHERS]; | ||
const appPublishers = (app as unknown as PublishMixin)[keys.PUBLISHERS]; | ||
const logError = (error: any) => debug(`Error in '${hook.path} ${event}' publisher`, error) | ||
const servicePublishers = (service as unknown as PublishMixin)[keys.PUBLISHERS] | ||
const appPublishers = (app as unknown as PublishMixin)[keys.PUBLISHERS] | ||
// This will return the first publisher list that is not empty | ||
// In the following precedence | ||
const publisher = ( | ||
const publisher = | ||
// 1. Service publisher for a specific event | ||
@@ -89,28 +91,29 @@ servicePublishers[event] || | ||
noop | ||
); | ||
try { | ||
Promise.resolve(publisher(data, hook)).then((result: any) => { | ||
if (!result) { | ||
return; | ||
} | ||
Promise.resolve(publisher(data, hook)) | ||
.then((result: any) => { | ||
if (!result) { | ||
return | ||
} | ||
const results = (Array.isArray(result) ? compact(flattenDeep(result)) : [result] as Channel[]); | ||
const channel = new CombinedChannel(results); | ||
const results = Array.isArray(result) ? compact(flattenDeep(result)) : ([result] as Channel[]) | ||
const channel = new CombinedChannel(results) | ||
if (channel && channel.length > 0) { | ||
app.emit('publish', event, channel, hook, data); | ||
} else { | ||
debug('No connections to publish to'); | ||
} | ||
}).catch(logError); | ||
if (channel && channel.length > 0) { | ||
app.emit('publish', event, channel, hook, data) | ||
} else { | ||
debug('No connections to publish to') | ||
} | ||
}) | ||
.catch(logError) | ||
} catch (error: any) { | ||
logError(error); | ||
logError(error) | ||
} | ||
}); | ||
}); | ||
}); | ||
}; | ||
}) | ||
}) | ||
}) | ||
} | ||
} | ||
export { Channel, CombinedChannel, RealTimeConnection } |
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */ | ||
import { Application, HookContext, getServiceOptions } from '@feathersjs/feathers'; | ||
import { createDebug } from '@feathersjs/commons'; | ||
import { Channel } from './channel/base'; | ||
import { CombinedChannel } from './channel/combined'; | ||
import { Application, HookContext, getServiceOptions } from '@feathersjs/feathers' | ||
import { createDebug } from '@feathersjs/commons' | ||
import { Channel } from './channel/base' | ||
import { CombinedChannel } from './channel/combined' | ||
const debug = createDebug('@feathersjs/transport-commons/channels/mixins'); | ||
const PUBLISHERS = Symbol('@feathersjs/transport-commons/publishers'); | ||
const CHANNELS = Symbol('@feathersjs/transport-commons/channels'); | ||
const ALL_EVENTS = Symbol('@feathersjs/transport-commons/all-events'); | ||
const debug = createDebug('@feathersjs/transport-commons/channels/mixins') | ||
const PUBLISHERS = Symbol('@feathersjs/transport-commons/publishers') | ||
const CHANNELS = Symbol('@feathersjs/transport-commons/channels') | ||
const ALL_EVENTS = Symbol('@feathersjs/transport-commons/all-events') | ||
@@ -16,91 +16,94 @@ export const keys = { | ||
ALL_EVENTS: ALL_EVENTS as typeof ALL_EVENTS | ||
}; | ||
} | ||
export interface ChannelMixin { | ||
[CHANNELS]: { [key: string]: Channel }; | ||
channel (...names: string[]): Channel; | ||
[CHANNELS]: { [key: string]: Channel } | ||
channel(...names: string[]): Channel | ||
} | ||
export function channelMixin () { | ||
export function channelMixin() { | ||
const mixin: ChannelMixin = { | ||
[CHANNELS]: {}, | ||
channel (...names: string[]): Channel { | ||
debug('Returning channels', names); | ||
channel(...names: string[]): Channel { | ||
debug('Returning channels', names) | ||
if (names.length === 0) { | ||
throw new Error('app.channel needs at least one channel name'); | ||
throw new Error('app.channel needs at least one channel name') | ||
} | ||
if (names.length === 1) { | ||
const [ name ] = names; | ||
const [name] = names | ||
if (Array.isArray(name)) { | ||
return this.channel(...name); | ||
return this.channel(...name) | ||
} | ||
if (!this[CHANNELS][name]) { | ||
const channel = new Channel(); | ||
const channel = new Channel() | ||
channel.once('empty', () => { | ||
channel.removeAllListeners(); | ||
delete this[CHANNELS][name]; | ||
}); | ||
channel.removeAllListeners() | ||
delete this[CHANNELS][name] | ||
}) | ||
this[CHANNELS][name] = channel; | ||
this[CHANNELS][name] = channel | ||
} | ||
return this[CHANNELS][name]; | ||
return this[CHANNELS][name] | ||
} | ||
const channels = names.map(name => this.channel(name)); | ||
const channels = names.map((name) => this.channel(name)) | ||
return new CombinedChannel(channels); | ||
return new CombinedChannel(channels) | ||
} | ||
}; | ||
} | ||
return mixin; | ||
return mixin | ||
} | ||
export type Event = string|(typeof ALL_EVENTS); | ||
export type Event = string | typeof ALL_EVENTS | ||
export type Publisher<T = any, A = Application, S = any> = (data: T, context: HookContext<A, S>) => Channel | Channel[] | void | Promise<Channel | Channel[] | void>; | ||
export type Publisher<T = any, A = Application, S = any> = ( | ||
data: T, | ||
context: HookContext<A, S> | ||
) => Channel | Channel[] | void | Promise<Channel | Channel[] | void> | ||
export interface PublishMixin<T = any> { | ||
[PUBLISHERS]: { [ALL_EVENTS]?: Publisher<T>, [key: string]: Publisher<T> }; | ||
publish (event: Event, publisher: Publisher<T>): this; | ||
registerPublisher (event: Event, publisher: Publisher<T>): this; | ||
[PUBLISHERS]: { [ALL_EVENTS]?: Publisher<T>; [key: string]: Publisher<T> } | ||
publish(event: Event, publisher: Publisher<T>): this | ||
registerPublisher(event: Event, publisher: Publisher<T>): this | ||
} | ||
export function publishMixin () { | ||
export function publishMixin() { | ||
const result: PublishMixin = { | ||
[PUBLISHERS]: {}, | ||
publish (...args) { | ||
return this.registerPublisher(...args); | ||
publish(...args) { | ||
return this.registerPublisher(...args) | ||
}, | ||
registerPublisher (event, publisher) { | ||
debug('Registering publisher', event); | ||
registerPublisher(event, publisher) { | ||
debug('Registering publisher', event) | ||
if (!publisher && typeof event === 'function') { | ||
publisher = event; | ||
event = ALL_EVENTS; | ||
publisher = event | ||
event = ALL_EVENTS | ||
} | ||
const { serviceEvents } = getServiceOptions(this); | ||
const { serviceEvents } = getServiceOptions(this) | ||
if (event !== ALL_EVENTS && !serviceEvents.includes(event)) { | ||
throw new Error(`'${event.toString()}' is not a valid service event`); | ||
throw new Error(`'${event.toString()}' is not a valid service event`) | ||
} | ||
const publishers = this[PUBLISHERS]; | ||
const publishers = this[PUBLISHERS] | ||
publishers[event] = publisher; | ||
publishers[event] = publisher | ||
return this; | ||
return this | ||
} | ||
}; | ||
} | ||
return result; | ||
return result | ||
} |
@@ -1,6 +0,7 @@ | ||
import { convert } from '@feathersjs/errors'; | ||
import { createDebug } from '@feathersjs/commons'; | ||
import { Id, NullableId, Params, ServiceInterface } from '@feathersjs/feathers'; | ||
/* eslint-disable @typescript-eslint/ban-ts-comment */ | ||
import { convert } from '@feathersjs/errors' | ||
import { createDebug } from '@feathersjs/commons' | ||
import { Id, NullableId, Params, ServiceInterface } from '@feathersjs/feathers' | ||
const debug = createDebug('@feathersjs/transport-commons/client'); | ||
const debug = createDebug('@feathersjs/transport-commons/client') | ||
@@ -18,107 +19,104 @@ const namespacedEmitterMethods = [ | ||
'removeListener' | ||
]; | ||
const otherEmitterMethods = [ | ||
'eventNames', | ||
'getMaxListeners', | ||
'setMaxListeners' | ||
]; | ||
] | ||
const otherEmitterMethods = ['eventNames', 'getMaxListeners', 'setMaxListeners'] | ||
const addEmitterMethods = (service: any) => { | ||
otherEmitterMethods.forEach(method => { | ||
otherEmitterMethods.forEach((method) => { | ||
service[method] = function (...args: any[]) { | ||
if (typeof this.connection[method] !== 'function') { | ||
throw new Error(`Can not call '${method}' on the client service connection`); | ||
throw new Error(`Can not call '${method}' on the client service connection`) | ||
} | ||
return this.connection[method](...args); | ||
}; | ||
}); | ||
return this.connection[method](...args) | ||
} | ||
}) | ||
// Methods that should add the namespace (service path) | ||
namespacedEmitterMethods.forEach(method => { | ||
namespacedEmitterMethods.forEach((method) => { | ||
service[method] = function (name: string, ...args: any[]) { | ||
if (typeof this.connection[method] !== 'function') { | ||
throw new Error(`Can not call '${method}' on the client service connection`); | ||
throw new Error(`Can not call '${method}' on the client service connection`) | ||
} | ||
const eventName = `${this.path} ${name}`; | ||
const eventName = `${this.path} ${name}` | ||
debug(`Calling emitter method ${method} with ` + | ||
`namespaced event '${eventName}'`); | ||
debug(`Calling emitter method ${method} with ` + `namespaced event '${eventName}'`) | ||
const result = this.connection[method](eventName, ...args); | ||
const result = this.connection[method](eventName, ...args) | ||
return result === this.connection ? this : result; | ||
}; | ||
}); | ||
}; | ||
return result === this.connection ? this : result | ||
} | ||
}) | ||
} | ||
interface ServiceOptions { | ||
name: string; | ||
connection: any; | ||
method: string; | ||
events?: string[]; | ||
name: string | ||
connection: any | ||
method: string | ||
events?: string[] | ||
} | ||
export type SocketService<T = any, D = Partial<any>> = Service<T, D>; | ||
export type SocketService<T = any, D = Partial<any>, P extends Params = Params> = Service<T, D, P> | ||
export class Service<T = any, D = Partial<T>> implements ServiceInterface<T, D> { | ||
events: string[]; | ||
path: string; | ||
connection: any; | ||
method: string; | ||
export class Service<T = any, D = Partial<T>, P extends Params = Params> | ||
implements ServiceInterface<T, D, P> | ||
{ | ||
events: string[] | ||
path: string | ||
connection: any | ||
method: string | ||
constructor (options: ServiceOptions) { | ||
this.events = options.events; | ||
this.path = options.name; | ||
this.connection = options.connection; | ||
this.method = options.method; | ||
constructor(options: ServiceOptions) { | ||
this.events = options.events | ||
this.path = options.name | ||
this.connection = options.connection | ||
this.method = options.method | ||
addEmitterMethods(this); | ||
addEmitterMethods(this) | ||
} | ||
send<X = any> (method: string, ...args: any[]) { | ||
send<X = any>(method: string, ...args: any[]) { | ||
return new Promise<X>((resolve, reject) => { | ||
args.unshift(method, this.path); | ||
args.unshift(method, this.path) | ||
args.push(function (error: any, data: any) { | ||
return error ? reject(convert(error)) : resolve(data); | ||
}); | ||
return error ? reject(convert(error)) : resolve(data) | ||
}) | ||
debug(`Sending socket.${this.method}`, args); | ||
debug(`Sending socket.${this.method}`, args) | ||
this.connection[this.method](...args); | ||
}); | ||
this.connection[this.method](...args) | ||
}) | ||
} | ||
methods (this: any, ...names: string[]) { | ||
names.forEach(name => { | ||
methods(this: any, ...names: string[]) { | ||
names.forEach((name) => { | ||
this[name] = function (data: any, params: Params = {}) { | ||
return this.send(name, data, params.query || {}); | ||
return this.send(name, data, params.query || {}) | ||
} | ||
}); | ||
return this; | ||
}) | ||
return this | ||
} | ||
find (params: Params = {}) { | ||
return this.send<T|T[]>('find', params.query || {}); | ||
find(params: Params = {}) { | ||
return this.send<T | T[]>('find', params.query || {}) | ||
} | ||
get (id: Id, params: Params = {}) { | ||
return this.send<T>('get', id, params.query || {}); | ||
get(id: Id, params: Params = {}) { | ||
return this.send<T>('get', id, params.query || {}) | ||
} | ||
create (data: any, params: Params = {}) { | ||
return this.send<T>('create', data, params.query || {}); | ||
create(data: any, params: Params = {}) { | ||
return this.send<T>('create', data, params.query || {}) | ||
} | ||
update (id: Id, data: any, params: Params = {}) { | ||
return this.send<T> ('update', id, data, params.query || {}); | ||
update(id: Id, data: any, params: Params = {}) { | ||
return this.send<T>('update', id, data, params.query || {}) | ||
} | ||
patch (id: NullableId, data: any, params: Params = {}) { | ||
return this.send<T|T[]> ('patch', id, data, params.query || {}); | ||
patch(id: NullableId, data: any, params: Params = {}) { | ||
return this.send<T | T[]>('patch', id, data, params.query || {}) | ||
} | ||
remove (id: NullableId, params: Params = {}) { | ||
return this.send<T|T[]> ('remove', id, params.query || {}); | ||
remove(id: NullableId, params: Params = {}) { | ||
return this.send<T | T[]>('remove', id, params.query || {}) | ||
} | ||
@@ -129,15 +127,15 @@ | ||
// of the emitter-component Socket.io is using | ||
off (name: string, ...args: any[]) { | ||
off(name: string, ...args: any[]) { | ||
if (typeof this.connection.off === 'function') { | ||
const result = this.connection.off(`${this.path} ${name}`, ...args); | ||
const result = this.connection.off(`${this.path} ${name}`, ...args) | ||
return result === this.connection ? this : result; | ||
return result === this.connection ? this : result | ||
} else if (args.length === 0) { | ||
// @ts-ignore | ||
return this.removeAllListeners(name); | ||
return this.removeAllListeners(name) | ||
} | ||
// @ts-ignore | ||
return this.removeListener(name, ...args); | ||
return this.removeListener(name, ...args) | ||
} | ||
} |
@@ -1,10 +0,10 @@ | ||
import { MethodNotAllowed } from '@feathersjs/errors/lib'; | ||
import { HookContext, NullableId, Params } from '@feathersjs/feathers'; | ||
import encodeUrl from 'encodeurl'; | ||
import { MethodNotAllowed } from '@feathersjs/errors/lib' | ||
import { HookContext, NullableId, Params } from '@feathersjs/feathers' | ||
import encodeUrl from 'encodeurl' | ||
export const METHOD_HEADER = 'x-service-method'; | ||
export const METHOD_HEADER = 'x-service-method' | ||
export interface ServiceParams { | ||
id: NullableId, | ||
data: any, | ||
id: NullableId | ||
data: any | ||
params: Params | ||
@@ -19,3 +19,3 @@ } | ||
seeOther: 303 | ||
}; | ||
} | ||
@@ -27,71 +27,71 @@ export const knownMethods: { [key: string]: string } = { | ||
delete: 'remove' | ||
}; | ||
} | ||
export function getServiceMethod (_httpMethod: string, id: unknown, headerOverride?: string) { | ||
const httpMethod = _httpMethod.toLowerCase(); | ||
export function getServiceMethod(_httpMethod: string, id: unknown, headerOverride?: string) { | ||
const httpMethod = _httpMethod.toLowerCase() | ||
if (httpMethod === 'post' && headerOverride) { | ||
return headerOverride; | ||
return headerOverride | ||
} | ||
const mappedMethod = knownMethods[httpMethod]; | ||
const mappedMethod = knownMethods[httpMethod] | ||
if (mappedMethod) { | ||
return mappedMethod; | ||
return mappedMethod | ||
} | ||
if (httpMethod === 'get') { | ||
return id === null ? 'find' : 'get'; | ||
return id === null ? 'find' : 'get' | ||
} | ||
throw new MethodNotAllowed(`Method ${_httpMethod} not allowed`); | ||
throw new MethodNotAllowed(`Method ${_httpMethod} not allowed`) | ||
} | ||
export const argumentsFor = { | ||
get: ({ id, params }: ServiceParams) => [ id, params ], | ||
find: ({ params }: ServiceParams) => [ params ], | ||
create: ({ data, params }: ServiceParams) => [ data, params ], | ||
update: ({ id, data, params }: ServiceParams) => [ id, data, params ], | ||
patch: ({ id, data, params }: ServiceParams) => [ id, data, params ], | ||
remove: ({ id, params }: ServiceParams) => [ id, params ], | ||
default: ({ data, params }: ServiceParams) => [ data, params ] | ||
get: ({ id, params }: ServiceParams) => [id, params], | ||
find: ({ params }: ServiceParams) => [params], | ||
create: ({ data, params }: ServiceParams) => [data, params], | ||
update: ({ id, data, params }: ServiceParams) => [id, data, params], | ||
patch: ({ id, data, params }: ServiceParams) => [id, data, params], | ||
remove: ({ id, params }: ServiceParams) => [id, params], | ||
default: ({ data, params }: ServiceParams) => [data, params] | ||
} | ||
export function getStatusCode (context: HookContext, body: any, location: string|string[]) { | ||
const { http = {} } = context; | ||
export function getStatusCode(context: HookContext, body: any, location: string | string[]) { | ||
const { http = {} } = context | ||
if (http.status) { | ||
return http.status; | ||
return http.status | ||
} | ||
if (context.method === 'create') { | ||
return statusCodes.created; | ||
return statusCodes.created | ||
} | ||
if (location !== undefined) { | ||
return statusCodes.seeOther; | ||
return statusCodes.seeOther | ||
} | ||
if (!body) { | ||
return statusCodes.noContent; | ||
return statusCodes.noContent | ||
} | ||
return statusCodes.success; | ||
return statusCodes.success | ||
} | ||
export function getResponse (context: HookContext) { | ||
const { http = {} } = context; | ||
const body = context.dispatch !== undefined ? context.dispatch : context.result; | ||
export function getResponse(context: HookContext) { | ||
const { http = {} } = context | ||
const body = context.dispatch !== undefined ? context.dispatch : context.result | ||
let headers = http.headers || {}; | ||
let location = headers.Location; | ||
let headers = http.headers || {} | ||
let location = headers.Location | ||
if (http.location !== undefined) { | ||
location = encodeUrl(http.location); | ||
headers = { ...headers, Location: location }; | ||
location = encodeUrl(http.location) | ||
headers = { ...headers, Location: location } | ||
} | ||
const status = getStatusCode(context, body, location); | ||
const status = getStatusCode(context, body, location) | ||
return { status, headers, body }; | ||
return { status, headers, body } | ||
} |
@@ -1,13 +0,6 @@ | ||
import { socket } from './socket'; | ||
import { routing } from './routing'; | ||
import { channels, Channel, CombinedChannel, RealTimeConnection } from './channels'; | ||
import { socket } from './socket' | ||
import { routing } from './routing' | ||
import { channels, Channel, CombinedChannel, RealTimeConnection } from './channels' | ||
export * as http from './http'; | ||
export { | ||
socket, | ||
routing, | ||
channels, | ||
Channel, | ||
CombinedChannel, | ||
RealTimeConnection | ||
}; | ||
export * as http from './http' | ||
export { socket, routing, channels, Channel, CombinedChannel, RealTimeConnection } |
@@ -1,50 +0,54 @@ | ||
import { Application, Service, ServiceOptions } from '@feathersjs/feathers'; | ||
import { Router } from './router'; | ||
import { Application, Service, ServiceOptions } from '@feathersjs/feathers' | ||
import { Router } from './router' | ||
declare module '@feathersjs/feathers/lib/declarations' { | ||
interface RouteLookup { | ||
service: Service, | ||
service: Service | ||
params: { [key: string]: any } | ||
} | ||
interface Application<Services, Settings> { // eslint-disable-line | ||
interface Application<Services, Settings> { | ||
// eslint-disable-line | ||
routes: Router<{ | ||
service: Service, | ||
service: Service | ||
params?: { [key: string]: any } | ||
}>; | ||
lookup (path: string): RouteLookup; | ||
}> | ||
lookup(path: string): RouteLookup | ||
} | ||
} | ||
export * from './router'; | ||
export * from './router' | ||
const lookup = function (this: Application, path: string) { | ||
const result = this.routes.lookup(path); | ||
const result = this.routes.lookup(path) | ||
if (result === null) { | ||
return null; | ||
return null | ||
} | ||
const { params: colonParams, data: { service, params: dataParams } } = result; | ||
const { | ||
params: colonParams, | ||
data: { service, params: dataParams } | ||
} = result | ||
const params = dataParams ? { ...dataParams, ...colonParams } : colonParams; | ||
const params = dataParams ? { ...dataParams, ...colonParams } : colonParams | ||
return { service, params }; | ||
}; | ||
return { service, params } | ||
} | ||
export const routing = () => (app: Application) => { | ||
if (typeof app.lookup === 'function') { | ||
return; | ||
return | ||
} | ||
app.routes = new Router(); | ||
app.lookup = lookup; | ||
app.routes = new Router() | ||
app.lookup = lookup | ||
// Add a mixin that registers a service on the router | ||
app.mixins.push((service: Service, path: string, options: ServiceOptions) => { | ||
const { routeParams: params = {} } = options; | ||
const { routeParams: params = {} } = options | ||
app.routes.insert(path, { service, params }); | ||
app.routes.insert(`${path}/:__id`, { service, params }); | ||
}); | ||
}; | ||
app.routes.insert(path, { service, params }) | ||
app.routes.insert(`${path}/:__id`, { service, params }) | ||
}) | ||
} |
@@ -1,77 +0,79 @@ | ||
import { stripSlashes } from '@feathersjs/commons'; | ||
import { stripSlashes } from '@feathersjs/commons' | ||
export interface LookupData { | ||
params: { [key: string]: string }; | ||
params: { [key: string]: string } | ||
} | ||
export interface LookupResult<T> extends LookupData { | ||
data?: T; | ||
data?: T | ||
} | ||
export class RouteNode<T = any> { | ||
data?: T; | ||
children: { [key: string]: RouteNode } = {}; | ||
placeholders: RouteNode[] = []; | ||
data?: T | ||
children: { [key: string]: RouteNode } = {} | ||
placeholders: RouteNode[] = [] | ||
constructor (public name: string, public depth: number) {} | ||
constructor(public name: string, public depth: number) {} | ||
insert (path: string[], data: T): RouteNode<T> { | ||
insert(path: string[], data: T): RouteNode<T> { | ||
if (this.depth === path.length) { | ||
if (this.data !== undefined) { | ||
throw new Error(`Path ${path.join('/')} already exists`); | ||
throw new Error(`Path ${path.join('/')} already exists`) | ||
} | ||
this.data = data; | ||
return this; | ||
this.data = data | ||
return this | ||
} | ||
const current = path[this.depth]; | ||
const nextDepth = this.depth + 1; | ||
const current = path[this.depth] | ||
const nextDepth = this.depth + 1 | ||
if (current.startsWith(':')) { | ||
// Insert a placeholder node like /messages/:id | ||
const placeholderName = current.substring(1); | ||
let placeholder = this.placeholders.find(p => p.name === placeholderName); | ||
const placeholderName = current.substring(1) | ||
let placeholder = this.placeholders.find((p) => p.name === placeholderName) | ||
if (!placeholder) { | ||
placeholder = new RouteNode(placeholderName, nextDepth); | ||
this.placeholders.push(placeholder); | ||
placeholder = new RouteNode(placeholderName, nextDepth) | ||
this.placeholders.push(placeholder) | ||
} | ||
return placeholder.insert(path, data); | ||
return placeholder.insert(path, data) | ||
} | ||
const child = this.children[current] || new RouteNode(current, nextDepth); | ||
const child = this.children[current] || new RouteNode(current, nextDepth) | ||
this.children[current] = child; | ||
this.children[current] = child | ||
return child.insert(path, data); | ||
return child.insert(path, data) | ||
} | ||
lookup (path: string[], info: LookupData): LookupResult<T>|null { | ||
lookup(path: string[], info: LookupData): LookupResult<T> | null { | ||
if (path.length === this.depth) { | ||
return this.data === undefined ? null : { | ||
...info, | ||
data: this.data | ||
} | ||
return this.data === undefined | ||
? null | ||
: { | ||
...info, | ||
data: this.data | ||
} | ||
} | ||
const current = path[this.depth]; | ||
const child = this.children[current]; | ||
const current = path[this.depth] | ||
const child = this.children[current] | ||
if (child) { | ||
return child.lookup(path, info); | ||
return child.lookup(path, info) | ||
} | ||
// This will return the first placeholder that matches early | ||
for(const placeholder of this.placeholders) { | ||
const result = placeholder.lookup(path, info); | ||
for (const placeholder of this.placeholders) { | ||
const result = placeholder.lookup(path, info) | ||
if (result !== null) { | ||
result.params[placeholder.name] = current; | ||
return result; | ||
result.params[placeholder.name] = current | ||
return result | ||
} | ||
} | ||
return null; | ||
return null | ||
} | ||
@@ -81,19 +83,19 @@ } | ||
export class Router<T = any> { | ||
constructor (public root: RouteNode<T> = new RouteNode<T>('', 0)) {} | ||
constructor(public root: RouteNode<T> = new RouteNode<T>('', 0)) {} | ||
getPath (path: string) { | ||
return stripSlashes(path).split('/'); | ||
getPath(path: string) { | ||
return stripSlashes(path).split('/') | ||
} | ||
insert (path: string, data: T) { | ||
return this.root.insert(this.getPath(path), data); | ||
insert(path: string, data: T) { | ||
return this.root.insert(this.getPath(path), data) | ||
} | ||
lookup (path: string) { | ||
lookup(path: string) { | ||
if (typeof path !== 'string') { | ||
return null; | ||
return null | ||
} | ||
return this.root.lookup(this.getPath(path), { params: {} }); | ||
return this.root.lookup(this.getPath(path), { params: {} }) | ||
} | ||
} |
@@ -1,70 +0,70 @@ | ||
import { Application, getServiceOptions, Params } from '@feathersjs/feathers'; | ||
import { createDebug } from '@feathersjs/commons'; | ||
import { channels } from '../channels'; | ||
import { routing } from '../routing'; | ||
import { getDispatcher, runMethod } from './utils'; | ||
import { RealTimeConnection } from '../channels/channel/base'; | ||
import { Application, getServiceOptions, Params } from '@feathersjs/feathers' | ||
import { createDebug } from '@feathersjs/commons' | ||
import { channels } from '../channels' | ||
import { routing } from '../routing' | ||
import { getDispatcher, runMethod } from './utils' | ||
import { RealTimeConnection } from '../channels/channel/base' | ||
const debug = createDebug('@feathersjs/transport-commons'); | ||
const debug = createDebug('@feathersjs/transport-commons') | ||
export interface SocketOptions { | ||
done: Promise<any>; | ||
emit: string; | ||
socketMap: WeakMap<RealTimeConnection, any>; | ||
socketKey?: any; | ||
getParams: (socket: any) => RealTimeConnection; | ||
done: Promise<any> | ||
emit: string | ||
socketMap: WeakMap<RealTimeConnection, any> | ||
socketKey?: any | ||
getParams: (socket: any) => RealTimeConnection | ||
} | ||
export function socket ({ done, emit, socketMap, socketKey, getParams }: SocketOptions) { | ||
export function socket({ done, emit, socketMap, socketKey, getParams }: SocketOptions) { | ||
return (app: Application) => { | ||
const leaveChannels = (connection: RealTimeConnection) => { | ||
const { channels } = app; | ||
const { channels } = app | ||
if (channels.length) { | ||
app.channel(app.channels).leave(connection); | ||
app.channel(app.channels).leave(connection) | ||
} | ||
}; | ||
} | ||
app.configure(channels()); | ||
app.configure(routing()); | ||
app.configure(channels()) | ||
app.configure(routing()) | ||
app.on('publish', getDispatcher(emit, socketMap, socketKey)); | ||
app.on('disconnect', leaveChannels); | ||
app.on('publish', getDispatcher(emit, socketMap, socketKey)) | ||
app.on('disconnect', leaveChannels) | ||
app.on('logout', (_authResult: any, params: Params) => { | ||
const { connection } = params; | ||
const { connection } = params | ||
if (connection) { | ||
leaveChannels(connection); | ||
leaveChannels(connection) | ||
} | ||
}); | ||
}) | ||
// `connection` event | ||
done.then(provider => provider.on('connection', (connection: any) => | ||
app.emit('connection', getParams(connection))) | ||
); | ||
done.then((provider) => | ||
provider.on('connection', (connection: any) => app.emit('connection', getParams(connection))) | ||
) | ||
// `socket.emit('methodName', 'serviceName', ...args)` handlers | ||
done.then(provider => provider.on('connection', (connection: any) => { | ||
const methodHandlers = Object.keys(app.services).reduce((result, name) => { | ||
const { methods } = getServiceOptions(app.service(name)); | ||
done.then((provider) => | ||
provider.on('connection', (connection: any) => { | ||
const methodHandlers = Object.keys(app.services).reduce((result, name) => { | ||
const { methods } = getServiceOptions(app.service(name)) | ||
methods.forEach(method => { | ||
if (!result[method]) { | ||
result[method] = (...args: any[]) => { | ||
const path = args.shift(); | ||
methods.forEach((method) => { | ||
if (!result[method]) { | ||
result[method] = (...args: any[]) => { | ||
const path = args.shift() | ||
debug(`Got '${method}' call for service '${path}'`); | ||
runMethod(app, getParams(connection), path, method, args); | ||
debug(`Got '${method}' call for service '${path}'`) | ||
runMethod(app, getParams(connection), path, method, args) | ||
} | ||
} | ||
} | ||
}); | ||
}) | ||
return result; | ||
}, {} as any); | ||
return result | ||
}, {} as any) | ||
Object.keys(methodHandlers).forEach(key => | ||
connection.on(key, methodHandlers[key]) | ||
); | ||
})); | ||
}; | ||
Object.keys(methodHandlers).forEach((key) => connection.on(key, methodHandlers[key])) | ||
}) | ||
) | ||
} | ||
} |
@@ -1,11 +0,11 @@ | ||
import { HookContext, Application, createContext, getServiceOptions } from '@feathersjs/feathers'; | ||
import { NotFound, MethodNotAllowed, BadRequest } from '@feathersjs/errors'; | ||
import { createDebug } from '@feathersjs/commons'; | ||
import isEqual from 'lodash/isEqual'; | ||
import { CombinedChannel } from '../channels/channel/combined'; | ||
import { RealTimeConnection } from '../channels/channel/base'; | ||
import { HookContext, Application, createContext, getServiceOptions } from '@feathersjs/feathers' | ||
import { NotFound, MethodNotAllowed, BadRequest } from '@feathersjs/errors' | ||
import { createDebug } from '@feathersjs/commons' | ||
import isEqual from 'lodash/isEqual' | ||
import { CombinedChannel } from '../channels/channel/combined' | ||
import { RealTimeConnection } from '../channels/channel/base' | ||
const debug = createDebug('@feathersjs/transport-commons'); | ||
const debug = createDebug('@feathersjs/transport-commons') | ||
export const DEFAULT_PARAMS_POSITION = 1; | ||
export const DEFAULT_PARAMS_POSITION = 1 | ||
@@ -16,35 +16,35 @@ export const paramsPositions: { [key: string]: number } = { | ||
patch: 2 | ||
}; | ||
} | ||
export function normalizeError (e: any) { | ||
const hasToJSON = typeof e.toJSON === 'function'; | ||
const result = hasToJSON ? e.toJSON() : {}; | ||
export function normalizeError(e: any) { | ||
const hasToJSON = typeof e.toJSON === 'function' | ||
const result = hasToJSON ? e.toJSON() : {} | ||
if (!hasToJSON) { | ||
Object.getOwnPropertyNames(e).forEach(key => { | ||
result[key] = e[key]; | ||
}); | ||
Object.getOwnPropertyNames(e).forEach((key) => { | ||
result[key] = e[key] | ||
}) | ||
} | ||
if (process.env.NODE_ENV === 'production') { | ||
delete result.stack; | ||
delete result.stack | ||
} | ||
delete result.hook; | ||
delete result.hook | ||
return result; | ||
return result | ||
} | ||
export function getDispatcher (emit: string, socketMap: WeakMap<RealTimeConnection, any>, socketKey?: any) { | ||
export function getDispatcher(emit: string, socketMap: WeakMap<RealTimeConnection, any>, socketKey?: any) { | ||
return function (event: string, channel: CombinedChannel, context: HookContext, data?: any) { | ||
debug(`Dispatching '${event}' to ${channel.length} connections`); | ||
debug(`Dispatching '${event}' to ${channel.length} connections`) | ||
channel.connections.forEach(connection => { | ||
channel.connections.forEach((connection) => { | ||
// The reference between connection and socket is set in `app.setup` | ||
const socket = socketKey ? connection[socketKey] : socketMap.get(connection); | ||
const socket = socketKey ? connection[socketKey] : socketMap.get(connection) | ||
if (socket) { | ||
const eventName = `${context.path || ''} ${event}`.trim(); | ||
const eventName = `${context.path || ''} ${event}`.trim() | ||
let result = channel.dataFor(connection) || context.dispatch || context.result; | ||
let result = channel.dataFor(connection) || context.dispatch || context.result | ||
@@ -54,63 +54,70 @@ // If we are getting events from an array but try to dispatch individual data | ||
if (!Array.isArray(data) && Array.isArray(context.result) && Array.isArray(result)) { | ||
result = context.result.find(resultData => isEqual(resultData, data)); | ||
result = context.result.find((resultData) => isEqual(resultData, data)) | ||
} | ||
debug(`Dispatching '${eventName}' to Socket ${socket.id} with`, result); | ||
debug(`Dispatching '${eventName}' to Socket ${socket.id} with`, result) | ||
socket[emit](eventName, result); | ||
socket[emit](eventName, result) | ||
} | ||
}); | ||
}; | ||
}) | ||
} | ||
} | ||
export async function runMethod (app: Application, connection: RealTimeConnection, path: string, method: string, args: any[]) { | ||
const trace = `method '${method}' on service '${path}'`; | ||
const methodArgs = args.slice(0); | ||
const callback = typeof methodArgs[methodArgs.length - 1] === 'function' | ||
? methodArgs.pop() : function () {}; | ||
export async function runMethod( | ||
app: Application, | ||
connection: RealTimeConnection, | ||
path: string, | ||
method: string, | ||
args: any[] | ||
) { | ||
const trace = `method '${method}' on service '${path}'` | ||
const methodArgs = args.slice(0) | ||
const callback = | ||
// eslint-disable-next-line @typescript-eslint/no-empty-function | ||
typeof methodArgs[methodArgs.length - 1] === 'function' ? methodArgs.pop() : function () {} | ||
debug(`Running ${trace}`, connection, args); | ||
debug(`Running ${trace}`, connection, args) | ||
const handleError = (error: any) => { | ||
debug(`Error in ${trace}`, error); | ||
callback(normalizeError(error)); | ||
}; | ||
debug(`Error in ${trace}`, error) | ||
callback(normalizeError(error)) | ||
} | ||
try { | ||
const lookup = app.lookup(path); | ||
const lookup = app.lookup(path) | ||
// No valid service was found throw a NotFound error | ||
if (lookup === null) { | ||
throw new NotFound(`Service '${path}' not found`); | ||
throw new NotFound(`Service '${path}' not found`) | ||
} | ||
const { service, params: route = {} } = lookup; | ||
const { methods } = getServiceOptions(service); | ||
const { service, params: route = {} } = lookup | ||
const { methods } = getServiceOptions(service) | ||
// Only service methods are allowed | ||
if (!methods.includes(method)) { | ||
throw new MethodNotAllowed(`Method '${method}' not allowed on service '${path}'`); | ||
throw new MethodNotAllowed(`Method '${method}' not allowed on service '${path}'`) | ||
} | ||
const position = paramsPositions[method] !== undefined ? paramsPositions[method] : DEFAULT_PARAMS_POSITION; | ||
const query = Object.assign({}, methodArgs[position]); | ||
const position = paramsPositions[method] !== undefined ? paramsPositions[method] : DEFAULT_PARAMS_POSITION | ||
const query = Object.assign({}, methodArgs[position]) | ||
// `params` have to be re-mapped to the query and added with the route | ||
const params = Object.assign({ query, route, connection }, connection); | ||
const params = Object.assign({ query, route, connection }, connection) | ||
// `params` is always the last parameter. Error if we got more arguments. | ||
if (methodArgs.length > (position + 1)) { | ||
throw new BadRequest(`Too many arguments for '${method}' method`); | ||
if (methodArgs.length > position + 1) { | ||
throw new BadRequest(`Too many arguments for '${method}' method`) | ||
} | ||
methodArgs[position] = params; | ||
methodArgs[position] = params | ||
const ctx = createContext(service, method); | ||
const returnedCtx: HookContext = await (service as any)[method](...methodArgs, ctx); | ||
const result = returnedCtx.dispatch || returnedCtx.result; | ||
const ctx = createContext(service, method) | ||
const returnedCtx: HookContext = await (service as any)[method](...methodArgs, ctx) | ||
const result = returnedCtx.dispatch || returnedCtx.result | ||
debug(`Returned successfully ${trace}`, result); | ||
callback(null, result); | ||
debug(`Returned successfully ${trace}`, result) | ||
callback(null, result) | ||
} catch (error: any) { | ||
handleError(error); | ||
handleError(error) | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
130142
1667