@juzi/wechaty-puppet-service 1.0.122 → 1.0.124
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/src/client/grpc-manager.d.ts +1 -0
- package/dist/cjs/src/client/grpc-manager.d.ts.map +1 -1
- package/dist/cjs/src/client/grpc-manager.js +49 -47
- package/dist/cjs/src/client/grpc-manager.js.map +1 -1
- package/dist/cjs/src/client/payload-store.d.ts +3 -0
- package/dist/cjs/src/client/payload-store.d.ts.map +1 -1
- package/dist/cjs/src/client/payload-store.js +8 -6
- package/dist/cjs/src/client/payload-store.js.map +1 -1
- package/dist/cjs/src/client/puppet-service.d.ts +14 -0
- package/dist/cjs/src/client/puppet-service.d.ts.map +1 -1
- package/dist/cjs/src/client/puppet-service.js +284 -219
- package/dist/cjs/src/client/puppet-service.js.map +1 -1
- package/dist/cjs/src/package-json.d.ts.map +1 -1
- package/dist/cjs/src/package-json.js +4 -3
- package/dist/cjs/src/package-json.js.map +1 -1
- package/dist/cjs/tests/fast-dirty-room-member.spec.js +123 -21
- package/dist/cjs/tests/fast-dirty-room-member.spec.js.map +1 -1
- package/dist/cjs/tests/grpc-stream-dirty-order.spec.d.ts +3 -0
- package/dist/cjs/tests/grpc-stream-dirty-order.spec.d.ts.map +1 -0
- package/dist/cjs/tests/grpc-stream-dirty-order.spec.js +135 -0
- package/dist/cjs/tests/grpc-stream-dirty-order.spec.js.map +1 -0
- package/dist/esm/src/client/grpc-manager.d.ts +1 -0
- package/dist/esm/src/client/grpc-manager.d.ts.map +1 -1
- package/dist/esm/src/client/grpc-manager.js +50 -48
- package/dist/esm/src/client/grpc-manager.js.map +1 -1
- package/dist/esm/src/client/payload-store.d.ts +3 -0
- package/dist/esm/src/client/payload-store.d.ts.map +1 -1
- package/dist/esm/src/client/payload-store.js +8 -6
- package/dist/esm/src/client/payload-store.js.map +1 -1
- package/dist/esm/src/client/puppet-service.d.ts +14 -0
- package/dist/esm/src/client/puppet-service.d.ts.map +1 -1
- package/dist/esm/src/client/puppet-service.js +284 -219
- package/dist/esm/src/client/puppet-service.js.map +1 -1
- package/dist/esm/src/package-json.d.ts.map +1 -1
- package/dist/esm/src/package-json.js +4 -3
- package/dist/esm/src/package-json.js.map +1 -1
- package/dist/esm/tests/fast-dirty-room-member.spec.js +123 -21
- package/dist/esm/tests/fast-dirty-room-member.spec.js.map +1 -1
- package/dist/esm/tests/grpc-stream-dirty-order.spec.d.ts +3 -0
- package/dist/esm/tests/grpc-stream-dirty-order.spec.d.ts.map +1 -0
- package/dist/esm/tests/grpc-stream-dirty-order.spec.js +110 -0
- package/dist/esm/tests/grpc-stream-dirty-order.spec.js.map +1 -0
- package/package.json +4 -3
- package/src/client/grpc-manager.ts +54 -48
- package/src/client/payload-store.ts +13 -7
- package/src/client/puppet-service.ts +283 -220
- package/src/package-json.ts +4 -3
|
@@ -28,20 +28,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
28
28
|
};
|
|
29
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
30
|
/**
|
|
31
|
-
* Regression
|
|
31
|
+
* Regression tests for the RoomMember persistent-store dirty semantics.
|
|
32
32
|
*
|
|
33
|
-
* The
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* (wechaty-puppet/src/mixins/cache-mixin.ts).
|
|
33
|
+
* The persistent `PayloadStore.roomMember` is a
|
|
34
|
+
* `FlashStore<roomId, {[memberId]: RoomMember}>` -- a per-room record of
|
|
35
|
+
* members. Dirty ids arrive in two shapes:
|
|
37
36
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* roomId
|
|
41
|
-
* across process restarts.
|
|
37
|
+
* 1. A compound `"<roomId><STRING_SPLITTER><memberId>"` -- a single
|
|
38
|
+
* member's payload went stale (e.g. one member renamed).
|
|
39
|
+
* 2. A bare `"<roomId>"` -- the whole room's member set went stale.
|
|
42
40
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
41
|
+
* The previous handler treated both shapes the same by calling
|
|
42
|
+
* `roomMember.delete(roomId)`, which threw away the entire room's
|
|
43
|
+
* cache even when only one member was dirty. That thrash forces the
|
|
44
|
+
* next N-1 members to re-fetch over gRPC.
|
|
45
|
+
*
|
|
46
|
+
* The new handler must:
|
|
47
|
+
* - On compound id: drop only the named member, keep the rest.
|
|
48
|
+
* - On compound id whose split leaves the record empty: delete the row.
|
|
49
|
+
* - On bare roomId: delete the entire row (as before).
|
|
45
50
|
*/
|
|
46
51
|
const tstest_1 = require("tstest");
|
|
47
52
|
const os_1 = __importDefault(require("os"));
|
|
@@ -49,29 +54,126 @@ const path_1 = __importDefault(require("path"));
|
|
|
49
54
|
const fs_1 = __importDefault(require("fs"));
|
|
50
55
|
const PUPPET = __importStar(require("@juzi/wechaty-puppet"));
|
|
51
56
|
const mod_js_1 = require("../src/mod.js");
|
|
52
|
-
|
|
57
|
+
const makePuppet = async () => {
|
|
53
58
|
const token = `puppet_service_test_${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
|
|
54
59
|
const puppet = new mod_js_1.PuppetService({ token });
|
|
55
60
|
const accountId = 'acct-test';
|
|
56
61
|
await puppet._payloadStore.start(accountId);
|
|
62
|
+
return { puppet, token };
|
|
63
|
+
};
|
|
64
|
+
const cleanup = async (puppet, token) => {
|
|
65
|
+
await puppet._payloadStore.stop();
|
|
66
|
+
try {
|
|
67
|
+
await fs_1.default.promises.rm(path_1.default.join(os_1.default.homedir(), '.wechaty', 'wechaty-puppet-service', token), { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
catch (_) { /* ignore */ }
|
|
70
|
+
};
|
|
71
|
+
(0, tstest_1.test)('fastDirty(RoomMember, "<roomId>\\u001F<memberId>") drops only the named member', async (t) => {
|
|
72
|
+
const { puppet, token } = await makePuppet();
|
|
57
73
|
const roomId = 'room-test-id';
|
|
58
74
|
await puppet._payloadStore.roomMember.set(roomId, {
|
|
59
75
|
'member-A': { id: 'member-A', name: 'A' },
|
|
60
76
|
'member-B': { id: 'member-B', name: 'B' },
|
|
61
77
|
});
|
|
62
|
-
const before = await puppet._payloadStore.roomMember.get(roomId);
|
|
63
|
-
t.ok(before, 'sanity: roomMember entry exists before dirty');
|
|
64
78
|
await puppet.fastDirty({
|
|
65
79
|
payloadType: PUPPET.types.Dirty.RoomMember,
|
|
66
80
|
payloadId: `${roomId}${PUPPET.STRING_SPLITTER}member-A`,
|
|
67
81
|
});
|
|
68
82
|
const after = await puppet._payloadStore.roomMember.get(roomId);
|
|
69
|
-
t.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
83
|
+
t.ok(after, 'room entry must survive when only one member is dirtied');
|
|
84
|
+
t.notOk(after && after['member-A'], 'dirtied member-A must be gone');
|
|
85
|
+
t.ok(after && after['member-B'], 'unrelated member-B must be preserved');
|
|
86
|
+
await cleanup(puppet, token);
|
|
87
|
+
});
|
|
88
|
+
(0, tstest_1.test)('fastDirty(RoomMember, "<roomId>\\u001F<lastMember>") deletes the row when empty', async (t) => {
|
|
89
|
+
const { puppet, token } = await makePuppet();
|
|
90
|
+
const roomId = 'room-test-id-lone';
|
|
91
|
+
await puppet._payloadStore.roomMember.set(roomId, {
|
|
92
|
+
'only-member': { id: 'only-member', name: 'Solo' },
|
|
93
|
+
});
|
|
94
|
+
await puppet.fastDirty({
|
|
95
|
+
payloadType: PUPPET.types.Dirty.RoomMember,
|
|
96
|
+
payloadId: `${roomId}${PUPPET.STRING_SPLITTER}only-member`,
|
|
97
|
+
});
|
|
98
|
+
const after = await puppet._payloadStore.roomMember.get(roomId);
|
|
99
|
+
t.notOk(after, 'row must be deleted once the record has no members left');
|
|
100
|
+
await cleanup(puppet, token);
|
|
101
|
+
});
|
|
102
|
+
(0, tstest_1.test)('fastDirty(RoomMember, "<roomId>") clears the whole room entry', async (t) => {
|
|
103
|
+
const { puppet, token } = await makePuppet();
|
|
104
|
+
const roomId = 'room-test-id-full';
|
|
105
|
+
await puppet._payloadStore.roomMember.set(roomId, {
|
|
106
|
+
'member-A': { id: 'member-A', name: 'A' },
|
|
107
|
+
'member-B': { id: 'member-B', name: 'B' },
|
|
108
|
+
});
|
|
109
|
+
await puppet.fastDirty({
|
|
110
|
+
payloadType: PUPPET.types.Dirty.RoomMember,
|
|
111
|
+
payloadId: roomId,
|
|
112
|
+
});
|
|
113
|
+
const after = await puppet._payloadStore.roomMember.get(roomId);
|
|
114
|
+
t.notOk(after, 'bare roomId dirty must clear the whole row');
|
|
115
|
+
await cleanup(puppet, token);
|
|
116
|
+
});
|
|
117
|
+
(0, tstest_1.test)('fastDirty(RoomMember) on unknown member is a no-op', async (t) => {
|
|
118
|
+
const { puppet, token } = await makePuppet();
|
|
119
|
+
const roomId = 'room-test-id-noop';
|
|
120
|
+
const before = {
|
|
121
|
+
'member-A': { id: 'member-A', name: 'A' },
|
|
122
|
+
'member-B': { id: 'member-B', name: 'B' },
|
|
123
|
+
};
|
|
124
|
+
await puppet._payloadStore.roomMember.set(roomId, before);
|
|
125
|
+
await puppet.fastDirty({
|
|
126
|
+
payloadType: PUPPET.types.Dirty.RoomMember,
|
|
127
|
+
payloadId: `${roomId}${PUPPET.STRING_SPLITTER}ghost-member`,
|
|
128
|
+
});
|
|
129
|
+
const after = await puppet._payloadStore.roomMember.get(roomId);
|
|
130
|
+
t.same(after, before, 'unrelated dirty must not disturb the record');
|
|
131
|
+
await cleanup(puppet, token);
|
|
132
|
+
});
|
|
133
|
+
(0, tstest_1.test)('fastDirty(RoomMember) concurrent compound dirties on same roomId drop every named member', async (t) => {
|
|
134
|
+
const { puppet, token } = await makePuppet();
|
|
135
|
+
const roomId = 'room-test-id-race';
|
|
136
|
+
await puppet._payloadStore.roomMember.set(roomId, {
|
|
137
|
+
'member-A': { id: 'member-A', name: 'A' },
|
|
138
|
+
'member-B': { id: 'member-B', name: 'B' },
|
|
139
|
+
});
|
|
140
|
+
// Fire both compound dirties without awaiting between them so their
|
|
141
|
+
// `get → mutate → set` cycles interleave. Without per-roomId
|
|
142
|
+
// serialization, both reads would see {A, B}, both writes would keep
|
|
143
|
+
// one sibling, and one of the two deletes would be lost.
|
|
144
|
+
await Promise.all([
|
|
145
|
+
puppet.fastDirty({
|
|
146
|
+
payloadType: PUPPET.types.Dirty.RoomMember,
|
|
147
|
+
payloadId: `${roomId}${PUPPET.STRING_SPLITTER}member-A`,
|
|
148
|
+
}),
|
|
149
|
+
puppet.fastDirty({
|
|
150
|
+
payloadType: PUPPET.types.Dirty.RoomMember,
|
|
151
|
+
payloadId: `${roomId}${PUPPET.STRING_SPLITTER}member-B`,
|
|
152
|
+
}),
|
|
153
|
+
]);
|
|
154
|
+
const after = await puppet._payloadStore.roomMember.get(roomId);
|
|
155
|
+
// Either the whole row is gone (last shrink deleted it) or the record
|
|
156
|
+
// is empty. Both are acceptable "no members left" observations.
|
|
157
|
+
const noMembersLeft = !after || Object.keys(after).length === 0;
|
|
158
|
+
t.ok(noMembersLeft, `both dirtied members must be evicted, got: ${JSON.stringify(after)}`);
|
|
159
|
+
await cleanup(puppet, token);
|
|
160
|
+
});
|
|
161
|
+
(0, tstest_1.test)('fastDirty(RoomMember) prototype-key member id must not match a real member', async (t) => {
|
|
162
|
+
const { puppet, token } = await makePuppet();
|
|
163
|
+
const roomId = 'room-test-id-proto';
|
|
164
|
+
const before = {
|
|
165
|
+
'member-A': { id: 'member-A', name: 'A' },
|
|
166
|
+
};
|
|
167
|
+
await puppet._payloadStore.roomMember.set(roomId, before);
|
|
168
|
+
// `toString` exists on Object.prototype; a naive `memberId in current`
|
|
169
|
+
// check would match it and take the mutate branch. The correct guard
|
|
170
|
+
// uses hasOwnProperty and treats this as an unknown member.
|
|
171
|
+
await puppet.fastDirty({
|
|
172
|
+
payloadType: PUPPET.types.Dirty.RoomMember,
|
|
173
|
+
payloadId: `${roomId}${PUPPET.STRING_SPLITTER}toString`,
|
|
174
|
+
});
|
|
175
|
+
const after = await puppet._payloadStore.roomMember.get(roomId);
|
|
176
|
+
t.same(after, before, 'prototype-key dirty must be a no-op');
|
|
177
|
+
await cleanup(puppet, token);
|
|
76
178
|
});
|
|
77
179
|
//# sourceMappingURL=fast-dirty-room-member.spec.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fast-dirty-room-member.spec.js","sourceRoot":"","sources":["../../../tests/fast-dirty-room-member.spec.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA
|
|
1
|
+
{"version":3,"file":"fast-dirty-room-member.spec.js","sourceRoot":"","sources":["../../../tests/fast-dirty-room-member.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grpc-stream-dirty-order.spec.d.ts","sourceRoot":"","sources":["../../../tests/grpc-stream-dirty-order.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --no-warnings --loader ts-node/esm
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
20
|
+
if (mod && mod.__esModule) return mod;
|
|
21
|
+
var result = {};
|
|
22
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
23
|
+
__setModuleDefault(result, mod);
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
/**
|
|
28
|
+
* Regression / hardening test for the EVENT_TYPE_DIRTY dispatch order.
|
|
29
|
+
*
|
|
30
|
+
* Client-side dirty handling touches two independent caches:
|
|
31
|
+
*
|
|
32
|
+
* - `fastDirty()` clears `_payloadStore` (FlashStore, on disk, async).
|
|
33
|
+
* - `emit('dirty')` triggers the puppet-side CacheMixin which clears
|
|
34
|
+
* the in-memory LRU synchronously in the same tick.
|
|
35
|
+
*
|
|
36
|
+
* If `emit('dirty')` runs before `fastDirty()` resolves, a listener that
|
|
37
|
+
* immediately reads back the payload -- as CacheMixin.onDirty does when
|
|
38
|
+
* a caller races with the dirty event -- can:
|
|
39
|
+
*
|
|
40
|
+
* 1. See an LRU miss (already cleared),
|
|
41
|
+
* 2. Refetch via `contactRawPayload` etc.,
|
|
42
|
+
* 3. Hit the still-un-cleared `_payloadStore` entry,
|
|
43
|
+
* 4. Repopulate the LRU with the stale row that fastDirty is about
|
|
44
|
+
* to delete a microtask later,
|
|
45
|
+
* 5. And the fresh fastDirty delete no-ops against a now-cold key.
|
|
46
|
+
*
|
|
47
|
+
* The current implementation `await`s fastDirty before emitting. This
|
|
48
|
+
* test pins that invariant: fastDirty must fully resolve before any
|
|
49
|
+
* `dirty` listener observes the event.
|
|
50
|
+
*/
|
|
51
|
+
const tstest_1 = require("tstest");
|
|
52
|
+
const PUPPET = __importStar(require("@juzi/wechaty-puppet"));
|
|
53
|
+
const wechaty_grpc_1 = require("@juzi/wechaty-grpc");
|
|
54
|
+
const mod_js_1 = require("../src/mod.js");
|
|
55
|
+
(0, tstest_1.test)('onGrpcStreamEvent awaits fastDirty before emitting dirty', async (t) => {
|
|
56
|
+
const token = `puppet_service_test_${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
|
|
57
|
+
const puppet = new mod_js_1.PuppetService({ token });
|
|
58
|
+
let fastDirtyResolvedAt;
|
|
59
|
+
let dirtyEmittedAt;
|
|
60
|
+
const FASTDIRTY_DELAY_MS = 50;
|
|
61
|
+
puppet.fastDirty = async (_payload) => {
|
|
62
|
+
await new Promise(resolve => setTimeout(resolve, FASTDIRTY_DELAY_MS));
|
|
63
|
+
fastDirtyResolvedAt = Date.now();
|
|
64
|
+
};
|
|
65
|
+
puppet.on('dirty', () => {
|
|
66
|
+
dirtyEmittedAt = Date.now();
|
|
67
|
+
});
|
|
68
|
+
const dirtyJson = JSON.stringify({
|
|
69
|
+
payloadType: PUPPET.types.Dirty.Contact,
|
|
70
|
+
payloadId: 'contact-x',
|
|
71
|
+
});
|
|
72
|
+
const fakeEvent = {
|
|
73
|
+
getType: () => wechaty_grpc_1.puppet.EventType.EVENT_TYPE_DIRTY,
|
|
74
|
+
getPayload: () => dirtyJson,
|
|
75
|
+
getSeq: () => 0,
|
|
76
|
+
};
|
|
77
|
+
await puppet.onGrpcStreamEvent(fakeEvent);
|
|
78
|
+
t.ok(fastDirtyResolvedAt, 'fastDirty must have been invoked');
|
|
79
|
+
t.ok(dirtyEmittedAt, 'dirty listener must have been invoked');
|
|
80
|
+
t.ok(fastDirtyResolvedAt <= dirtyEmittedAt, `dirty emit (${dirtyEmittedAt}) must not precede fastDirty resolve (${fastDirtyResolvedAt})`);
|
|
81
|
+
});
|
|
82
|
+
(0, tstest_1.test)('onGrpcStreamEvent: dirty emit sequence-number is strictly after every fastDirty resolution', async (t) => {
|
|
83
|
+
// Wall-clock ordering (the baseline test above) is fine as a smoke
|
|
84
|
+
// signal but can collapse to a tie when both boundaries fall inside
|
|
85
|
+
// the same millisecond. The invariant we actually care about is
|
|
86
|
+
// *causal*: each fastDirty must have fully resolved before the
|
|
87
|
+
// corresponding dirty emit fires. Record call ordinals from a shared
|
|
88
|
+
// monotonic counter to pin that -- ordinals cannot tie.
|
|
89
|
+
const token = `puppet_service_test_${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
|
|
90
|
+
const puppet = new mod_js_1.PuppetService({ token });
|
|
91
|
+
let sequence = 0;
|
|
92
|
+
const nextOrdinal = () => ++sequence;
|
|
93
|
+
// Order of interest per event: fastDirty enter -> fastDirty resolve -> dirty emit.
|
|
94
|
+
const fastDirtyEnterOrdinals = [];
|
|
95
|
+
const fastDirtyResolveOrdinals = [];
|
|
96
|
+
const dirtyEmitOrdinals = [];
|
|
97
|
+
const fastDirtyStub = tstest_1.sinon.stub().callsFake(async (_payload) => {
|
|
98
|
+
fastDirtyEnterOrdinals.push(nextOrdinal());
|
|
99
|
+
// Yield the microtask queue so a broken ordering would surface: if
|
|
100
|
+
// emit ran ahead of the await point below, its ordinal would slot
|
|
101
|
+
// between our enter and resolve records.
|
|
102
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
103
|
+
fastDirtyResolveOrdinals.push(nextOrdinal());
|
|
104
|
+
});
|
|
105
|
+
puppet.fastDirty = fastDirtyStub;
|
|
106
|
+
const dirtySpy = tstest_1.sinon.spy(() => {
|
|
107
|
+
dirtyEmitOrdinals.push(nextOrdinal());
|
|
108
|
+
});
|
|
109
|
+
puppet.on('dirty', dirtySpy);
|
|
110
|
+
const makeEvent = (payloadId) => {
|
|
111
|
+
const dirtyJson = JSON.stringify({
|
|
112
|
+
payloadType: PUPPET.types.Dirty.Contact,
|
|
113
|
+
payloadId,
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
getType: () => wechaty_grpc_1.puppet.EventType.EVENT_TYPE_DIRTY,
|
|
117
|
+
getPayload: () => dirtyJson,
|
|
118
|
+
getSeq: () => 0,
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
// Two back-to-back dirty events: this is the scenario Fix #6 pins,
|
|
122
|
+
// sequential await must hold across both.
|
|
123
|
+
await puppet.onGrpcStreamEvent(makeEvent('contact-1'));
|
|
124
|
+
await puppet.onGrpcStreamEvent(makeEvent('contact-2'));
|
|
125
|
+
t.equal(fastDirtyStub.callCount, 2, 'fastDirty invoked once per dirty event');
|
|
126
|
+
t.equal(dirtySpy.callCount, 2, 'dirty listener invoked once per dirty event');
|
|
127
|
+
t.equal(fastDirtyEnterOrdinals.length, 2, 'both fastDirty invocations recorded an entry ordinal');
|
|
128
|
+
t.equal(fastDirtyResolveOrdinals.length, 2, 'both fastDirty invocations recorded a resolve ordinal');
|
|
129
|
+
t.equal(dirtyEmitOrdinals.length, 2, 'both dirty emits recorded an ordinal');
|
|
130
|
+
for (let i = 0; i < 2; i++) {
|
|
131
|
+
t.ok(fastDirtyEnterOrdinals[i] < fastDirtyResolveOrdinals[i], `event #${i}: fastDirty must enter (ord=${fastDirtyEnterOrdinals[i]}) before it resolves (ord=${fastDirtyResolveOrdinals[i]})`);
|
|
132
|
+
t.ok(fastDirtyResolveOrdinals[i] < dirtyEmitOrdinals[i], `event #${i}: dirty emit ordinal (${dirtyEmitOrdinals[i]}) must strictly follow fastDirty resolve ordinal (${fastDirtyResolveOrdinals[i]})`);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
//# sourceMappingURL=grpc-stream-dirty-order.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grpc-stream-dirty-order.spec.js","sourceRoot":"","sources":["../../../tests/grpc-stream-dirty-order.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,6 +18,7 @@ declare class GrpcManager extends EventEmitter {
|
|
|
18
18
|
endpoint: string;
|
|
19
19
|
serverName: string;
|
|
20
20
|
token: WechatyToken;
|
|
21
|
+
private readonly _log;
|
|
21
22
|
constructor(options: PuppetServiceOptions);
|
|
22
23
|
start(lastEventSeq?: string, accountId?: string): Promise<void>;
|
|
23
24
|
stop(): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grpc-manager.d.ts","sourceRoot":"","sources":["../../../../src/client/grpc-manager.ts"],"names":[],"mappings":";;;AAoBA,OAAO,YAAY,MAAM,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"grpc-manager.d.ts","sourceRoot":"","sources":["../../../../src/client/grpc-manager.ts"],"names":[],"mappings":";;;AAoBA,OAAO,YAAY,MAAM,QAAQ,CAAA;AAOjC,OAAO,EACL,IAAI,EACJ,MAAM,EACP,MAA0B,oBAAoB,CAAA;AAI/C,OAAO,EACL,YAAY,EAEb,MAA0B,eAAe,CAAA;AAc1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAU/D,cAAM,WAAY,SAAQ,YAAY;IAwBvB,OAAO,CAAC,OAAO;IAtB5B,SAAS,CAAC,OAAO,CAAC,EAAI,MAAM,CAAC,YAAY,CAAA;IACzC,IAAU,MAAM,IAAM,MAAM,CAAC,YAAY,CAMxC;IAED,WAAW,CAAC,EAAG,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,aAAa,CAAC,CAAA;IAE9D;;OAEG;IACH,MAAM,EAAO,MAAM,CAAA;IACnB,UAAU,EAAG,OAAO,CAAA;IACpB,QAAQ,EAAK,MAAM,CAAA;IACnB,UAAU,EAAG,MAAM,CAAA;IACnB,KAAK,EAAQ,YAAY,CAAA;IAEzB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAY;gBAEZ,OAAO,EAAE,oBAAoB;IAmE5C,KAAK,CAAE,YAAY,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BhE,IAAI,IAAK,OAAO,CAAC,IAAI,CAAC;cAsCZ,UAAU,IAAK,OAAO,CAAC,IAAI,CAAC;IAqD5C,SAAS,CAAC,aAAa,IAAK,IAAI;IAsB1B,WAAW,CAAE,YAAY,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+H5E,UAAU,IAAK,IAAI;CA8BpB;AAED,OAAO,EACL,WAAW,GACZ,CAAA"}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import util from 'util';
|
|
21
21
|
import EventEmitter from 'events';
|
|
22
22
|
import crypto from 'crypto';
|
|
23
|
-
import { log } from '@juzi/wechaty-puppet';
|
|
23
|
+
import { log, } from '@juzi/wechaty-puppet';
|
|
24
24
|
import { grpc, puppet, } from '@juzi/wechaty-grpc';
|
|
25
25
|
import { timeoutPromise, } from 'gerror';
|
|
26
26
|
import { WechatyToken, WechatyResolver, } from 'wechaty-token';
|
|
@@ -54,19 +54,21 @@ class GrpcManager extends EventEmitter {
|
|
|
54
54
|
endpoint;
|
|
55
55
|
serverName;
|
|
56
56
|
token;
|
|
57
|
+
_log;
|
|
57
58
|
constructor(options) {
|
|
58
59
|
super();
|
|
59
60
|
this.options = options;
|
|
60
|
-
|
|
61
|
+
this._log = options.logger ?? log;
|
|
62
|
+
this._log.verbose('GrpcManager', 'constructor(%s)', JSON.stringify(options));
|
|
61
63
|
this.caCert = Buffer.from(envVars.WECHATY_PUPPET_SERVICE_TLS_CA_CERT(this.options.tls?.caCert) || TLS_CA_CERT);
|
|
62
|
-
|
|
64
|
+
this._log.verbose('GrpcManager', 'constructor() tlsRootCert(hash): "%s"', crypto.createHash('sha256')
|
|
63
65
|
.update(this.caCert)
|
|
64
66
|
.digest('hex'));
|
|
65
67
|
/**
|
|
66
68
|
* Token will be used in the gRPC resolver (in endpoint)
|
|
67
69
|
*/
|
|
68
70
|
this.token = new WechatyToken(envVars.WECHATY_PUPPET_SERVICE_TOKEN(this.options.token));
|
|
69
|
-
|
|
71
|
+
this._log.verbose('GrpcManager', 'constructor() token: "%s"', this.token);
|
|
70
72
|
this.endpoint = envVars.WECHATY_PUPPET_SERVICE_ENDPOINT(this.options.endpoint)
|
|
71
73
|
/**
|
|
72
74
|
* Wechaty Token Discovery-able URL
|
|
@@ -78,12 +80,12 @@ class GrpcManager extends EventEmitter {
|
|
|
78
80
|
'/',
|
|
79
81
|
this.token,
|
|
80
82
|
].join('');
|
|
81
|
-
|
|
83
|
+
this._log.verbose('GrpcManager', 'constructor() endpoint: "%s"', this.endpoint);
|
|
82
84
|
/**
|
|
83
85
|
* Disable TLS
|
|
84
86
|
*/
|
|
85
87
|
this.disableTls = envVars.WECHATY_PUPPET_SERVICE_NO_TLS_INSECURE_CLIENT(this.options.tls?.disable);
|
|
86
|
-
|
|
88
|
+
this._log.verbose('GrpcManager', 'constructor() disableTls: "%s"', this.disableTls);
|
|
87
89
|
/**
|
|
88
90
|
* for Node.js TLS SNI
|
|
89
91
|
* https://en.wikipedia.org/wiki/Server_Name_Indication
|
|
@@ -105,47 +107,47 @@ class GrpcManager extends EventEmitter {
|
|
|
105
107
|
].join('\n'));
|
|
106
108
|
}
|
|
107
109
|
this.serverName = serverNameIndication;
|
|
108
|
-
|
|
110
|
+
this._log.verbose('GrpcManager', 'constructor() serverName(SNI): "%s"', this.serverName);
|
|
109
111
|
}
|
|
110
112
|
async start(lastEventSeq, accountId) {
|
|
111
|
-
|
|
113
|
+
this._log.verbose('GrpcManager', 'start()');
|
|
112
114
|
/**
|
|
113
115
|
* 1. Init grpc client
|
|
114
116
|
*/
|
|
115
|
-
|
|
117
|
+
this._log.verbose('GrpcManager', 'start() initializing client ...');
|
|
116
118
|
await this.initClient();
|
|
117
|
-
|
|
119
|
+
this._log.verbose('GrpcManager', 'start() initializing client ... done');
|
|
118
120
|
/**
|
|
119
121
|
* 2. Connect to stream
|
|
120
122
|
*/
|
|
121
|
-
|
|
123
|
+
this._log.verbose('GrpcManager', 'start() starting stream ...');
|
|
122
124
|
await this.startStream(lastEventSeq, accountId);
|
|
123
|
-
|
|
125
|
+
this._log.verbose('GrpcManager', 'start() starting stream ... done');
|
|
124
126
|
/**
|
|
125
127
|
* 3. Start the puppet
|
|
126
128
|
*/
|
|
127
|
-
|
|
129
|
+
this._log.verbose('GrpcManager', 'start() calling grpc server: start() ...');
|
|
128
130
|
await util.promisify(this.client.start
|
|
129
131
|
.bind(this.client))(new puppet.StartRequest());
|
|
130
|
-
|
|
131
|
-
|
|
132
|
+
this._log.verbose('GrpcManager', 'start() calling grpc server: start() ... done');
|
|
133
|
+
this._log.verbose('GrpcManager', 'start() ... done');
|
|
132
134
|
}
|
|
133
135
|
async stop() {
|
|
134
|
-
|
|
136
|
+
this._log.verbose('GrpcManager', 'stop()');
|
|
135
137
|
/**
|
|
136
138
|
* 1. Disconnect from stream
|
|
137
139
|
*/
|
|
138
|
-
|
|
140
|
+
this._log.verbose('GrpcManager', 'stop() stop stream ...');
|
|
139
141
|
this.stopStream();
|
|
140
|
-
|
|
142
|
+
this._log.verbose('GrpcManager', 'stop() stop stream ... done');
|
|
141
143
|
/**
|
|
142
144
|
* 2. Stop the puppet
|
|
143
145
|
*/
|
|
144
146
|
try {
|
|
145
|
-
|
|
147
|
+
this._log.verbose('GrpcManager', 'stop() stop client ...');
|
|
146
148
|
await util.promisify(this.client.stop
|
|
147
149
|
.bind(this.client))(new puppet.StopRequest());
|
|
148
|
-
|
|
150
|
+
this._log.verbose('GrpcManager', 'stop() stop client ... done');
|
|
149
151
|
}
|
|
150
152
|
catch (e) {
|
|
151
153
|
this.emit('error', e);
|
|
@@ -154,17 +156,17 @@ class GrpcManager extends EventEmitter {
|
|
|
154
156
|
* 3. Destroy grpc client
|
|
155
157
|
*/
|
|
156
158
|
try {
|
|
157
|
-
|
|
159
|
+
this._log.verbose('GrpcManager', 'stop() destroy client ...');
|
|
158
160
|
this.destroyClient();
|
|
159
|
-
|
|
161
|
+
this._log.verbose('GrpcManager', 'stop() destroy client ... done');
|
|
160
162
|
}
|
|
161
163
|
catch (e) {
|
|
162
164
|
this.emit('error', e);
|
|
163
165
|
}
|
|
164
|
-
|
|
166
|
+
this._log.verbose('GrpcManager', 'stop() ... done');
|
|
165
167
|
}
|
|
166
168
|
async initClient() {
|
|
167
|
-
|
|
169
|
+
this._log.verbose('GrpcManager', 'initClient()');
|
|
168
170
|
/**
|
|
169
171
|
* Huan(202108): for maximum compatible with the non-tls community servers/clients,
|
|
170
172
|
* we introduced the WECHATY_PUPPET_SERVICE_NO_TLS_INSECURE_{SERVER,CLIENT} environment variables.
|
|
@@ -172,11 +174,11 @@ class GrpcManager extends EventEmitter {
|
|
|
172
174
|
*/
|
|
173
175
|
let credential;
|
|
174
176
|
if (this.disableTls) {
|
|
175
|
-
|
|
177
|
+
this._log.warn('GrpcManager', 'initClient() TLS: disabled (INSECURE)');
|
|
176
178
|
credential = grpc.credentials.createInsecure();
|
|
177
179
|
}
|
|
178
180
|
else {
|
|
179
|
-
|
|
181
|
+
this._log.verbose('GrpcManager', 'initClient() TLS: enabled');
|
|
180
182
|
const callCred = callCredToken(this.token.token);
|
|
181
183
|
const channelCred = grpc.credentials.createSsl(this.caCert);
|
|
182
184
|
const combCreds = grpc.credentials.combineChannelCredentials(channelCred, callCred);
|
|
@@ -198,16 +200,16 @@ class GrpcManager extends EventEmitter {
|
|
|
198
200
|
clientOptions['grpc.default_authority'] = grpcDefaultAuthority;
|
|
199
201
|
}
|
|
200
202
|
if (this._client) {
|
|
201
|
-
|
|
203
|
+
this._log.warn('GrpcManager', 'initClient() this.#client exists? Old client has been dropped.');
|
|
202
204
|
this._client = undefined;
|
|
203
205
|
}
|
|
204
206
|
this._client = new puppet.PuppetClient(this.endpoint, credential, clientOptions);
|
|
205
|
-
|
|
207
|
+
this._log.verbose('GrpcManager', 'initClient() ... done');
|
|
206
208
|
}
|
|
207
209
|
destroyClient() {
|
|
208
|
-
|
|
210
|
+
this._log.verbose('GrpcManager', 'destroyClient()');
|
|
209
211
|
if (!this._client) {
|
|
210
|
-
|
|
212
|
+
this._log.warn('GrpcManager', 'destroyClient() this.#client not exist');
|
|
211
213
|
return;
|
|
212
214
|
}
|
|
213
215
|
const client = this._client;
|
|
@@ -220,16 +222,16 @@ class GrpcManager extends EventEmitter {
|
|
|
220
222
|
client.close();
|
|
221
223
|
}
|
|
222
224
|
catch (e) {
|
|
223
|
-
|
|
225
|
+
this._log.error('GrpcManager', 'destroyClient() client.close() rejection: %s\n%s', e && e.message, e.stack);
|
|
224
226
|
}
|
|
225
227
|
}
|
|
226
228
|
async startStream(lastEventSeq, accountId) {
|
|
227
|
-
|
|
229
|
+
this._log.verbose('GrpcManager', 'startStream()');
|
|
228
230
|
if (this.eventStream) {
|
|
229
|
-
|
|
231
|
+
this._log.verbose('GrpcManager', 'startStream() this.eventStream exists, dropped.');
|
|
230
232
|
this.eventStream = undefined;
|
|
231
233
|
}
|
|
232
|
-
|
|
234
|
+
this._log.verbose('GrpcManager', 'startStream() grpc -> event() ...');
|
|
233
235
|
const eventRequest = new puppet.EventRequest();
|
|
234
236
|
if (lastEventSeq) {
|
|
235
237
|
eventRequest.setSeq(Number(lastEventSeq));
|
|
@@ -237,9 +239,9 @@ class GrpcManager extends EventEmitter {
|
|
|
237
239
|
if (accountId) {
|
|
238
240
|
eventRequest.setAccountId(accountId);
|
|
239
241
|
}
|
|
240
|
-
|
|
242
|
+
this._log.info('GrpcManager', `startStream() connecting event stream with account ${accountId} and seq ${lastEventSeq}`);
|
|
241
243
|
const eventStream = this.client.event(eventRequest);
|
|
242
|
-
|
|
244
|
+
this._log.verbose('GrpcManager', 'startStream() grpc -> event() ... done');
|
|
243
245
|
/**
|
|
244
246
|
* Store the event data from the stream when we test connection,
|
|
245
247
|
* and re-emit the event data when we have finished testing the connection
|
|
@@ -294,14 +296,14 @@ class GrpcManager extends EventEmitter {
|
|
|
294
296
|
* if a puppet service provider is coming from the community, it might not follow the protocol specification.
|
|
295
297
|
* So we need a timeout for compatible with those providers
|
|
296
298
|
*/
|
|
297
|
-
|
|
299
|
+
this._log.verbose('GrpcManager', 'startStream() grpc -> event peeking data or timeout ...');
|
|
298
300
|
try {
|
|
299
301
|
await timeoutPromise(future, 5 * 1000); // 5 seconds
|
|
300
302
|
}
|
|
301
303
|
catch (_) {
|
|
302
|
-
|
|
304
|
+
this._log.verbose('GrpcManager', 'startStream() grpc -> event peeking data or timeout ... timeout');
|
|
303
305
|
}
|
|
304
|
-
|
|
306
|
+
this._log.verbose('GrpcManager', 'startStream() grpc -> event peeking data or timeout ... data peeked');
|
|
305
307
|
/**
|
|
306
308
|
* Bridge the events
|
|
307
309
|
* Huan(202108): adding the below event listeners
|
|
@@ -309,7 +311,7 @@ class GrpcManager extends EventEmitter {
|
|
|
309
311
|
* so that if there's any `error` event,
|
|
310
312
|
* it will be triggered already.
|
|
311
313
|
*/
|
|
312
|
-
|
|
314
|
+
this._log.verbose('GrpcManager', 'startStream() initializing event stream ...');
|
|
313
315
|
eventStream
|
|
314
316
|
.on('cancel', (...args) => this.emit('cancel', ...args))
|
|
315
317
|
.on('data', (...args) => this.emit('data', ...args))
|
|
@@ -318,22 +320,22 @@ class GrpcManager extends EventEmitter {
|
|
|
318
320
|
.on('metadata', (...args) => this.emit('metadata', ...args))
|
|
319
321
|
.on('status', (...args) => this.emit('status', ...args));
|
|
320
322
|
this.eventStream = eventStream;
|
|
321
|
-
|
|
323
|
+
this._log.verbose('GrpcManager', 'startStream() initializing event stream ... done');
|
|
322
324
|
/**
|
|
323
325
|
* Re-emit the peeked data if there' s any
|
|
324
326
|
*/
|
|
325
327
|
if (peekedData) {
|
|
326
|
-
|
|
328
|
+
this._log.verbose('GrpcManager', 'startStream() sending back the peeked data ...');
|
|
327
329
|
this.emit('data', peekedData);
|
|
328
330
|
peekedData = undefined;
|
|
329
|
-
|
|
331
|
+
this._log.verbose('GrpcManager', 'startStream() sending back the peeked data ... done');
|
|
330
332
|
}
|
|
331
|
-
|
|
333
|
+
this._log.verbose('GrpcManager', 'startStream() ... done');
|
|
332
334
|
}
|
|
333
335
|
stopStream() {
|
|
334
|
-
|
|
336
|
+
this._log.verbose('GrpcManager', 'stopStream()');
|
|
335
337
|
if (!this.eventStream) {
|
|
336
|
-
|
|
338
|
+
this._log.verbose('GrpcManager', 'stopStream() no eventStream when stop, skip destroy.');
|
|
337
339
|
return;
|
|
338
340
|
}
|
|
339
341
|
/**
|
|
@@ -349,9 +351,9 @@ class GrpcManager extends EventEmitter {
|
|
|
349
351
|
*/
|
|
350
352
|
// this.eventStream.cancel()
|
|
351
353
|
try {
|
|
352
|
-
|
|
354
|
+
this._log.verbose('GrpcManager', 'stopStream() destroying event stream ...');
|
|
353
355
|
eventStream.destroy();
|
|
354
|
-
|
|
356
|
+
this._log.verbose('GrpcManager', 'stopStream() destroying event stream ... done');
|
|
355
357
|
}
|
|
356
358
|
catch (e) {
|
|
357
359
|
this.emit('error', e);
|