kingkont 0.18.11 → 0.18.13
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/main.js +70 -13
- package/package.json +1 -1
- package/renderer/board.js +28 -7
- package/renderer/notifyPanel.js +13 -5
package/main.js
CHANGED
|
@@ -273,12 +273,26 @@ ipcMain.handle('chatium:status', async () => {
|
|
|
273
273
|
const base = s?.chatium?.base || CHATIUM_BASE;
|
|
274
274
|
if (!token) return { connected: false };
|
|
275
275
|
try {
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
276
|
+
const url = `${base}/app/spaces/server/api/auth~me`;
|
|
277
|
+
const r = await fetch(url, { headers: { 'Authorization': `Bearer ${token}` } });
|
|
278
|
+
const rawText = await r.text();
|
|
279
|
+
let me; try { me = JSON.parse(rawText); } catch { me = null; }
|
|
280
|
+
// Диагностика: логируем что Chatium вообще отдаёт. Юзер жалуется
|
|
281
|
+
// что displayName/email не показываются — нужно увидеть какие
|
|
282
|
+
// поля приходят в реальности.
|
|
283
|
+
console.log(`[chatium:status] ${r.status} ${url}`);
|
|
284
|
+
console.log(`[chatium:status] response keys: ${me ? Object.keys(me).join(', ') : '(non-json)'}`);
|
|
285
|
+
console.log(`[chatium:status] response body: ${rawText.slice(0, 800)}`);
|
|
279
286
|
if (r.ok) {
|
|
280
|
-
|
|
281
|
-
|
|
287
|
+
// _allKeys и _raw добавлены для UI-debug — renderer покажет их
|
|
288
|
+
// в console если displayName/email/login пусто.
|
|
289
|
+
return {
|
|
290
|
+
connected: true,
|
|
291
|
+
...(me || {}),
|
|
292
|
+
base,
|
|
293
|
+
_allKeys: me ? Object.keys(me) : [],
|
|
294
|
+
_raw: rawText.slice(0, 800),
|
|
295
|
+
};
|
|
282
296
|
}
|
|
283
297
|
if (r.status === 401) {
|
|
284
298
|
// Токен протух/отозван — чистим.
|
|
@@ -287,8 +301,9 @@ ipcMain.handle('chatium:status', async () => {
|
|
|
287
301
|
writeSettings(next);
|
|
288
302
|
return { connected: false, reason: 'expired' };
|
|
289
303
|
}
|
|
290
|
-
return { connected: false, reason: `http_${r.status}
|
|
304
|
+
return { connected: false, reason: `http_${r.status}`, _raw: rawText.slice(0, 400) };
|
|
291
305
|
} catch (e) {
|
|
306
|
+
console.warn('[chatium:status] network error:', e?.message || e);
|
|
292
307
|
return { connected: false, reason: 'network_error', error: String(e?.message || e) };
|
|
293
308
|
}
|
|
294
309
|
});
|
|
@@ -296,18 +311,43 @@ ipcMain.handle('chatium:status', async () => {
|
|
|
296
311
|
function runChatiumLoginFlow() {
|
|
297
312
|
return new Promise((resolveOk, rejectErr) => {
|
|
298
313
|
const state = crypto.randomBytes(16).toString('hex');
|
|
314
|
+
// Кэш отрендеренной success-страницы: после первого успешного /cb
|
|
315
|
+
// ВСЕ следующие запросы (refresh, navigation back, favicon retry,
|
|
316
|
+
// Chatium повторно редиректит и т.д.) отдают ту же страницу.
|
|
317
|
+
// Иначе юзер мог увидеть «localhost не грузится» если refresh'нул
|
|
318
|
+
// вкладку после автозакрытия server'а. Listener живёт ещё 30s
|
|
319
|
+
// после успеха для этих случаев.
|
|
320
|
+
let cachedSuccessPage = null;
|
|
321
|
+
let alreadyResolved = false;
|
|
299
322
|
const server = http.createServer((req, res) => {
|
|
300
323
|
const url = new URL(req.url, `http://localhost`);
|
|
324
|
+
console.log(`[chatium-login] ← ${req.method} ${req.url}`);
|
|
325
|
+
// Уже зарезолвили — отдаём ту же success-страницу повторно
|
|
326
|
+
// (юзер refresh'нул, или Chatium шлёт callback дважды).
|
|
327
|
+
if (alreadyResolved && cachedSuccessPage) {
|
|
328
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
329
|
+
res.end(cachedSuccessPage);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (url.pathname === '/favicon.ico') {
|
|
333
|
+
res.writeHead(204); res.end();
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
301
336
|
if (url.pathname !== '/cb') {
|
|
302
|
-
|
|
337
|
+
console.log(`[chatium-login] 404 (unexpected path): ${url.pathname}`);
|
|
338
|
+
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
339
|
+
res.end('Not found. Ожидался /cb от Chatium-callback.');
|
|
303
340
|
return;
|
|
304
341
|
}
|
|
305
342
|
const token = url.searchParams.get('token');
|
|
306
343
|
const recvState = url.searchParams.get('state');
|
|
344
|
+
const allParams = Array.from(url.searchParams.keys());
|
|
345
|
+
console.log(`[chatium-login] callback params: ${allParams.join(', ')}`);
|
|
307
346
|
|
|
308
347
|
if (!token || recvState !== state) {
|
|
348
|
+
console.warn(`[chatium-login] BAD callback: token=${!!token} stateMatch=${recvState === state}`);
|
|
309
349
|
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
310
|
-
res.end(authResultPage('error',
|
|
350
|
+
res.end(authResultPage('error', `Авторизация не удалась. token=${token ? 'есть' : 'нет'}, state=${recvState === state ? 'ok' : 'mismatch'}. Попробуйте снова.`));
|
|
311
351
|
cleanup();
|
|
312
352
|
rejectErr(new Error('State mismatch или token пуст'));
|
|
313
353
|
return;
|
|
@@ -318,12 +358,25 @@ function runChatiumLoginFlow() {
|
|
|
318
358
|
fetch(`${CHATIUM_BASE}/app/spaces/server/api/auth~me`, {
|
|
319
359
|
headers: { 'Authorization': `Bearer ${token}` },
|
|
320
360
|
})
|
|
321
|
-
.then(r => r.ok
|
|
322
|
-
.catch(
|
|
323
|
-
.then(
|
|
361
|
+
.then(r => r.text().then(t => ({ ok: r.ok, status: r.status, text: t })))
|
|
362
|
+
.catch(e => ({ ok: false, status: 0, text: '', err: e?.message || String(e) }))
|
|
363
|
+
.then(({ ok, status, text, err }) => {
|
|
364
|
+
let me = null;
|
|
365
|
+
try { me = JSON.parse(text); } catch {}
|
|
366
|
+
console.log(`[chatium-login] /me → ${status} keys=${me ? Object.keys(me).join(',') : '(non-json)'}`);
|
|
367
|
+
if (err) console.warn(`[chatium-login] /me fetch failed: ${err}`);
|
|
368
|
+
cachedSuccessPage = authResultPage('ok', 'Готово! Можно закрыть эту вкладку и вернуться в приложение.');
|
|
369
|
+
alreadyResolved = true;
|
|
324
370
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
325
|
-
res.end(
|
|
326
|
-
|
|
371
|
+
res.end(cachedSuccessPage);
|
|
372
|
+
// НЕ закрываем server сразу — даём юзеру 30s на refresh / повторный
|
|
373
|
+
// hit (Chatium иногда шлёт callback дважды; или юзер случайно
|
|
374
|
+
// нажал refresh на этой вкладке).
|
|
375
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
376
|
+
timeoutId = setTimeout(() => {
|
|
377
|
+
console.log('[chatium-login] graceful close after 30s grace period');
|
|
378
|
+
cleanup();
|
|
379
|
+
}, 30 * 1000);
|
|
327
380
|
resolveOk({
|
|
328
381
|
token,
|
|
329
382
|
userId: me?.userId || null,
|
|
@@ -342,6 +395,7 @@ function runChatiumLoginFlow() {
|
|
|
342
395
|
}
|
|
343
396
|
|
|
344
397
|
server.on('error', (err) => {
|
|
398
|
+
console.warn('[chatium-login] server error:', err.message);
|
|
345
399
|
cleanup();
|
|
346
400
|
rejectErr(err);
|
|
347
401
|
});
|
|
@@ -349,12 +403,14 @@ function runChatiumLoginFlow() {
|
|
|
349
403
|
server.listen(0, '127.0.0.1', () => {
|
|
350
404
|
const addr = server.address();
|
|
351
405
|
const callback = `http://localhost:${addr.port}/cb`;
|
|
406
|
+
console.log(`[chatium-login] listener up on ${callback}`);
|
|
352
407
|
// На стороне Chatium роут авторизации привязан к корню workspace (/),
|
|
353
408
|
// т.е. /app/spaces/server. После успеха он редиректит на наш callback с token.
|
|
354
409
|
const url = new URL(`${CHATIUM_BASE}/app/spaces/server`);
|
|
355
410
|
url.searchParams.set('callback', callback);
|
|
356
411
|
url.searchParams.set('state', state);
|
|
357
412
|
url.searchParams.set('app', 'KingKont');
|
|
413
|
+
console.log(`[chatium-login] opening browser → ${url.toString()}`);
|
|
358
414
|
|
|
359
415
|
shell.openExternal(url.toString()).catch((e) => {
|
|
360
416
|
cleanup();
|
|
@@ -362,6 +418,7 @@ function runChatiumLoginFlow() {
|
|
|
362
418
|
});
|
|
363
419
|
|
|
364
420
|
timeoutId = setTimeout(() => {
|
|
421
|
+
console.warn('[chatium-login] timeout 5min — abort');
|
|
365
422
|
cleanup();
|
|
366
423
|
rejectErr(new Error('Таймаут: авторизация не завершена за 5 минут'));
|
|
367
424
|
}, CHATIUM_AUTH_TIMEOUT_MS);
|
package/package.json
CHANGED
package/renderer/board.js
CHANGED
|
@@ -216,21 +216,42 @@ async function renderWelcomeIdentity() {
|
|
|
216
216
|
let status = null;
|
|
217
217
|
try { status = await window.appChatium?.status?.(); } catch {}
|
|
218
218
|
if (status?.connected) {
|
|
219
|
-
// Приоритет имени: displayName
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
// Приоритет имени: displayName → name → fullName → login → confirmedEmail
|
|
220
|
+
// → email → user.* (если nested) → userId. Раньше displayName был
|
|
221
|
+
// first, но Chatium может возвращать поле под другим именем —
|
|
222
|
+
// проверяем все известные варианты + nested user-объект.
|
|
223
|
+
const u = status.user || status.profile || status.account || {};
|
|
224
|
+
const name = status.displayName || status.name || status.fullName
|
|
225
|
+
|| status.login || status.username || status.userName
|
|
226
|
+
|| status.confirmedEmail || status.email
|
|
227
|
+
|| u.displayName || u.name || u.fullName || u.login || u.email
|
|
223
228
|
|| status.userId || 'KingKont';
|
|
224
229
|
const sub = status.userId && status.userId !== name ? `· ${status.userId.slice(0, 8)}` : '';
|
|
225
|
-
// Диагностика: если
|
|
226
|
-
|
|
227
|
-
|
|
230
|
+
// Диагностика: если ни одного «человеческого» поля не нашлось —
|
|
231
|
+
// громкий лог в консоль с полным дампом, чтобы видеть что прислал
|
|
232
|
+
// сервер (см. main.js [chatium:status] логи тоже).
|
|
233
|
+
const noHumanField = !status.displayName && !status.name && !status.fullName
|
|
234
|
+
&& !status.login && !status.confirmedEmail && !status.email
|
|
235
|
+
&& !u.displayName && !u.name && !u.email;
|
|
236
|
+
if (noHumanField) {
|
|
237
|
+
console.warn('[chat-identity] No human-readable name field. Status object:', status);
|
|
238
|
+
console.warn('[chat-identity] _allKeys:', status._allKeys);
|
|
239
|
+
console.warn('[chat-identity] _raw:', status._raw);
|
|
228
240
|
}
|
|
229
241
|
wrap.innerHTML = `
|
|
230
242
|
<span style="color:#5c5; font-size:13px; line-height:1;">●</span>
|
|
231
243
|
<span class="who">${escapeHtml(name)}</span>
|
|
232
244
|
<span class="who-sub">${escapeHtml(sub)}</span>
|
|
233
245
|
`;
|
|
246
|
+
// Если имя не найдено — добавляем visible debug-pill с available keys,
|
|
247
|
+
// чтобы юзер мог тебе показать что отдаёт Chatium без копания в консоли.
|
|
248
|
+
if (noHumanField && Array.isArray(status._allKeys) && status._allKeys.length) {
|
|
249
|
+
const dbg = document.createElement('span');
|
|
250
|
+
dbg.style.cssText = 'background:#3a2a4a; color:#dcb; font-size:10px; padding:2px 6px; border-radius:4px; font-family:ui-monospace,monospace; cursor:help;';
|
|
251
|
+
dbg.textContent = `keys: ${status._allKeys.join(',').slice(0, 60)}`;
|
|
252
|
+
dbg.title = `Полный ответ:\n${status._raw || '(пусто)'}\n\nИ скопируй это сообщение разработчику.`;
|
|
253
|
+
wrap.appendChild(dbg);
|
|
254
|
+
}
|
|
234
255
|
const logoutBtn = document.createElement('button');
|
|
235
256
|
logoutBtn.textContent = 'Выйти';
|
|
236
257
|
logoutBtn.title = 'Logout из KingKont';
|
package/renderer/notifyPanel.js
CHANGED
|
@@ -200,14 +200,22 @@
|
|
|
200
200
|
// Public API.
|
|
201
201
|
// Дедуп: gen-completion идёт двумя путями (local pollJob → showToast,
|
|
202
202
|
// и server _startPoller → WS jobs:all). Без дедупа юзер видит ДВА
|
|
203
|
-
// одинаковых события с разницей в секунды.
|
|
204
|
-
//
|
|
203
|
+
// одинаковых события с разницей в секунды.
|
|
204
|
+
//
|
|
205
|
+
// Ключ дедупа — ТОЛЬКО nodeId (без kind). Раньше был «kind:nodeId»,
|
|
206
|
+
// но local pollJob использует kind='info' когда генерация завершилась
|
|
207
|
+
// на НЕ-текущей доске, а WS-path всегда 'ok' → разные ключи → дубль
|
|
208
|
+
// не ловился (юзер видел два одинаковых «✓ image «name» готов»,
|
|
209
|
+
// один синий, второй зелёный). Сейчас одинаковый nodeId-completion
|
|
210
|
+
// в окне 30s — один event.
|
|
205
211
|
const DEDUP_WINDOW_MS = 30 * 1000;
|
|
212
|
+
// События с этими kind считаются «completion» — для них дедупим по nodeId.
|
|
213
|
+
const _COMPLETION_KINDS = new Set(['ok', 'error', 'info', 'warn']);
|
|
206
214
|
function _dedupKey(kind, text, target) {
|
|
207
|
-
if (target?.nodeId && (kind
|
|
208
|
-
return
|
|
215
|
+
if (target?.nodeId && _COMPLETION_KINDS.has(kind)) {
|
|
216
|
+
return `node:${target.nodeId}`; // одинаковый nodeId = тот же gen
|
|
209
217
|
}
|
|
210
|
-
return null; //
|
|
218
|
+
return null; // chat-события (target.chat) не дедупим — нет nodeId
|
|
211
219
|
}
|
|
212
220
|
function addEvent({ kind, text, raw, target }) {
|
|
213
221
|
if (!text) return;
|