Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@logux/server

Package Overview
Dependencies
Maintainers
1
Versions
51
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@logux/server - npm Package Compare versions

Comparing version 0.7.0 to 0.7.1

18

bind-control-server/index.js

@@ -25,2 +25,6 @@ let ip = require('ip')

if (masks.every(i => !i.contains(ipAddress))) {
app.reporter('wrongControlIp', {
ipAddress,
mask: app.options.controlMask
})
res.statusCode = 403

@@ -69,5 +73,9 @@ res.end('IP address is not in LOGUX_CONTROL_MASK/controlMask')

} else if (body.secret !== app.options.controlSecret) {
app.rememberBadAuth(req.connection.remoteAddress)
app.reporter('wrongControlSecret', {
ipAddress,
wrongSecret: body.secret
})
res.statusCode = 403
res.end('Wrong secret')
app.rememberBadAuth(req.connection.remoteAddress)
} else {

@@ -88,3 +96,3 @@ for (let i of body.commands) {

if (!app.options.controlSecret) {
res.statusCode = 403
res.statusCode = 500
res.end(NO_SECRET)

@@ -97,5 +105,9 @@ return

} else if (reqUrl.search !== '?' + app.options.controlSecret) {
app.rememberBadAuth(req.connection.remoteAddress)
app.reporter('wrongControlSecret', {
ipAddress,
wrongSecret: reqUrl.search.slice(1)
})
res.statusCode = 403
res.end('Wrong secret')
app.rememberBadAuth(req.connection.remoteAddress)
return

@@ -102,0 +114,0 @@ }

# Change Log
This project adheres to [Semantic Versioning](http://semver.org/).
## 0.7.1
* Print to the log about denied control requests attempts.
* Fix server options types.
* Return status code 500 on control requests if server has no secret.
## 0.7 “Eliza Cassan”

@@ -5,0 +10,0 @@ * Use Logux Core 0.5 and WebSocket Protocol 3.

6

context/index.d.ts
import { Action } from '@logux/core'
import BaseServer, { BaseServerOptions, ServerMeta } from '../base-server'
import Server, { ServerMeta } from '../server'

@@ -31,3 +31,3 @@ /**

subprotocol: string,
server: BaseServer
server: Server
)

@@ -100,3 +100,3 @@

*/
server: BaseServer
server: Server

@@ -103,0 +103,0 @@ /**

@@ -119,2 +119,12 @@ let bunyan = require('bunyan')

wrongControlSecret: () => ({
level: 'warn',
msg: 'Wrong secret in control request'
}),
wrongControlIp: () => ({
level: 'warn',
msg: 'IP address of control request do not pass the mask'
}),
unknownType: record => ({

@@ -121,0 +131,0 @@ level: /^ server(:| )/.test(record.actionId) ? 'error' : 'warn',

import ServerClient from './server-client'
import ALLOWED_META from './allowed-meta'
import BaseServer from './base-server'
import Server from './server'

@@ -15,5 +14,5 @@

LoguxUnsubscribeAction,
ServerMeta
} from './base-server'
export { ServerOptions } from './server'
ServerMeta,
ServerOptions
} from './server'

@@ -23,4 +22,3 @@ export {

ALLOWED_META,
BaseServer,
Server
}
{
"name": "@logux/server",
"version": "0.7.0",
"version": "0.7.1",
"description": "Build own Logux server or make proxy between WebSocket and HTTP backend on any language",

@@ -5,0 +5,0 @@ "keywords": [

import { ServerConnection, ServerNode } from '@logux/core'
import BaseServer from '../base-server'
import Server from '../server'

@@ -18,3 +18,3 @@ /**

*/
constructor(app: BaseServer, connection: ServerConnection, key: number)
constructor(app: Server, connection: ServerConnection, key: number)

@@ -24,3 +24,3 @@ /**

*/
app: BaseServer
app: Server

@@ -27,0 +27,0 @@ /**

@@ -0,7 +1,297 @@

import {
Action, AnyAction, ID, Log, Meta, ServerConnection, Store, TestTime
} from '@logux/core'
import { Server as HTTPServer } from 'http'
import { Unsubscribe } from 'nanoevents'
import BunyanLogger = require('bunyan')
import BaseServer, { Reporter, BaseServerOptions } from '../base-server'
import Context, { ChannelContext } from '../context'
import ServerClient from '../server-client'
export type ServerOptions = BaseServerOptions & {
export type ServerMeta = Meta & {
/**
* Action processing status
*/
status?: 'waiting' | 'processed' | 'error'
/**
* Node ID of the server received the action.
*/
server: string
/**
* All clients subscribed to listed channels will receive the action.
*/
channels?: string[]
/**
* All clients subscribed to channel will receive the action.
*/
channel?: string
/**
* All clients with listed user IDs will receive the action.
*/
users?: string[]
/**
* All clients with listed user ID will receive the action.
*/
user?: string
/**
* All clients with listed client IDs will receive the action.
*/
clients?: string[]
/**
* All clients with listed client ID will receive the action.
*/
client?: string
/**
* All clients with listed node IDs will receive the action.
*/
nodes?: string[]
/**
* Client with listed node ID will receive the action.
*/
node?: string
}
export type LoguxSubscribeAction = {
type: 'logux/subscribe'
channel: string
since?: {
id: string
time: number
}
}
export type LoguxUnsubscribeAction = {
type: 'logux/unsubscribe'
channel: string
}
export type LoguxProcessedAction = {
type: 'logux/processed'
id: ID
}
export type LoguxUndoAction = {
type: 'logux/undo'
id: ID
reason?: string
}
export type LoguxAction =
| LoguxSubscribeAction
| LoguxUnsubscribeAction
| LoguxProcessedAction
| LoguxUndoAction
type ActionReporter = {
action: Action
meta: ServerMeta
}
type SubscriptionReporter = {
actionId: ID
channel: string
}
type CleanReporter = {
actionId: ID
}
type AuthenticationReporter = {
connectionId: string
subprotocol: string
nodeId: string
}
type ReportersArguments = {
add: ActionReporter
useless: ActionReporter
clean: CleanReporter
error: {
err: Error
fatal?: true
actionId?: ID
nodeId?: string
connectionId?: string
}
clientError: {
err: Error
nodeId?: string
connectionId?: string
}
connect: {
connectionId: string
ipAddress: string
}
disconnect: {
nodeId?: string
connectionId?: string
}
destroy: void
unknownType: {
type: string
actionId: ID
}
wrongChannel: SubscriptionReporter
processed: {
actionId: ID
latency: number
}
subscribed: SubscriptionReporter
unsubscribed: SubscriptionReporter
denied: CleanReporter
authenticated: AuthenticationReporter
unauthenticated: AuthenticationReporter
zombie: {
nodeId: string
}
listen: {
controlSecret: string
controlMask: string,
loguxServer: string
environment: 'production' | 'development'
subprotocol: string
supports: string
backend: string
server: boolean
nodeId: string
redis: string
notes: object
cert: boolean
host: string
port: string
}
}
export type Reporter = <E extends keyof ReportersArguments> (
event: E, payload: ReportersArguments[E]
) => void
export type Resend = {
channel?: string
channels?: string[]
user?: string
users?: string[]
client?: string
clients?: string[]
node?: string
nodes?: string[]
}
export type ServerOptions = {
/**
* Server current application subprotocol version in SemVer format.
*/
subprotocol: string
/**
* npm’s version requirements for client subprotocol version.
*/
supports: string
/**
* Application root to load files and show errors.
* Default is `process.cwd()`.
*/
root?: string
/**
* Timeout in milliseconds to disconnect connection.
* Default is `20000`.
*/
timeout?: number
/**
* Milliseconds since last message to test connection by sending ping.
* Default is `10000`.
*/
ping?: number
/**
* URL to PHP, Ruby on Rails, or other backend to process actions and
* authentication.
*/
backend?: string
/**
* URL to Redis for Logux Server Pro scaling.
*/
redis?: string
/**
* Secret to control the server.
*/
controlSecret?: string
/**
* CIDR masks for IP address, where control requests could came from.
*/
controlMask?: string
/**
* Store to save log. Will be {@link @logux/core:MemoryStore}, by default.
*/
store?: Store
/**
* Test time to test server.
*/
time?: TestTime
/**
* Custom random ID to be used in node ID.
*/
id?: string
/**
* Development or production server mode. By default,
* it will be taken from `NODE_ENV` environment variable.
* On empty `NODE_ENV` it will be `'development'`.
*/
env?: 'production' | 'development'
/**
* Process ID, to display in reporter.
*/
pid?: number
/**
* HTTP server to connect WebSocket server to it. Same as in `ws.Server`.
*/
server?: HTTPServer
/**
* Port to bind server. It will create HTTP server manually to connect
* WebSocket server to it. Default is `31337`.
*/
port?: number
/**
* IP-address to bind server. Default is `127.0.0.1`.
*/
host?: string
/**
* SSL key or path to it. Path could be relative from server root.
* It is required in production mode, because WSS is highly recommended.
*/
key?: string
/**
* SSL certificate or path to it. Path could be relative from server
* root. It is required in production mode, because WSS is highly
* recommended.
*/
cert?: string
/**
* Report process/errors to CLI in bunyan JSON or in human readable

@@ -20,2 +310,154 @@ * format. It can be also a function to show current server status.

/**
* The authentication callback.
*
* @param userId User ID.
* @param token The client credentials.
* @param client Client object.
* @returns `true` if credentials was correct
*/
interface Authenticator {
(
userId: string,
token: string,
server: ServerClient
): boolean | Promise<boolean>
}
/**
* Check does user can do this action.
*
* @param ctx Information about node, who create this action.
* @param action The action data.
* @param meta The action metadata.
* @returns `true` if client are allowed to use this action.
*/
interface Authorizer<A extends Action, D extends object> {
(ctx: Context<D>, action: A, meta: ServerMeta): boolean | Promise<boolean>
}
/**
* Return object with keys for meta to resend action to other users.
*
* @param ctx Information about node, who create this action.
* @param action The action data.
* @param meta The action metadata.
* @returns Meta’s keys.
*/
interface Resender<A extends Action, D extends object> {
(ctx: Context<D>, action: A, meta: ServerMeta): Resend | Promise<Resend>
}
/**
* Action business logic.
*
* @param ctx Information about node, who create this action.
* @param action The action data.
* @param meta The action metadata.
* @returns Promise when processing will be finished.
*/
interface Processor<A extends Action, D extends object> {
(ctx: Context<D>, action: A, meta: ServerMeta): void | Promise<void>
}
/**
* Callback which will be run on the end of action/subscription
* processing or on an error.
*
* @param ctx Information about node, who create this action.
* @param action The action data.
* @param meta The action metadata.
*/
interface ActionFinally<A extends Action, D extends object> {
(ctx: Context<D>, action: A, meta: ServerMeta): void
}
/**
* Channel filter callback
*
* @param ctx Information about node, who create this action.
* @param action The action data.
* @param meta The action metadata.
* @returns Should action be sent to client.
*/
interface ChannelFilter {
(ctx: Context<{ }>, action: Action, meta: ServerMeta): boolean
}
/**
* Channel authorizer callback
*
* @param ctx Information about node, who create this action.
* @param action The action data.
* @param meta The action metadata.
* @returns `true` if client are allowed to subscribe to this channel.
*/
interface ChannelAuthorizer<
A extends Action, D extends object, P extends object | string[]
> {
(
ctx: ChannelContext<D, P>, action: A, meta: ServerMeta
): boolean | Promise<boolean>
}
/**
* Generates custom filter for channel’s actions.
*
* @param ctx Information about node, who create this action.
* @param action The action data.
* @param meta The action metadata.
* @returns Actions filter.
*/
interface FilterCreator<
A extends Action, D extends object, P extends object | string[]
> {
(
ctx: ChannelContext<D, P>, action: A, meta: ServerMeta
): Promise<ChannelFilter> | ChannelFilter | void
}
/**
* Send actions with initial state.
*
* @param ctx Information about node, who create this action.
* @param action The action data.
* @param meta The action metadata.
* @returns Promise during initial actions loading.
*/
interface ChannelInitialized<
A extends Action, D extends object, P extends object | string[]
> {
(ctx: ChannelContext<D, P>, action: A, meta: ServerMeta): void | Promise<void>
}
/**
* Callback which will be run on the end of subscription
* processing or on an error.
*
* @param ctx Information about node, who create this action.
* @param action The action data.
* @param meta The action metadata.
*/
interface ChannelFinally<
A extends Action, D extends object, P extends object | string[]
> {
(ctx: ChannelContext<D, P>, action: A, meta: ServerMeta): void
}
type ActionCallbacks<A extends Action, D extends object> = {
access: Authorizer<A, D>
resend?: Resender<A, D>
process?: Processor<A, D>
finally?: ActionFinally<A, D>
}
type ChannelCallbacks<
A extends Action, D extends object, P extends object | string[]
> = {
access: ChannelAuthorizer<A, D, P>
filter?: FilterCreator<A, D, P>
init?: ChannelInitialized<A, D, P>
finally?: ChannelFinally<A, D, P>
}
/**
* End-user API to create Logux server.

@@ -42,3 +484,3 @@ *

*/
export default class Server extends BaseServer {
export default class Server {
/**

@@ -79,2 +521,380 @@ * Load options from command-line arguments and/or environment

options: ServerOptions
/**
* Function to show current server status.
*/
reporter: Reporter
/**
* Production or development mode.
*
* ```js
* if (server.env === 'development') {
* logDebugData()
* }
* ```
*/
env: 'production' | 'development'
/**
* Server unique ID.
*
* ```js
* console.log('Error was raised on ' + server.nodeId)
* ```
*/
nodeId: string
/**
* Server actions log.
*
* ```js
* server.log.each(finder)
* ```
*/
log: Log<ServerMeta>
/**
* Connected clients.
*
* ```js
* for (let i in server.connected) {
* console.log(server.connected[i].remoteAddress)
* }
* ```
*/
connected: {
[key: string]: ServerClient
}
/**
* Set authenticate function. It will receive client credentials
* and node ID. It should return a Promise with `true` or `false`.
*
* ```js
* server.auth(async (userId, token) => {
* const user = await findUserByToken(token)
* return !!user && userId === user.id
* })
* ```
*
* @param authenticator The authentication callback.
*/
auth (authenticator: Authenticator): void
/**
* Start WebSocket server and listen for clients.
*
* @returns When the server has been bound.
*/
listen (): Promise<void>
/**
* Subscribe for synchronization events. It implements nanoevents API.
* Supported events:
*
* * `error`: server error during action processing.
* * `fatal`: server error during loading.
* * `clientError`: wrong client behaviour.
* * `connected`: new client was connected.
* * `disconnected`: client was disconnected.
* * `preadd`: action is going to be added to the log.
* The best place to set `reasons`.
* * `add`: action was added to the log.
* * `clean`: action was cleaned from the log.
* * `processed`: action processing was finished.
* * `subscribed`: channel initial data was loaded.
*
* ```js
* server.on('error', error => {
* trackError(error)
* })
* ```
*
* @param event The event name.
* @param listener The listener function.
* @returns Unbind listener from event.
*/
on (
event: 'fatal' | 'clientError',
listener: (err: Error) => void
): Unsubscribe
on (
event: 'error',
listener: (err: Error, action: Action, meta: ServerMeta) => void
): Unsubscribe
on (
event: 'connected' | 'disconnected',
listener: (client: ServerClient) => void
): Unsubscribe
on (
event: 'preadd' | 'add' | 'clean',
listener: (action: Action, meta: ServerMeta) => void
): Unsubscribe
on (
event: 'processed',
listener: (
action: Action,
meta: ServerMeta,
latencyMilliseconds: number
) => void
): Unsubscribe
on (
event: 'subscribed',
listener: (
action: LoguxSubscribeAction,
meta: ServerMeta,
latencyMilliseconds: number
) => void
): Unsubscribe
/**
* Stop server and unbind all listeners.
*
* ```js
* afterEach(() => {
* testServer.destroy()
* })
* ```
*
* @returns Promise when all listeners will be removed.
*/
destroy (): Promise<void>
/**
* Define action type’s callbacks.
*
* ```js
* server.type('CHANGE_NAME', {
* access (ctx, action, meta) {
* return action.user === ctx.userId
* },
* resend (ctx, action) {
* return { channel: `user/${ action.user }` }
* }
* process (ctx, action, meta) {
* if (isFirstOlder(lastNameChange(action.user), meta)) {
* return db.changeUserName({ id: action.user, name: action.name })
* }
* }
* })
* ```
*
* @param name The action’s type.
* @param callbacks Callbacks for actions with this type.
*
* @template A Action’s type.
* @template D Type for `ctx.data`.
*/
type<A extends Action = AnyAction, D extends object = { }> (
name: A['type'],
callbacks: ActionCallbacks<A, D>
): void
/**
* Define callbacks for actions, which type was not defined
* by any {@link Server#type}. Useful for proxy or some hacks.
*
* Without this settings, server will call {@link Server#unknownType}
* on unknown type.
*
* ```js
* server.otherType(
* async access (ctx, action, meta) {
* const response = await phpBackend.checkByHTTP(action, meta)
* if (response.code === 404) {
* this.unknownType(action, meta)
* return false
* } else {
* return response.body === 'granted'
* }
* }
* async process (ctx, action, meta) {
* return await phpBackend.sendHTTP(action, meta)
* }
* })
* ```
*
* @param callbacks Callbacks for actions with this type.
*
* @template D Type for `ctx.data`.
*/
otherType<
D extends object = { }
> (callbacks: ActionCallbacks<Action, D>): void
/**
* Define the channel.
*
* ```js
* server.channel('user/:id', {
* access (ctx, action, meta) {
* return ctx.params.id === ctx.userId
* }
* filter (ctx, action, meta) {
* return (otherCtx, otherAction, otherMeta) => {
* return !action.hidden
* }
* }
* async init (ctx, action, meta) {
* const user = await db.loadUser(ctx.params.id)
* ctx.sendBack({ type: 'USER_NAME', name: user.name })
* }
* })
* ```
*
* @param pattern Pattern or regular expression for channel name.
* @param callbacks Callback during subscription process.
*
* @template P Type for `ctx.params`.
* @template D Type for `ctx.data`.
* @template A `logux/subscribe` Action’s type.
*/
channel<
P extends object = { },
D extends object = { },
A extends LoguxSubscribeAction = LoguxSubscribeAction
> (pattern: string, callbacks: ChannelCallbacks<A, D, P>): void
channel<
P extends string[] = string[],
D extends object = { },
A extends LoguxSubscribeAction = LoguxSubscribeAction
> (pattern: RegExp, callbacks: ChannelCallbacks<A, D, P>): void
/**
* Set callbacks for unknown channel subscription.
*
*```js
* server.otherChannel({
* async access (ctx, action, meta) {
* const res = await phpBackend.checkChannel(ctx.params[0], ctx.userId)
* if (res.code === 404) {
* this.wrongChannel(action, meta)
* return false
* } else {
* return response.body === 'granted'
* }
* }
* })
* ```
*
* @param callbacks Callback during subscription process.
*
* @template D Type for `ctx.data`.
* @template A `logux/subscribe` Action’s type.
*/
otherChannel<
D extends object = { },
> (callbacks: ChannelCallbacks<LoguxSubscribeAction, D, { }>): void
/**
* Undo action from client.
*
* ```js
* if (couldNotFixConflict(action, meta)) {
* server.undo(meta)
* }
* ```
*
* @param meta The action’s metadata.
* @param reason Optional code for reason. Default is `'error'`
* @param extra Extra fields to `logux/undo` action.
* @returns When action was saved to the log.
*/
undo (meta: ServerMeta, reason?: string, extra?: object): Promise<void>
/**
* Send runtime error stacktrace to all clients.
*
* ```js
* process.on('uncaughtException', e => {
* server.debugError(e)
* })
* ```
*
* @param error Runtime error instance.
*/
debugError (error: Error): void
/**
* Send action, received by other server, to all clients of current server.
* This method is for multi-server configuration only.
*
* ```js
* server.on('add', (action, meta) => {
* if (meta.server === server.nodeId) {
* sendToOtherServers(action, meta)
* }
* })
* onReceivingFromOtherServer((action, meta) => {
* server.sendAction(action, meta)
* })
* ```
*
* @param action New action.
* @param meta Action’s metadata.
*/
sendAction (action: Action, meta: ServerMeta): void
/**
* Add new client for server. You should call this method manually
* mostly for test purposes.
*
* ```js
* server.addClient(test.right)
* ```
*
* @param connection Logux connection to client.
* @returns Client ID.
*/
addClient (connection: ServerConnection): number
/**
* If you receive action with unknown type, this method will mark this action
* with `error` status and undo it on the clients.
*
* If you didn’t set {@link Server#otherType},
* Logux will call it automatically.
*
* ```js
* server.otherType({
* access (ctx, action, meta) {
* if (action.type.startsWith('myapp/')) {
* return proxy.access(action, meta)
* } else {
* server.unknownType(action, meta)
* }
* }
* })
* ```
*
* @param action The action with unknown type.
* @param meta Action’s metadata.
*/
unknownType (action: Action, meta: ServerMeta): void
/**
* Report that client try to subscribe for unknown channel.
*
* Logux call it automatically,
* if you will not set {@link Server#otherChannel}.
*
* ```js
* server.otherChannel({
* async access (ctx, action, meta) {
* const res = phpBackend.checkChannel(params[0], ctx.userId)
* if (res.code === 404) {
* this.wrongChannel(action, meta)
* return false
* } else {
* return response.body === 'granted'
* }
* }
* })
* ```
*
* @param action The subscribe action.
* @param meta Action’s metadata.
*/
wrongChannel (action: LoguxSubscribeAction, meta: ServerMeta): void
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc