UNPKG

wechaty-puppet

Version:

Abstract Puppet for Wechaty

139 lines 5.9 kB
import { timeoutPromise, } from 'gerror'; import { log } from '../config.js'; import { DirtyType } from '../schemas/mod.js'; import { CacheAgent } from '../agents/mod.js'; /** * * Huan(202111) Issue #158 - Refactoring the 'dirty' event, dirtyPayload(), * and XXXPayloadDirty() methods logic & spec * * @see https://github.com/wechaty/puppet/issues/158 * */ const cacheMixin = (mixinBase) => { class CacheMixin extends mixinBase { cache; __cacheMixinCleanCallbackList; constructor(...args) { super(...args); log.verbose('PuppetCacheMixin', 'constructor(%s)', args[0]?.cache ? '{ cache: ' + JSON.stringify(args[0].cache) + ' }' : ''); const options = args[0] || {}; this.__cacheMixinCleanCallbackList = []; this.cache = new CacheAgent(options.cache); } async start() { log.verbose('PuppetCacheMixin', 'start()'); await super.start(); this.cache.start(); const onDirty = this.onDirty.bind(this); this.on('dirty', onDirty); log.verbose('PuppetCacheMixin', 'start() "dirty" event listener added'); const cleanFn = () => { this.off('dirty', onDirty); log.verbose('PuppetCacheMixin', 'start() "dirty" event listener removed'); }; this.__cacheMixinCleanCallbackList.push(cleanFn); } async stop() { log.verbose('PuppetCacheMixin', 'stop()'); this.cache.stop(); this.__cacheMixinCleanCallbackList.map(setImmediate); this.__cacheMixinCleanCallbackList.length = 0; await super.stop(); } /** * * @windmemory(202008): add dirty payload methods * * @see https://github.com/wechaty/grpc/pull/79 * * Call this method when you want to notify the server that the data cache need to be invalidated. */ dirtyPayload(type, id) { log.verbose('PuppetCacheMixin', 'dirtyPayload(%s<%s>, %s)', DirtyType[type], type, id); /** * Huan(202111): we return first before emit the `dirty` event? */ setImmediate(() => this.emit('dirty', { payloadId: id, payloadType: type, })); } /** * OnDirty will be registered as a `dirty` event listener, * and it will invalidate the cache. */ onDirty({ payloadType, payloadId, }) { log.verbose('PuppetCacheMixin', 'onDirty(%s<%s>, %s)', DirtyType[payloadType], payloadType, payloadId); const dirtyFuncMap = { [DirtyType.Contact]: (id) => this.cache.contact.delete(id), [DirtyType.Friendship]: (id) => this.cache.friendship.delete(id), [DirtyType.Message]: (id) => this.cache.message.delete(id), [DirtyType.Post]: (id) => this.cache.post.delete(id), [DirtyType.Room]: (id) => this.cache.room.delete(id), [DirtyType.RoomMember]: (id) => this.cache.roomMember.delete(id), [DirtyType.Unspecified]: (id) => { throw new Error('Unspecified type with id: ' + id); }, }; const dirtyFunc = dirtyFuncMap[payloadType]; dirtyFunc(payloadId); } /** * When we are using PuppetService, the `dirty` event will be emitted from the server, * and we need to wait for the `dirty` event so we can make sure the cache has been invalidated. */ async __dirtyPayloadAwait(type, id) { log.verbose('PuppetCacheMixin', '__dirtyPayloadAwait(%s<%s>, %s)', DirtyType[type], type, id); if (!this.__currentUserId) { log.verbose('PuppetCacheMixin', '__dirtyPayloadAwait() will not dirty any payload when the puppet is not logged in'); return; } const isCurrentDirtyEvent = (event) => event.payloadId === id && event.payloadType === type; const onDirtyResolve = (resolve) => { const onDirty = (event) => { if (isCurrentDirtyEvent(event)) { resolve(); } }; return onDirty; }; let onDirty; const future = new Promise(resolve => { onDirty = onDirtyResolve(resolve); this.on('dirty', onDirty); }); /** * 1. call for sending the `dirty` event */ this.dirtyPayload(type, id); /** * 2. wait for the `dirty` event arrive, with a 5 seconds timeout */ try { await timeoutPromise(future, 5 * 1000) .finally(() => this.off('dirty', onDirty)); } catch (e) { // timeout, log warning & ignore it log.warn('PuppetCacheMixin', [ '__dirtyPayloadAwait() timeout.', 'The `dirty` event should be received but no one found.', 'Learn more from https://github.com/wechaty/puppet/issues/158', 'payloadType: %s(%s)', 'payloadId: %s', 'error: %s', 'stack: %s', ].join('\n '), DirtyType[type], type, id, e.message, e.stack); } /** * Huan(202111): wait for all the taks in the event loop queue to be executed * before we return, because there might be other `onDirty` listeners */ await new Promise(setImmediate); } } return CacheMixin; }; export { cacheMixin }; //# sourceMappingURL=cache-mixin.js.map