@logux/client
Advanced tools
Comparing version 0.8.5 to 0.9.0
@@ -33,13 +33,17 @@ function attention (client) { | ||
if (doc && typeof doc.hidden !== 'undefined') { | ||
unbind.push(client.node.on('error', error => { | ||
if (error.type !== 'timeout' && !timeout) { | ||
blink() | ||
} | ||
})) | ||
unbind.push( | ||
client.node.on('error', error => { | ||
if (error.type !== 'timeout' && !timeout) { | ||
blink() | ||
} | ||
}) | ||
) | ||
unbind.push(client.on('add', action => { | ||
if (action.type === 'logux/undo' && action.reason && !timeout) { | ||
blink() | ||
} | ||
})) | ||
unbind.push( | ||
client.on('add', action => { | ||
if (action.type === 'logux/undo' && action.reason && !timeout) { | ||
blink() | ||
} | ||
}) | ||
) | ||
@@ -46,0 +50,0 @@ document.addEventListener('visibilitychange', tabListener, false) |
@@ -48,5 +48,12 @@ import { Client } from '../client' | ||
*/ | ||
position?: 'top-left' | 'top-center' | 'top-right' | | ||
'middle-left' | 'middle-center' | 'middle-right' | | ||
'bottom-left' | 'bottom-center' | 'bottom-right' | ||
position?: | ||
| 'top-left' | ||
| 'top-center' | ||
| 'top-right' | ||
| 'middle-left' | ||
| 'middle-center' | ||
| 'middle-right' | ||
| 'bottom-left' | ||
| 'bottom-center' | ||
| 'bottom-right' | ||
@@ -53,0 +60,0 @@ /** |
@@ -68,23 +68,27 @@ import { status } from '../status/index.js' | ||
let unbind = status(client, state => { | ||
if (state === 'sendingAfterWait' || state === 'connectingAfterWait') { | ||
show(styles.sending, messages.sending) | ||
} else if (state === 'synchronizedAfterWait') { | ||
show(styles.synchronized, messages.synchronized) | ||
} else if (state === 'synchronized') { | ||
hide(widget) | ||
} else if (state === 'disconnected') { | ||
show(styles.disconnected, messages.disconnected) | ||
} else if (state === 'wait') { | ||
show(styles.wait, messages.wait) | ||
} else if (state === 'protocolError') { | ||
show(styles.protocolError, messages.protocolError) | ||
} else if (state === 'syncError') { | ||
show(styles.error, messages.syncError) | ||
} else if (state === 'error') { | ||
show(styles.error, messages.error) | ||
} else if (state === 'denied') { | ||
show(styles.error, messages.denied) | ||
} | ||
}, opts) | ||
let unbind = status( | ||
client, | ||
state => { | ||
if (state === 'sendingAfterWait' || state === 'connectingAfterWait') { | ||
show(styles.sending, messages.sending) | ||
} else if (state === 'synchronizedAfterWait') { | ||
show(styles.synchronized, messages.synchronized) | ||
} else if (state === 'synchronized') { | ||
hide(widget) | ||
} else if (state === 'disconnected') { | ||
show(styles.disconnected, messages.disconnected) | ||
} else if (state === 'wait') { | ||
show(styles.wait, messages.wait) | ||
} else if (state === 'protocolError') { | ||
show(styles.protocolError, messages.protocolError) | ||
} else if (state === 'syncError') { | ||
show(styles.error, messages.syncError) | ||
} else if (state === 'error') { | ||
show(styles.error, messages.error) | ||
} else if (state === 'denied') { | ||
show(styles.error, messages.denied) | ||
} | ||
}, | ||
opts | ||
) | ||
@@ -91,0 +95,0 @@ widget.appendChild(text) |
# Change Log | ||
This project adheres to [Semantic Versioning](http://semver.org/). | ||
## 0.9 | ||
* Use Logux Core 0.6 and WebSocket Protocol 4. | ||
* Add `CrossTabClient#waitFor()`. | ||
* Add `user` event. | ||
* Improve `log()` output with collapsed groups. | ||
* Show offline in the badge from the beginning. | ||
* Fix `Client#changeUser()`. | ||
## 0.8.5 | ||
@@ -5,0 +13,0 @@ * Remove `global` from `IndexedStore` (by Neville Franks). |
import { Unsubscribe } from 'nanoevents' | ||
import { | ||
Connection, Store, TestTime, Log, ClientNode, Action, Meta, TokenGenerator | ||
Connection, | ||
LogStore, | ||
TestTime, | ||
Log, | ||
ClientNode, | ||
Action, | ||
Meta, | ||
TokenGenerator | ||
} from '@logux/core' | ||
@@ -71,3 +77,3 @@ | ||
*/ | ||
store?: Store | ||
store?: LogStore | ||
@@ -122,3 +128,3 @@ /** | ||
*/ | ||
export class Client { | ||
export class Client<H extends object = {}, L extends Log = Log<ClientMeta>> { | ||
/** | ||
@@ -168,3 +174,3 @@ * @param opts Client options. | ||
*/ | ||
log: Log<ClientMeta> | ||
log: L | ||
@@ -178,3 +184,3 @@ /** | ||
*/ | ||
node: ClientNode<ClientMeta> | ||
node: ClientNode<H, L> | ||
@@ -197,2 +203,3 @@ /** | ||
* * `clean`: action has been removed from log (by any tab). | ||
* * `user`: user ID was changed. | ||
* | ||
@@ -210,4 +217,6 @@ * ```js | ||
on ( | ||
event: 'preadd' | 'add' | 'clean', listener: ClientActionListener | ||
event: 'preadd' | 'add' | 'clean', | ||
listener: ClientActionListener | ||
): Unsubscribe | ||
on (event: 'user', listener: (userId: string) => void): Unsubscribe | ||
@@ -227,6 +236,8 @@ /** | ||
* | ||
* You need manually chang user ID in all browser tabs. | ||
* | ||
* @param userId The new user ID. | ||
* @param token Credentials for new user. | ||
*/ | ||
changeUser (userId: string, token: string): void | ||
changeUser (userId: string, token?: string): void | ||
@@ -233,0 +244,0 @@ /** |
@@ -7,2 +7,3 @@ import { createNanoEvents } from 'nanoevents' | ||
import { Reconnect } from '@logux/core/reconnect' | ||
import { parseId } from '@logux/core/parse-id' | ||
import { nanoid } from 'nanoid' | ||
@@ -26,3 +27,3 @@ import { Log } from '@logux/core/log' | ||
class Client { | ||
constructor (opts = { }) { | ||
constructor (opts = {}) { | ||
this.options = opts | ||
@@ -38,4 +39,6 @@ | ||
if (typeof this.options.userId === 'undefined') { | ||
throw new Error('Missed userId option in Logux client. ' + | ||
'Pass false if you have no users.') | ||
throw new Error( | ||
'Missed userId option in Logux client. ' + | ||
'Pass false if you have no users.' | ||
) | ||
} | ||
@@ -64,3 +67,3 @@ if (this.options.userId === false) { | ||
this.isLocalStorage = true | ||
} catch (e) {} | ||
} catch {} | ||
} | ||
@@ -77,18 +80,2 @@ | ||
this.nodeId = this.clientId + ':' + this.tabId | ||
let auth | ||
if (/^ws:\/\//.test(this.options.server) && !opts.allowDangerousProtocol) { | ||
auth = async (nodeId, env) => { | ||
if (env !== 'development') { | ||
console.error( | ||
'Without SSL, old proxies block WebSockets. ' + | ||
'Use WSS for Logux or set allowDangerousProtocol option.' | ||
) | ||
return false | ||
} else { | ||
return true | ||
} | ||
} | ||
} | ||
let store = this.options.store || new MemoryStore() | ||
@@ -105,4 +92,3 @@ | ||
log.on('preadd', (action, meta) => { | ||
let isOwn = meta.id.includes(` ${ this.nodeId } `) | ||
if (isOwn && !meta.subprotocol) { | ||
if (parseId(meta.id).nodeId === this.nodeId && !meta.subprotocol) { | ||
meta.subprotocol = this.options.subprotocol | ||
@@ -113,6 +99,6 @@ } | ||
this.last = { } | ||
this.subscriptions = { } | ||
let subscribing = { } | ||
let unsubscribing = { } | ||
this.last = {} | ||
this.subscriptions = {} | ||
let subscribing = {} | ||
let unsubscribing = {} | ||
@@ -204,7 +190,7 @@ this.emitter = createNanoEvents() | ||
let outFilter = async (action, meta) => { | ||
return !!meta.sync && meta.id.includes(` ${ this.options.userId }:`) | ||
return !!meta.sync && parseId(meta.id).userId === this.options.userId | ||
} | ||
let outMap = async (action, meta) => { | ||
let filtered = { } | ||
let filtered = {} | ||
for (let i in meta) { | ||
@@ -228,6 +214,20 @@ if (i === 'subprotocol') { | ||
token: this.options.token, | ||
ping: this.options.ping, | ||
auth | ||
ping: this.options.ping | ||
}) | ||
if (/^ws:\/\//.test(this.options.server) && !opts.allowDangerousProtocol) { | ||
let unbindEnvTest = this.node.on('state', () => { | ||
if (this.node.state === 'synchronized') { | ||
unbindEnvTest() | ||
if (this.node.remoteHeaders.env !== 'development') { | ||
console.error( | ||
'Without SSL, old proxies block WebSockets. ' + | ||
'Use WSS for Logux or set allowDangerousProtocol option.' | ||
) | ||
this.destroy() | ||
} | ||
} | ||
}) | ||
} | ||
this.node.on('debug', (type, stack) => { | ||
@@ -269,3 +269,7 @@ if (type === 'error') { | ||
on (event, listener) { | ||
return this.log.emitter.on(event, listener) | ||
if (event === 'user') { | ||
return this.emitter.on(event, listener) | ||
} else { | ||
return this.log.emitter.on(event, listener) | ||
} | ||
} | ||
@@ -284,3 +288,3 @@ | ||
let wasConnected = this.node.connected | ||
if (wasConnected) this.node.connection.disconnect('freeze') | ||
if (wasConnected) this.node.connection.disconnect('destroy') | ||
@@ -292,11 +296,7 @@ this.options.userId = userId | ||
let events = this.node.emitter.events | ||
this.node.connection.emitter.events = { } | ||
this.log.nodeId = this.nodeId | ||
this.node = new ClientNode(this.nodeId, this.log, this.node.connection, { | ||
...this.node.options, | ||
token: this.options.token | ||
}) | ||
this.node.emitter.events = events | ||
this.node.localNodeId = this.nodeId | ||
this.node.options.token = token | ||
this.emitter.emit('user', userId) | ||
if (wasConnected) this.node.connection.connect() | ||
@@ -303,0 +303,0 @@ } |
function block (e) { | ||
if (typeof e === 'undefined') e = window.event | ||
if (e) e.returnValue = 'unsynced' | ||
e.returnValue = 'unsynced' | ||
return 'unsynced' | ||
@@ -33,13 +32,15 @@ } | ||
unbind.push(client.on('add', (action, meta) => { | ||
if (action.type === 'logux/subscribe') { | ||
return | ||
} else if (action.type === 'logux/unsubscribe') { | ||
return | ||
} | ||
if (disconnected && meta.sync && meta.added) { | ||
wait = true | ||
update() | ||
} | ||
})) | ||
unbind.push( | ||
client.on('add', (action, meta) => { | ||
if (action.type === 'logux/subscribe') { | ||
return | ||
} else if (action.type === 'logux/unsubscribe') { | ||
return | ||
} | ||
if (disconnected && meta.sync && meta.added) { | ||
wait = true | ||
update() | ||
} | ||
}) | ||
) | ||
@@ -46,0 +47,0 @@ return () => { |
import { Unsubscribe } from 'nanoevents' | ||
import { Log } from '@logux/core' | ||
import { Client, ClientActionListener } from '../client' | ||
import { Client, ClientActionListener, ClientMeta } from '../client' | ||
@@ -26,4 +27,6 @@ /** | ||
*/ | ||
export class CrossTabClient extends Client { | ||
export class CrossTabClient< | ||
H extends object = {}, | ||
L extends Log = Log<ClientMeta> | ||
> extends Client<H, L> { | ||
/** | ||
@@ -69,2 +72,3 @@ * Current tab role. Only `leader` tab connects to server. `followers` just | ||
* * `state`: leader tab synchronization state has been changed. | ||
* * `user`: user ID was changed. | ||
* | ||
@@ -81,8 +85,17 @@ * ```js | ||
*/ | ||
on (event: 'role' | 'state', listener: () => void): Unsubscribe | ||
on (event: 'user', listener: (userId: string) => void): Unsubscribe | ||
on ( | ||
event: 'role' | 'state', listener: () => void | ||
event: 'preadd' | 'add' | 'clean', | ||
listener: ClientActionListener | ||
): Unsubscribe | ||
on ( | ||
event: 'preadd' | 'add' | 'clean', listener: ClientActionListener | ||
): Unsubscribe | ||
/** | ||
* Wait for specific state of the leader tab. | ||
* | ||
* @param state State name | ||
*/ | ||
waitFor ( | ||
state: 'disconnected' | 'connecting' | 'sending' | 'synchronized' | ||
): Promise<void> | ||
} |
@@ -137,7 +137,7 @@ import { LoguxError } from '@logux/core/logux-error' | ||
function isMemory (store) { | ||
return store.created && store.added | ||
return Array.isArray(store.entries) && Array.isArray(store.added) | ||
} | ||
class CrossTabClient extends Client { | ||
constructor (opts = { }) { | ||
constructor (opts = {}) { | ||
super(opts) | ||
@@ -215,2 +215,7 @@ | ||
changeUser (userId, token) { | ||
sendToTabs(this, 'user', [this.tabId, userId]) | ||
super.changeUser(userId, token) | ||
} | ||
on (event, listener) { | ||
@@ -224,2 +229,16 @@ if (event === 'preadd') { | ||
waitFor (state) { | ||
if (this.state === state) { | ||
return Promise.resolve() | ||
} | ||
return new Promise(resolve => { | ||
let unbind = this.on('state', () => { | ||
if (this.state === state) { | ||
unbind() | ||
resolve() | ||
} | ||
}) | ||
}) | ||
} | ||
onStorage (e) { | ||
@@ -235,6 +254,10 @@ if (e.newValue === null) return | ||
if (areWeOutdates(this, meta)) { | ||
let err = new LoguxError('wrong-subprotocol', { | ||
supported: meta.subprotocol, | ||
used: this.node.options.subprotocol | ||
}, true) | ||
let err = new LoguxError( | ||
'wrong-subprotocol', | ||
{ | ||
supported: meta.subprotocol, | ||
used: this.node.options.subprotocol | ||
}, | ||
true | ||
) | ||
this.node.emitter.emit('error', err) | ||
@@ -266,2 +289,7 @@ } | ||
} | ||
} else if (e.key === storageKey(this, 'user')) { | ||
data = JSON.parse(e.newValue) | ||
if (data[0] !== this.tabId) { | ||
this.emitter.emit('user', data[1]) | ||
} | ||
} | ||
@@ -275,3 +303,2 @@ } | ||
} | ||
super.onUnload() | ||
@@ -278,0 +305,0 @@ } |
@@ -15,4 +15,6 @@ function favicon (client, links) { | ||
} else if ( | ||
!client.connected && offline && | ||
prevFav !== offline && prevFav !== error | ||
!client.connected && | ||
offline && | ||
prevFav !== offline && | ||
prevFav !== error | ||
) { | ||
@@ -46,9 +48,13 @@ fav.href = prevFav = offline | ||
unbind.push(client.on('add', action => { | ||
if (action.type === 'logux/undo' && action.reason) setError() | ||
})) | ||
unbind.push( | ||
client.on('add', action => { | ||
if (action.type === 'logux/undo' && action.reason) setError() | ||
}) | ||
) | ||
unbind.push(client.node.on('error', err => { | ||
if (err.type !== 'timeout') setError() | ||
})) | ||
unbind.push( | ||
client.node.on('error', err => { | ||
if (err.type !== 'timeout') setError() | ||
}) | ||
) | ||
} | ||
@@ -55,0 +61,0 @@ |
@@ -1,2 +0,2 @@ | ||
import { Store } from '@logux/core' | ||
import { LogStore } from '@logux/core' | ||
@@ -22,7 +22,12 @@ /** | ||
*/ | ||
export class IndexedStore extends Store { | ||
export class IndexedStore extends LogStore { | ||
/** | ||
* @param name Database name to run multiple Logux instances on same web page. | ||
*/ | ||
constructor (name: string) | ||
constructor (name?: string) | ||
/** | ||
* Database name. | ||
*/ | ||
name: string | ||
} |
@@ -44,3 +44,3 @@ import { isFirstOlder } from '@logux/core' | ||
this.name = name | ||
this.adding = { } | ||
this.adding = {} | ||
} | ||
@@ -96,3 +96,8 @@ | ||
let store = await this.init() | ||
let result = await promisify(store.os('log').index('id').get(id)) | ||
let result = await promisify( | ||
store | ||
.os('log') | ||
.index('id') | ||
.get(id) | ||
) | ||
if (result) { | ||
@@ -99,0 +104,0 @@ return [result.action, result.meta] |
@@ -10,7 +10,2 @@ import { Client } from '../client' | ||
/** | ||
* Disable colors in logs. | ||
*/ | ||
color?: boolean | ||
/** | ||
* Disable connection state messages. | ||
@@ -39,2 +34,7 @@ */ | ||
clean?: boolean | ||
/** | ||
* Disable user ID changing. | ||
*/ | ||
user?: boolean | ||
} | ||
@@ -41,0 +41,0 @@ |
243
log/index.js
@@ -1,46 +0,38 @@ | ||
import browserSupportsLogStyles from 'browser-supports-log-styles' | ||
import { parseId } from '@logux/core/parse-id' | ||
function style (string) { | ||
function bold (string) { | ||
return '%c' + string + '%c' | ||
} | ||
function colorify (color, text, action, meta) { | ||
function showLog (text, details) { | ||
text = '%cLogux%c ' + text | ||
if (!color) text = text.replace(/%c/g, '') | ||
let args = Array.from(text.match(/%c/g)).map((_, i) => { | ||
if (i === 0) { | ||
return 'color:#ffa200;font-weight:bold' | ||
} else if (i % 2 === 0) { | ||
return 'font-weight:bold' | ||
} else { | ||
return 'font-weight:normal' | ||
} | ||
}) | ||
let args = [text] | ||
if (color) { | ||
let styles = text.match(/%c[^%]+%c/g) | ||
for (let i = 0; i < styles.length; i++) { | ||
if (i === 0) { | ||
args.push('color:#ffa200;font-weight:bold') | ||
if (details) { | ||
console.groupCollapsed(text, ...args) | ||
for (let name in details) { | ||
if (typeof details[name] === 'string') { | ||
console.log(name + ': %c' + details[name], 'font-weight:bold') | ||
} else { | ||
args.push('font-weight:bold') | ||
console.log(name, details[name]) | ||
} | ||
args.push('') | ||
} | ||
console.groupEnd() | ||
} else { | ||
console.log(text, ...args) | ||
} | ||
if (action) args.push(action) | ||
if (meta) args.push(meta) | ||
return args | ||
} | ||
function log (client, messages = { }) { | ||
function log (client, messages = {}) { | ||
let node = client.node | ||
let color = messages.color !== false && browserSupportsLogStyles() | ||
let showLog = (text, action, meta) => { | ||
console.log.apply(console, colorify(color, text, action, meta)) | ||
} | ||
let showError = error => { | ||
let text = 'error: ' + error.description | ||
if (error.received) text = 'server sent ' + text | ||
console.error.apply(console, colorify(color, text)) | ||
} | ||
let sent = {} | ||
let unbind = [] | ||
@@ -50,100 +42,123 @@ let prevConnected = false | ||
if (messages.state !== false) { | ||
unbind.push(client.on('state', () => { | ||
let postfix = '' | ||
if (client.state === 'connecting' && node.connection.url) { | ||
postfix = '. ' + style(node.localNodeId) + ' is connecting to ' + | ||
style(node.connection.url) + '.' | ||
} else if (client.connected && !prevConnected && node.remoteNodeId) { | ||
postfix = '. Client was connected to ' + style(node.remoteNodeId) + '.' | ||
prevConnected = true | ||
} else if (!client.connected) { | ||
prevConnected = false | ||
} | ||
showLog('state is ' + style(client.state) + postfix) | ||
})) | ||
unbind.push( | ||
client.on('state', () => { | ||
let details | ||
if (client.state === 'connecting' && node.connection.url) { | ||
details = { | ||
'Node ID': node.localNodeId, | ||
'Server': node.connection.url | ||
} | ||
} else if (client.connected && !prevConnected && node.remoteNodeId) { | ||
prevConnected = true | ||
details = { | ||
'Server ID': node.remoteNodeId | ||
} | ||
} else if (!client.connected) { | ||
prevConnected = false | ||
} | ||
showLog('state is ' + bold(client.state), details) | ||
}) | ||
) | ||
} | ||
if (messages.role !== false) { | ||
unbind.push(client.on('role', () => { | ||
showLog('tab role is ' + style(client.role)) | ||
})) | ||
unbind.push( | ||
client.on('role', () => { | ||
showLog('tab role is ' + bold(client.role)) | ||
}) | ||
) | ||
} | ||
if (messages.error !== false) { | ||
unbind.push(node.on('error', error => { | ||
showError(error) | ||
})) | ||
unbind.push(node.on('clientError', error => { | ||
showError(error) | ||
})) | ||
} | ||
let cleaned = { } | ||
let cleaned = {} | ||
let ignore = (messages.ignoreActions || []).reduce((all, i) => { | ||
all[i] = true | ||
return all | ||
}, { }) | ||
}, {}) | ||
if (messages.add !== false) { | ||
unbind.push(client.on('add', (action, meta) => { | ||
if (meta.tab && meta.tab !== client.tabId) return | ||
if (ignore[action.type]) return | ||
let message | ||
if (action.type === 'logux/subscribe') { | ||
message = 'subscribed to channel ' + style(action.channel) | ||
if (Object.keys(action).length === 2) { | ||
showLog(message) | ||
unbind.push( | ||
client.on('add', (action, meta) => { | ||
if (meta.tab && meta.tab !== client.tabId) return | ||
if (ignore[action.type]) return | ||
if (meta.sync) sent[meta.id] = action | ||
let message | ||
if (action.type === 'logux/subscribe') { | ||
message = 'subscribed to channel ' + bold(action.channel) | ||
if (Object.keys(action).length === 2) { | ||
showLog(message) | ||
} else { | ||
showLog(message, { Action: action }) | ||
} | ||
} else if (action.type === 'logux/unsubscribe') { | ||
message = 'unsubscribed from channel ' + bold(action.channel) | ||
if (Object.keys(action).length === 2) { | ||
showLog(message) | ||
} else { | ||
showLog(message, { Action: action }) | ||
} | ||
} else if (action.type === 'logux/processed') { | ||
if (sent[action.id]) { | ||
showLog('action ' + bold(sent[action.id].type) + ' was processed', { | ||
Action: sent[action.id] | ||
}) | ||
delete sent[action.id] | ||
} else { | ||
showLog('action ' + bold(action.id) + ' was processed') | ||
} | ||
} else if (action.type === 'logux/undo') { | ||
message = | ||
'action ' + | ||
bold(action.id) + | ||
' was undid because of ' + | ||
bold(action.reason) | ||
let details = {} | ||
if (sent[action.id]) { | ||
details.Action = sent[action.id] | ||
delete sent[action.id] | ||
} | ||
if (Object.keys(action).length > 3) { | ||
details.Undo = action | ||
} | ||
showLog(message, details) | ||
} else { | ||
showLog(message, action) | ||
let details = { Action: action, Meta: meta } | ||
message = 'added ' | ||
if (meta.reasons.length === 0) { | ||
cleaned[meta.id] = true | ||
message += 'and cleaned ' | ||
} | ||
message += bold(action.type) + ' action' | ||
let { nodeId } = parseId(meta.id) | ||
if (nodeId !== node.localNodeId) { | ||
details.From = nodeId | ||
} | ||
showLog(message, details) | ||
} | ||
} else if (action.type === 'logux/unsubscribe') { | ||
message = 'unsubscribed from channel ' + style(action.channel) | ||
if (Object.keys(action).length === 2) { | ||
showLog(message) | ||
} else { | ||
showLog(message, action) | ||
} | ||
} else if (action.type === 'logux/processed') { | ||
showLog('action ' + style(action.id) + ' was processed') | ||
} else if (action.type === 'logux/undo') { | ||
message = 'action ' + style(action.id) + ' was undid because of ' + | ||
style(action.reason) | ||
if (Object.keys(action).length === 3) { | ||
showLog(message) | ||
} else { | ||
showLog(message, action) | ||
} | ||
} else { | ||
message = 'added ' | ||
if (meta.reasons.length === 0) { | ||
cleaned[meta.id] = true | ||
message += 'and cleaned ' | ||
} | ||
message += style(action.type) + ' action' | ||
let nodeId = meta.id.split(' ')[1] | ||
if (nodeId !== node.localNodeId) { | ||
message += ' from ' + style(nodeId) | ||
} | ||
showLog(message, action, meta) | ||
} | ||
})) | ||
}) | ||
) | ||
} | ||
if (messages.user !== false) { | ||
unbind.push( | ||
client.on('user', userId => { | ||
let message = 'user ID was changed to ' + bold(userId) | ||
showLog(message, { 'Node ID': client.nodeId }) | ||
}) | ||
) | ||
} | ||
if (messages.clean !== false) { | ||
unbind.push(client.on('clean', (action, meta) => { | ||
if (cleaned[meta.id]) { | ||
delete cleaned[meta.id] | ||
return | ||
} | ||
if (meta.tab && meta.tab !== client.id) return | ||
if (ignore[action.type]) return | ||
if (action.type === 'logux/subscribe') return | ||
if (action.type === 'logux/unsubscribe') return | ||
if (action.type === 'logux/processed') return | ||
if (action.type === 'logux/undo') return | ||
let message = 'cleaned ' + style(action.type) + ' action' | ||
showLog(message, action, meta) | ||
})) | ||
unbind.push( | ||
client.on('clean', (action, meta) => { | ||
if (cleaned[meta.id]) { | ||
delete cleaned[meta.id] | ||
return | ||
} | ||
if (meta.tab && meta.tab !== client.id) return | ||
if (ignore[action.type]) return | ||
if (action.type.startsWith('logux/')) return | ||
let message = 'cleaned ' + bold(action.type) + ' action' | ||
showLog(message, { Action: action, Meta: meta }) | ||
}) | ||
) | ||
} | ||
@@ -150,0 +165,0 @@ |
{ | ||
"name": "@logux/client", | ||
"version": "0.8.5", | ||
"version": "0.9.0", | ||
"description": "Logux base components to build web client", | ||
@@ -21,14 +21,6 @@ "keywords": [ | ||
"dependencies": { | ||
"@logux/core": "^0.5.3", | ||
"browser-supports-log-styles": "^1.1.7", | ||
"nanoevents": "^5.1.5", | ||
"nanoid": "^3.1.3" | ||
"@logux/core": "^0.6.0", | ||
"nanoevents": "^5.1.8", | ||
"nanoid": "^3.1.10" | ||
}, | ||
"size-limit2": [ | ||
{ | ||
"path": "./client/index.js", | ||
"import": "{ Client }", | ||
"limit": "9 KB" | ||
} | ||
], | ||
"type": "module", | ||
@@ -35,0 +27,0 @@ "main": "index.cjs", |
@@ -10,3 +10,8 @@ # Logux Client [![Cult Of Martians][cult-img]][cult] | ||
**Documentation: [logux.io]** | ||
* **[Guide, recipes, and API](https://logux.io/)** | ||
* **[Chat](https://gitter.im/logux/logux)** for any questions | ||
* **[Issues](https://github.com/logux/logux/issues)** | ||
and **[roadmap](https://github.com/logux/logux/projects/1)** | ||
* **[Projects](https://logux.io/guide/architecture/parts/)** | ||
inside Logux ecosystem | ||
@@ -13,0 +18,0 @@ This repository contains Logux base components to build web client: |
@@ -7,6 +7,20 @@ import { Action } from '@logux/core' | ||
( | ||
current: 'synchronized' | 'synchronizedAfterWait' | 'disconnected' | | ||
'connecting' | 'connectingAfterWait' | 'protocolError' | | ||
'syncError' | 'error' | 'denied' | 'wait', | ||
details: undefined | Error | { action: Action, meta: ClientMeta } | ||
current: | ||
| 'synchronized' | ||
| 'synchronizedAfterWait' | ||
| 'disconnected' | ||
| 'connecting' | ||
| 'connectingAfterWait' | ||
| 'protocolError' | ||
| 'syncError' | ||
| 'error' | ||
| 'denied' | ||
| 'wait', | ||
details: | ||
| undefined | ||
| Error | ||
| { | ||
action: Action | ||
meta: ClientMeta | ||
} | ||
): void | ||
@@ -38,31 +52,5 @@ } | ||
export function status ( | ||
client: Client, callback: StatusListener, options?: StatusOptions | ||
client: Client, | ||
callback: StatusListener, | ||
options?: StatusOptions | ||
): () => void | ||
/** | ||
* Low-level function to show Logux synchronization status with your custom UI. | ||
* It is used in {@link badge} widget. | ||
* | ||
* @param {Client} client Observed Client instance. | ||
* @param {statusReceiver} callback Status callback. | ||
* @param {object} [options] Options. | ||
* @param {number} [options.duration=3000] `synchronizedAfterWait` duration. | ||
* | ||
* @return {function} Unbind status listener. | ||
* | ||
* @example | ||
* import status from '@logux/client/status' | ||
* status(client, current => { | ||
* updateUI(current) | ||
* }) | ||
*/ | ||
/** | ||
* @callback statusReceiver | ||
* @param { | ||
* "synchronized"|"synchronizedAfterWait"|"disconnected"|"wait"|"error"| | ||
* "connecting"|"connectingAfterWait"|"syncError"|"denied"|"protocolError" | ||
* } type Status type. | ||
* @param {object|undefined} details Status details. | ||
*/ |
@@ -1,2 +0,2 @@ | ||
function status (client, callback, options = { }) { | ||
function status (client, callback, options = {}) { | ||
let observable = client.on ? client : client.node | ||
@@ -11,5 +11,5 @@ let disconnected = observable.state === 'disconnected' | ||
let unbind = [] | ||
let processing = { } | ||
let processing = {} | ||
let synchronized = () => { | ||
function setSynchronized () { | ||
if (Object.keys(processing).length === 0) { | ||
@@ -28,3 +28,3 @@ if (wait) { | ||
unbind.push(observable.on('state', () => { | ||
function changeState () { | ||
clearTimeout(timeout) | ||
@@ -38,3 +38,3 @@ | ||
disconnected = false | ||
synchronized() | ||
setSynchronized() | ||
} else if (observable.state === 'connecting') { | ||
@@ -47,46 +47,59 @@ timeout = setTimeout(() => { | ||
} | ||
})) | ||
} | ||
unbind.push(client.node.on('error', error => { | ||
if (error.type === 'wrong-protocol' || error.type === 'wrong-subprotocol') { | ||
old = true | ||
callback('protocolError') | ||
} else if (error.type !== 'timeout') { | ||
unbind.push(observable.on('state', changeState)) | ||
unbind.push( | ||
client.node.on('error', error => { | ||
if ( | ||
error.type === 'wrong-protocol' || | ||
error.type === 'wrong-subprotocol' | ||
) { | ||
old = true | ||
callback('protocolError') | ||
} else if (error.type !== 'timeout') { | ||
callback('syncError', { error }) | ||
} | ||
}) | ||
) | ||
unbind.push( | ||
client.node.on('clientError', error => { | ||
callback('syncError', { error }) | ||
} | ||
})) | ||
}) | ||
) | ||
unbind.push(client.node.on('clientError', error => { | ||
callback('syncError', { error }) | ||
})) | ||
let log = client.on ? client : client.log | ||
unbind.push(log.on('add', (action, meta) => { | ||
if (action.type === 'logux/subscribe') { | ||
return | ||
} else if (action.type === 'logux/unsubscribe') { | ||
return | ||
} | ||
unbind.push( | ||
log.on('add', (action, meta) => { | ||
if (action.type === 'logux/subscribe') { | ||
return | ||
} else if (action.type === 'logux/unsubscribe') { | ||
return | ||
} | ||
if (action.type === 'logux/processed') { | ||
delete processing[action.id] | ||
synchronized() | ||
} else if (action.type === 'logux/undo') { | ||
delete processing[action.id] | ||
} else if (meta.sync) { | ||
processing[meta.id] = true | ||
} | ||
if (action.type === 'logux/processed') { | ||
delete processing[action.id] | ||
setSynchronized() | ||
} else if (action.type === 'logux/undo') { | ||
delete processing[action.id] | ||
} else if (meta.sync) { | ||
processing[meta.id] = true | ||
} | ||
if (action.type === 'logux/undo' && action.reason) { | ||
if (action.reason === 'denied') { | ||
callback('denied', { action, meta }) | ||
} else { | ||
callback('error', { action, meta }) | ||
if (action.type === 'logux/undo' && action.reason) { | ||
if (action.reason === 'denied') { | ||
callback('denied', { action, meta }) | ||
} else { | ||
callback('error', { action, meta }) | ||
} | ||
} else if (disconnected && meta.sync && meta.added) { | ||
if (!wait) callback('wait') | ||
wait = true | ||
} | ||
} else if (disconnected && meta.sync && meta.added) { | ||
if (!wait) callback('wait') | ||
wait = true | ||
} | ||
})) | ||
}) | ||
) | ||
changeState() | ||
return () => { | ||
@@ -93,0 +106,0 @@ for (let i of unbind) i() |
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
3
71
109938
51
3400
+ Added@logux/core@0.6.2(transitive)
- Removedbrowser-supports-log-styles@^1.1.7
- Removed@logux/core@0.5.3(transitive)
- Removedbrowser-supports-log-styles@1.1.7(transitive)
Updated@logux/core@^0.6.0
Updatednanoevents@^5.1.8
Updatednanoid@^3.1.10