m365connector 0.3.5 → 0.3.7

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.
@@ -1,9 +1,5 @@
1
1
  import crypto from "node:crypto";
2
2
 
3
- const WHOAMI_TIMEOUT_MS = 60000;
4
- const FIND_PEOPLE_TIMEOUT_MS = 90000;
5
- const COUNT_PEOPLE_TIMEOUT_MS = 120000;
6
-
7
3
  const SEARCH_TOOL = {
8
4
  name: "search",
9
5
  description:
@@ -247,10 +243,9 @@ export function registerTools(registry, context) {
247
243
  const size = Number.isInteger(args.size) ? args.size : 10;
248
244
  const from = Number.isInteger(args.from) ? args.from : 0;
249
245
 
250
- const wsResult = await context.ws.sendRequest(
251
- "search",
252
- { conversation_id: conversationId, query, source, connector, size, from },
253
- { timeoutMs: 30000 }
246
+ await context.substrateTokenProvider.getTokenEntry();
247
+ const wsResult = await context.searchApi.search(
248
+ { conversation_id: conversationId, query, source, connector, size, from }
254
249
  );
255
250
 
256
251
  const resultObject = asObject(wsResult);
@@ -330,10 +325,8 @@ export function registerTools(registry, context) {
330
325
  throw new Error("Invalid or expired read_handle. Run search again.");
331
326
  }
332
327
 
333
- const wsResult = await context.ws.sendRequest(
334
- "open",
335
- { conversation_id: conversationId, context: readContext, fullContent: Boolean(args.fullContent) },
336
- { timeoutMs: args.fullContent ? 90000 : 60000 }
328
+ const wsResult = await context.contentReader.read(
329
+ { conversation_id: conversationId, context: readContext, fullContent: Boolean(args.fullContent) }
337
330
  );
338
331
 
339
332
  const result = asObject(wsResult);
@@ -407,16 +400,11 @@ export function registerTools(registry, context) {
407
400
  throw new Error("conversation_id is required");
408
401
  }
409
402
 
410
- const wsResult = await context.ws.sendRequest(
411
- "whoami",
412
- {
413
- conversation_id: conversationId,
414
- direct_reports_size: Number.isInteger(args.direct_reports_size)
415
- ? args.direct_reports_size
416
- : 30
417
- },
418
- { timeoutMs: WHOAMI_TIMEOUT_MS }
419
- );
403
+ const wsResult = await context.contentReader.whoAmI({
404
+ directReportsSize: Number.isInteger(args.direct_reports_size)
405
+ ? args.direct_reports_size
406
+ : 30
407
+ });
420
408
 
421
409
  return asObject(wsResult);
422
410
  });
@@ -440,11 +428,7 @@ export function registerTools(registry, context) {
440
428
  };
441
429
  const size = Number.isInteger(args.size) ? args.size : 20;
442
430
 
443
- const wsResult = await context.ws.sendRequest(
444
- "find_people",
445
- { conversation_id: conversationId, ...requestParams, size },
446
- { timeoutMs: FIND_PEOPLE_TIMEOUT_MS }
447
- );
431
+ const wsResult = await context.contentReader.findPeople({ ...requestParams, size });
448
432
 
449
433
  return asObject(wsResult);
450
434
  });
@@ -465,11 +449,7 @@ export function registerTools(registry, context) {
465
449
  title: typeof args.title === "string" ? args.title.trim() : ""
466
450
  };
467
451
 
468
- const wsResult = await context.ws.sendRequest(
469
- "count_people",
470
- { conversation_id: conversationId, ...requestParams },
471
- { timeoutMs: COUNT_PEOPLE_TIMEOUT_MS }
472
- );
452
+ const wsResult = await context.contentReader.countPeople(requestParams);
473
453
 
474
454
  return asObject(wsResult);
475
455
  });
package/src/ws-server.js DELETED
@@ -1,203 +0,0 @@
1
- import crypto from "node:crypto";
2
- import { WebSocket, WebSocketServer } from "ws";
3
-
4
- export class WsServer {
5
- constructor(options = {}) {
6
- const stderrLogger = {
7
- info: (...args) => console.error(...args),
8
- warn: (...args) => console.warn(...args),
9
- error: (...args) => console.error(...args)
10
- };
11
-
12
- this.host = options.host || "127.0.0.1";
13
- this.port = Number(options.port || 52365);
14
- this.logger = options.logger || stderrLogger;
15
-
16
- this.wss = null;
17
- this.client = null;
18
- this.pending = new Map();
19
- }
20
-
21
- async start() {
22
- if (this.wss) {
23
- return;
24
- }
25
-
26
- this.wss = new WebSocketServer({
27
- host: this.host,
28
- port: this.port
29
- });
30
-
31
- this.wss.on("connection", (socket, req) => {
32
- this.handleConnection(socket, req).catch((err) => {
33
- this.logger.error("[m365connector/ws] connection handling failed:", err);
34
- try {
35
- socket.close(1011, "internal error");
36
- } catch (_err) {
37
- // ignored
38
- }
39
- });
40
- });
41
-
42
- await new Promise((resolve, reject) => {
43
- this.wss.once("listening", resolve);
44
- this.wss.once("error", reject);
45
- });
46
-
47
- this.logger.info(`[m365connector/ws] listening on ws://${this.host}:${this.port}`);
48
- }
49
-
50
- async stop() {
51
- for (const pending of this.pending.values()) {
52
- clearTimeout(pending.timeout);
53
- pending.reject(new Error("WebSocket server stopped"));
54
- }
55
- this.pending.clear();
56
-
57
- if (this.client) {
58
- try {
59
- this.client.close(1001, "server stopping");
60
- } catch (_err) {
61
- // ignored
62
- }
63
- this.client = null;
64
- }
65
-
66
- if (!this.wss) {
67
- return;
68
- }
69
-
70
- const wss = this.wss;
71
- this.wss = null;
72
- await new Promise((resolve) => wss.close(resolve));
73
- }
74
-
75
- isReady() {
76
- return Boolean(this.client && this.client.readyState === WebSocket.OPEN);
77
- }
78
-
79
- getStatus() {
80
- return {
81
- host: this.host,
82
- port: this.port,
83
- connected: this.isReady()
84
- };
85
- }
86
-
87
- async sendRequest(type, params, options = {}) {
88
- const timeoutMs = Number(options.timeoutMs || 30000);
89
-
90
- if (!this.isReady()) {
91
- throw new Error("M365 Connector extension is not connected");
92
- }
93
-
94
- const id = crypto.randomUUID();
95
- const payload = { id, type, params: params || {} };
96
-
97
- return new Promise((resolve, reject) => {
98
- const timeout = setTimeout(() => {
99
- this.logger.warn(`[m365connector/ws] request timed out: type=${type} id=${id}`);
100
- this.pending.delete(id);
101
- reject(new Error(`WebSocket request timed out after ${timeoutMs}ms`));
102
- }, timeoutMs);
103
-
104
- this.pending.set(id, { resolve, reject, timeout });
105
-
106
- try {
107
- this.client.send(JSON.stringify(payload));
108
- } catch (err) {
109
- clearTimeout(timeout);
110
- this.pending.delete(id);
111
- reject(err);
112
- }
113
- });
114
- }
115
-
116
- async handleConnection(socket, req) {
117
- const origin = req.headers.origin;
118
-
119
- if (!origin) {
120
- this.logger.warn("[m365connector/ws] rejected connection: missing origin header");
121
- socket.close(1008, "origin required");
122
- return;
123
- }
124
-
125
- if (!origin.startsWith("chrome-extension://")) {
126
- this.logger.warn(`[m365connector/ws] rejected connection from origin: ${origin}`);
127
- socket.close(1008, "origin not allowed");
128
- return;
129
- }
130
-
131
- if (this.client && this.client.readyState === WebSocket.OPEN) {
132
- // MV3 service workers can die silently without sending a close frame.
133
- // When the extension reconnects, replace the stale connection.
134
- this.logger.info("[m365connector/ws] replacing existing connection with new one");
135
- try { this.client.terminate(); } catch (_e) { /* ignored */ }
136
- this.handleClose(this.client);
137
- }
138
-
139
- // Clean up if client ref exists but is no longer OPEN
140
- if (this.client && this.client.readyState !== WebSocket.OPEN) {
141
- this.client = null;
142
- }
143
-
144
- this.client = socket;
145
-
146
- socket.on("message", async (rawData) => {
147
- await this.handleMessage(socket, rawData);
148
- });
149
-
150
- socket.on("close", (code, reason) => {
151
- this.logger.info(`[m365connector/ws] socket close: code=${code}, reason=${String(reason)}`);
152
- this.handleClose(socket);
153
- });
154
-
155
- socket.on("error", (err) => {
156
- this.logger.warn("[m365connector/ws] client socket error:", err.message);
157
- });
158
- this.logger.info("[m365connector/ws] extension connected");
159
- }
160
-
161
- async handleMessage(socket, rawData) {
162
- let message;
163
- try {
164
- message = JSON.parse(String(rawData));
165
- } catch (_err) {
166
- socket.close(4002, "invalid json");
167
- return;
168
- }
169
-
170
- if (!message?.id) {
171
- return;
172
- }
173
-
174
- const pending = this.pending.get(message.id);
175
- if (!pending) {
176
- return;
177
- }
178
-
179
- clearTimeout(pending.timeout);
180
- this.pending.delete(message.id);
181
-
182
- if (!message.success) {
183
- pending.reject(new Error(message.error || "Unknown extension error"));
184
- return;
185
- }
186
-
187
- pending.resolve(message.data);
188
- }
189
-
190
- handleClose(socket) {
191
- if (this.client === socket) {
192
- this.client = null;
193
- }
194
-
195
- for (const [id, pending] of this.pending.entries()) {
196
- clearTimeout(pending.timeout);
197
- pending.reject(new Error("Extension disconnected"));
198
- this.pending.delete(id);
199
- }
200
-
201
- this.logger.info("[m365connector/ws] extension disconnected");
202
- }
203
- }