@open-mercato/core 0.6.8-develop.6891.1.4dca3f1ad3 → 0.6.8-develop.6893.1.7af3b3a72d
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/.turbo/turbo-build.log +1 -1
- package/dist/modules/communication_channels/api/get/me/channels/route.js +16 -2
- package/dist/modules/communication_channels/api/get/me/channels/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js +14 -1
- package/dist/modules/communication_channels/api/post/channels/[id]/poll-now/route.js.map +2 -2
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +24 -11
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +2 -2
- package/dist/modules/communication_channels/lib/polling-eligibility.js +8 -0
- package/dist/modules/communication_channels/lib/polling-eligibility.js.map +7 -0
- package/dist/modules/communication_channels/workers/poll-channel.js +2 -2
- package/dist/modules/communication_channels/workers/poll-channel.js.map +2 -2
- package/dist/modules/configs/cli.js +12 -10
- package/dist/modules/configs/cli.js.map +2 -2
- package/dist/modules/configs/lib/touchGeneratedBarrels.js +2 -2
- package/dist/modules/configs/lib/touchGeneratedBarrels.js.map +2 -2
- package/dist/modules/customers/message-objects.js +1 -1
- package/dist/modules/customers/message-objects.js.map +1 -1
- package/package.json +8 -8
- package/src/modules/communication_channels/api/get/me/channels/route.ts +24 -2
- package/src/modules/communication_channels/api/post/channels/[id]/poll-now/route.ts +22 -1
- package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +58 -13
- package/src/modules/communication_channels/i18n/de.json +6 -0
- package/src/modules/communication_channels/i18n/en.json +6 -0
- package/src/modules/communication_channels/i18n/es.json +6 -0
- package/src/modules/communication_channels/i18n/ko.json +6 -0
- package/src/modules/communication_channels/i18n/pl.json +6 -0
- package/src/modules/communication_channels/lib/polling-eligibility.ts +14 -0
- package/src/modules/communication_channels/workers/poll-channel.ts +3 -3
- package/src/modules/configs/cli.ts +12 -10
- package/src/modules/configs/lib/touchGeneratedBarrels.ts +5 -2
- package/src/modules/customers/message-objects.ts +1 -1
|
@@ -42,6 +42,13 @@ type ChannelRow = {
|
|
|
42
42
|
/** Spec C — push delivery state (null when provider doesn't support push). */
|
|
43
43
|
pushStatus: 'active' | 'inactive' | 'failed' | null
|
|
44
44
|
lastPushError: { code: string | null; message: string | null; at: string | null } | null
|
|
45
|
+
/**
|
|
46
|
+
* `true` when the adapter declares real-time push, so the hub's poll worker
|
|
47
|
+
* skips this channel entirely — inbound arrives over the provider connection.
|
|
48
|
+
*/
|
|
49
|
+
supportsRealtimePush: boolean
|
|
50
|
+
/** `true` when the adapter implements `registerPush` (Gmail-style subscriptions). */
|
|
51
|
+
supportsPushRegistration: boolean
|
|
45
52
|
createdAt: string | null
|
|
46
53
|
}
|
|
47
54
|
|
|
@@ -314,17 +321,38 @@ export default function ProfileCommunicationChannelsPage() {
|
|
|
314
321
|
},
|
|
315
322
|
{
|
|
316
323
|
id: 'pushStatus',
|
|
317
|
-
|
|
324
|
+
// Its own key: sharing `push.status.active` made the header render
|
|
325
|
+
// "Push active" over a column whose rows say "Polling only" (#4980).
|
|
326
|
+
header: t('communication_channels.profile.columns.push', 'Push'),
|
|
318
327
|
cell: ({ row }) => {
|
|
319
|
-
const
|
|
320
|
-
|
|
328
|
+
const ps = row.original.pushStatus
|
|
329
|
+
const errorTitle = row.original.lastPushError?.message ?? undefined
|
|
330
|
+
// Derived from the adapter's declared capabilities, never from the
|
|
331
|
+
// provider name: `supportsPushRegistration` means push subscriptions
|
|
332
|
+
// can be (re-)registered from here, `supportsRealtimePush` means the
|
|
333
|
+
// hub's poll worker skips the channel because the provider connection
|
|
334
|
+
// delivers inbound itself (#4980).
|
|
335
|
+
if (!row.original.supportsPushRegistration) {
|
|
336
|
+
if (!row.original.supportsRealtimePush) {
|
|
337
|
+
return (
|
|
338
|
+
<span className="text-xs text-muted-foreground">
|
|
339
|
+
{t('communication_channels.push.status.inactive', 'Polling only')}
|
|
340
|
+
</span>
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
if (ps === 'failed') {
|
|
344
|
+
return (
|
|
345
|
+
<Tag variant="error" dot title={errorTitle}>
|
|
346
|
+
{t('communication_channels.push.status.pushDrivenFailed', 'Push connection failed')}
|
|
347
|
+
</Tag>
|
|
348
|
+
)
|
|
349
|
+
}
|
|
321
350
|
return (
|
|
322
|
-
<
|
|
323
|
-
{t('communication_channels.push.status.
|
|
324
|
-
</
|
|
351
|
+
<Tag variant="success" dot>
|
|
352
|
+
{t('communication_channels.push.status.pushDriven', 'Push-driven')}
|
|
353
|
+
</Tag>
|
|
325
354
|
)
|
|
326
355
|
}
|
|
327
|
-
const ps = row.original.pushStatus
|
|
328
356
|
if (ps === 'active') {
|
|
329
357
|
return (
|
|
330
358
|
<Tag variant="success" dot>
|
|
@@ -333,7 +361,6 @@ export default function ProfileCommunicationChannelsPage() {
|
|
|
333
361
|
)
|
|
334
362
|
}
|
|
335
363
|
if (ps === 'failed') {
|
|
336
|
-
const errorMsg = row.original.lastPushError?.message ?? null
|
|
337
364
|
return (
|
|
338
365
|
<div className="flex items-center gap-2">
|
|
339
366
|
<Tag variant="error" dot>
|
|
@@ -345,18 +372,23 @@ export default function ProfileCommunicationChannelsPage() {
|
|
|
345
372
|
size="sm"
|
|
346
373
|
onClick={() => void onRegisterPush(row.original.id)}
|
|
347
374
|
aria-label={t('communication_channels.push.button.reregister', 'Re-register push')}
|
|
348
|
-
title={
|
|
375
|
+
title={errorTitle}
|
|
349
376
|
>
|
|
350
377
|
{t('communication_channels.push.button.reregister', 'Re-register push')}
|
|
351
378
|
</Button>
|
|
352
379
|
</div>
|
|
353
380
|
)
|
|
354
381
|
}
|
|
355
|
-
// null or 'inactive' — provider
|
|
382
|
+
// null or 'inactive' — the provider can register push but has not yet.
|
|
383
|
+
// Only a hub-polled channel falls back to polling meanwhile; for a
|
|
384
|
+
// push-driven one nothing is delivering inbound at all, so claiming
|
|
385
|
+
// "Polling only" would repeat the defect this issue is about (#4980).
|
|
356
386
|
return (
|
|
357
387
|
<div className="flex items-center gap-2">
|
|
358
388
|
<span className="text-xs text-muted-foreground">
|
|
359
|
-
{
|
|
389
|
+
{row.original.supportsRealtimePush
|
|
390
|
+
? t('communication_channels.push.status.notRegistered', 'Push not registered')
|
|
391
|
+
: t('communication_channels.push.status.inactive', 'Polling only')}
|
|
360
392
|
</span>
|
|
361
393
|
<Button
|
|
362
394
|
type="button"
|
|
@@ -409,25 +441,38 @@ export default function ProfileCommunicationChannelsPage() {
|
|
|
409
441
|
// Allowed from 'connected' AND 'error' — the latter lets the user
|
|
410
442
|
// recover a stuck channel without disconnecting + reconnecting.
|
|
411
443
|
// 'requires_reauth' and 'disconnected' are owned by other flows.
|
|
444
|
+
// A push-driven channel is never polled by the worker, so offering the
|
|
445
|
+
// action at all would promise a sync that cannot happen (#4980).
|
|
446
|
+
const pushDriven = row.original.supportsRealtimePush
|
|
412
447
|
const pollable =
|
|
413
448
|
row.original.isActive &&
|
|
449
|
+
!pushDriven &&
|
|
414
450
|
(row.original.status === 'connected' || row.original.status === 'error')
|
|
415
451
|
const label =
|
|
416
452
|
row.original.status === 'error'
|
|
417
453
|
? t('communication_channels.profile.actions.retryPoll', 'Retry')
|
|
418
454
|
: t('communication_channels.profile.actions.pollNow', 'Poll now')
|
|
419
|
-
|
|
455
|
+
const disabledReason = pushDriven
|
|
456
|
+
? t(
|
|
457
|
+
'communication_channels.profile.actions.pollNowPushDriven',
|
|
458
|
+
'This channel is push-driven — inbound messages arrive over the provider connection, so polling does not apply.',
|
|
459
|
+
)
|
|
460
|
+
: undefined
|
|
461
|
+
const button = (
|
|
420
462
|
<Button
|
|
421
463
|
type="button"
|
|
422
464
|
variant={row.original.status === 'error' ? 'default' : 'outline'}
|
|
423
465
|
size="sm"
|
|
424
466
|
onClick={() => void onPollNow(row.original.id)}
|
|
425
467
|
disabled={!pollable}
|
|
426
|
-
aria-label={label}
|
|
468
|
+
aria-label={disabledReason ? `${label} — ${disabledReason}` : label}
|
|
427
469
|
>
|
|
428
470
|
{label}
|
|
429
471
|
</Button>
|
|
430
472
|
)
|
|
473
|
+
// A disabled button does not receive hover events in every browser, so
|
|
474
|
+
// the explanation lives on a wrapper the pointer can still reach.
|
|
475
|
+
return disabledReason ? <span title={disabledReason}>{button}</span> : button
|
|
431
476
|
},
|
|
432
477
|
},
|
|
433
478
|
{
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"communication_channels.emptyState": "Noch keine gemeinsamen Kanäle. Diese Seite listet gemeinsame, mandantenweite Kanäle auf. Ihr persönliches E-Mail-Postfach ist privat – verbinden und verwalten Sie es unter Profil → Kommunikationskanäle.",
|
|
24
24
|
"communication_channels.errors.loadDetail": "Kanal konnte nicht geladen werden",
|
|
25
25
|
"communication_channels.errors.loadList": "Kanäle konnten nicht geladen werden",
|
|
26
|
+
"communication_channels.errors.pollNowPushDriven": "Der Kanal ist Push-gesteuert — Abrufen ist nicht anwendbar. Eingehende Nachrichten kommen über die Push-Verbindung des Anbieters.",
|
|
26
27
|
"communication_channels.errors.reactionFailed": "Reaktion fehlgeschlagen",
|
|
27
28
|
"communication_channels.errors.undoBlockedPrimaryConflict": "Ein anderer Kanal ist jetzt für diesen Benutzer als primär festgelegt. Setze ihn auf nicht-primär, bevor du den getrennten Kanal wieder als primär festlegst.",
|
|
28
29
|
"communication_channels.infoPanel.aria": "Kanalinfo",
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
"communication_channels.profile.actions.importHistory": "Verlauf importieren",
|
|
54
55
|
"communication_channels.profile.actions.pollNow": "Jetzt abrufen",
|
|
55
56
|
"communication_channels.profile.actions.pollNowFailed": "Abruf konnte nicht ausgelöst werden",
|
|
57
|
+
"communication_channels.profile.actions.pollNowPushDriven": "Dieser Kanal ist Push-gesteuert — eingehende Nachrichten kommen über die Anbieterverbindung, Abrufen ist daher nicht anwendbar.",
|
|
56
58
|
"communication_channels.profile.actions.pollNowSuccess": "Abruf ausgelöst — neue Nachrichten erscheinen in wenigen Sekunden auf den verknüpften Personen-Zeitleisten.",
|
|
57
59
|
"communication_channels.profile.actions.retryPoll": "Wiederholen",
|
|
58
60
|
"communication_channels.profile.actions.setPrimary": "Als primär festlegen",
|
|
@@ -64,6 +66,7 @@
|
|
|
64
66
|
"communication_channels.profile.columns.lastPolled": "Zuletzt synchronisiert",
|
|
65
67
|
"communication_channels.profile.columns.pollNow": "Synchronisieren",
|
|
66
68
|
"communication_channels.profile.columns.primary": "Primär",
|
|
69
|
+
"communication_channels.profile.columns.push": "Push",
|
|
67
70
|
"communication_channels.profile.connect.blocked": "Verbindung durch Validierung blockiert",
|
|
68
71
|
"communication_channels.profile.connect.cancel": "Abbrechen",
|
|
69
72
|
"communication_channels.profile.connect.connected": "Kanal verbunden.",
|
|
@@ -126,6 +129,9 @@
|
|
|
126
129
|
"communication_channels.push.status.active": "Push aktiv",
|
|
127
130
|
"communication_channels.push.status.failed": "Push fehlgeschlagen — nutze Polling",
|
|
128
131
|
"communication_channels.push.status.inactive": "Nur Polling",
|
|
132
|
+
"communication_channels.push.status.notRegistered": "Push nicht registriert",
|
|
133
|
+
"communication_channels.push.status.pushDriven": "Push-gesteuert",
|
|
134
|
+
"communication_channels.push.status.pushDrivenFailed": "Push-Verbindung fehlgeschlagen",
|
|
129
135
|
"communication_channels.reaction.bar.aria": "Reaktionen",
|
|
130
136
|
"communication_channels.reaction.cannotRemoveExternal": "Reaktionen externer Teilnehmer können nur in der Anbieter-App entfernt werden.",
|
|
131
137
|
"communication_channels.reaction.toggleAria": "Reaktion {emoji} umschalten",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"communication_channels.emptyState": "No shared channels yet. This page lists shared, tenant-wide channels. Your personal email mailbox is private — connect and manage it under Profile → Communication Channels.",
|
|
24
24
|
"communication_channels.errors.loadDetail": "Failed to load channel",
|
|
25
25
|
"communication_channels.errors.loadList": "Failed to load channels",
|
|
26
|
+
"communication_channels.errors.pollNowPushDriven": "Channel is push-driven — polling does not apply. Inbound messages arrive through the provider push connection.",
|
|
26
27
|
"communication_channels.errors.reactionFailed": "Reaction failed",
|
|
27
28
|
"communication_channels.errors.undoBlockedPrimaryConflict": "Another channel is now primary for this user. Set it as non-primary before restoring the disconnected channel as primary.",
|
|
28
29
|
"communication_channels.infoPanel.aria": "Channel info",
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
"communication_channels.profile.actions.importHistory": "Import history",
|
|
54
55
|
"communication_channels.profile.actions.pollNow": "Poll now",
|
|
55
56
|
"communication_channels.profile.actions.pollNowFailed": "Failed to trigger poll",
|
|
57
|
+
"communication_channels.profile.actions.pollNowPushDriven": "This channel is push-driven — inbound messages arrive over the provider connection, so polling does not apply.",
|
|
56
58
|
"communication_channels.profile.actions.pollNowSuccess": "Poll triggered — new messages will appear on linked Person timelines in a few seconds.",
|
|
57
59
|
"communication_channels.profile.actions.retryPoll": "Retry",
|
|
58
60
|
"communication_channels.profile.actions.setPrimary": "Set as primary",
|
|
@@ -64,6 +66,7 @@
|
|
|
64
66
|
"communication_channels.profile.columns.lastPolled": "Last synced",
|
|
65
67
|
"communication_channels.profile.columns.pollNow": "Sync",
|
|
66
68
|
"communication_channels.profile.columns.primary": "Primary",
|
|
69
|
+
"communication_channels.profile.columns.push": "Push",
|
|
67
70
|
"communication_channels.profile.connect.blocked": "Connection blocked by validation",
|
|
68
71
|
"communication_channels.profile.connect.cancel": "Cancel",
|
|
69
72
|
"communication_channels.profile.connect.connected": "Channel connected.",
|
|
@@ -126,6 +129,9 @@
|
|
|
126
129
|
"communication_channels.push.status.active": "Push active",
|
|
127
130
|
"communication_channels.push.status.failed": "Push failed — using polling",
|
|
128
131
|
"communication_channels.push.status.inactive": "Polling only",
|
|
132
|
+
"communication_channels.push.status.notRegistered": "Push not registered",
|
|
133
|
+
"communication_channels.push.status.pushDriven": "Push-driven",
|
|
134
|
+
"communication_channels.push.status.pushDrivenFailed": "Push connection failed",
|
|
129
135
|
"communication_channels.reaction.bar.aria": "Reactions",
|
|
130
136
|
"communication_channels.reaction.cannotRemoveExternal": "Reactions from external participants can only be removed in the provider app.",
|
|
131
137
|
"communication_channels.reaction.toggleAria": "Toggle {emoji} reaction",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"communication_channels.emptyState": "Aún no hay canales compartidos. Esta página muestra los canales compartidos de toda la organización. Tu buzón de correo personal es privado: conéctalo y gestiónalo en Perfil → Canales de comunicación.",
|
|
24
24
|
"communication_channels.errors.loadDetail": "No se pudo cargar el canal",
|
|
25
25
|
"communication_channels.errors.loadList": "No se pudieron cargar los canales",
|
|
26
|
+
"communication_channels.errors.pollNowPushDriven": "El canal funciona con push — el sondeo no aplica. Los mensajes entrantes llegan por la conexión push del proveedor.",
|
|
26
27
|
"communication_channels.errors.reactionFailed": "La reacción falló",
|
|
27
28
|
"communication_channels.errors.undoBlockedPrimaryConflict": "Otro canal es ahora el principal para este usuario. Configúralo como no principal antes de restaurar el canal desconectado como principal.",
|
|
28
29
|
"communication_channels.infoPanel.aria": "Información del canal",
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
"communication_channels.profile.actions.importHistory": "Importar historial",
|
|
54
55
|
"communication_channels.profile.actions.pollNow": "Sondear ahora",
|
|
55
56
|
"communication_channels.profile.actions.pollNowFailed": "No se pudo iniciar el sondeo",
|
|
57
|
+
"communication_channels.profile.actions.pollNowPushDriven": "Este canal funciona con push — los mensajes entrantes llegan por la conexión del proveedor, por lo que el sondeo no aplica.",
|
|
56
58
|
"communication_channels.profile.actions.pollNowSuccess": "Sondeo iniciado — los nuevos mensajes aparecerán en las líneas de tiempo de las personas vinculadas en unos segundos.",
|
|
57
59
|
"communication_channels.profile.actions.retryPoll": "Reintentar",
|
|
58
60
|
"communication_channels.profile.actions.setPrimary": "Establecer como principal",
|
|
@@ -64,6 +66,7 @@
|
|
|
64
66
|
"communication_channels.profile.columns.lastPolled": "Última sincronización",
|
|
65
67
|
"communication_channels.profile.columns.pollNow": "Sincronizar",
|
|
66
68
|
"communication_channels.profile.columns.primary": "Principal",
|
|
69
|
+
"communication_channels.profile.columns.push": "Push",
|
|
67
70
|
"communication_channels.profile.connect.blocked": "Conexión bloqueada por la validación",
|
|
68
71
|
"communication_channels.profile.connect.cancel": "Cancelar",
|
|
69
72
|
"communication_channels.profile.connect.connected": "Canal conectado.",
|
|
@@ -126,6 +129,9 @@
|
|
|
126
129
|
"communication_channels.push.status.active": "Push activo",
|
|
127
130
|
"communication_channels.push.status.failed": "Push fallido — usando sondeo",
|
|
128
131
|
"communication_channels.push.status.inactive": "Solo sondeo",
|
|
132
|
+
"communication_channels.push.status.notRegistered": "Push no registrado",
|
|
133
|
+
"communication_channels.push.status.pushDriven": "Basado en push",
|
|
134
|
+
"communication_channels.push.status.pushDrivenFailed": "Error en la conexión push",
|
|
129
135
|
"communication_channels.reaction.bar.aria": "Reacciones",
|
|
130
136
|
"communication_channels.reaction.cannotRemoveExternal": "Las reacciones de participantes externos solo se pueden eliminar en la aplicación del proveedor.",
|
|
131
137
|
"communication_channels.reaction.toggleAria": "Alternar reacción {emoji}",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"communication_channels.emptyState": "아직 공유 채널이 없습니다. 이 페이지에는 공유되는 테넌트 전체 채널이 나열됩니다. 개인 이메일 메일함은 비공개이며, 프로필 → 통신 채널에서 연결하고 관리하세요.",
|
|
24
24
|
"communication_channels.errors.loadDetail": "채널을 불러오지 못했습니다",
|
|
25
25
|
"communication_channels.errors.loadList": "채널을 불러오지 못했습니다",
|
|
26
|
+
"communication_channels.errors.pollNowPushDriven": "이 채널은 푸시 기반입니다 — 폴링은 적용되지 않습니다. 수신 메시지는 공급자의 푸시 연결로 전달됩니다.",
|
|
26
27
|
"communication_channels.errors.reactionFailed": "반응 처리에 실패했습니다",
|
|
27
28
|
"communication_channels.errors.undoBlockedPrimaryConflict": "다른 채널이 현재 이 사용자의 기본 채널로 설정되어 있습니다. 연결이 끊어진 채널을 기본으로 복원하기 전에 해당 채널을 기본이 아닌 채널로 설정하세요.",
|
|
28
29
|
"communication_channels.infoPanel.aria": "채널 정보",
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
"communication_channels.profile.actions.importHistory": "기록 가져오기",
|
|
54
55
|
"communication_channels.profile.actions.pollNow": "지금 폴링",
|
|
55
56
|
"communication_channels.profile.actions.pollNowFailed": "폴링 실행에 실패했습니다",
|
|
57
|
+
"communication_channels.profile.actions.pollNowPushDriven": "이 채널은 푸시 기반입니다 — 수신 메시지가 공급자 연결로 전달되므로 폴링은 적용되지 않습니다.",
|
|
56
58
|
"communication_channels.profile.actions.pollNowSuccess": "폴링이 실행되었습니다 — 몇 초 후 연결된 사람 타임라인에 새 메시지가 표시됩니다.",
|
|
57
59
|
"communication_channels.profile.actions.retryPoll": "재시도",
|
|
58
60
|
"communication_channels.profile.actions.setPrimary": "기본으로 설정",
|
|
@@ -64,6 +66,7 @@
|
|
|
64
66
|
"communication_channels.profile.columns.lastPolled": "마지막 동기화",
|
|
65
67
|
"communication_channels.profile.columns.pollNow": "동기화",
|
|
66
68
|
"communication_channels.profile.columns.primary": "기본",
|
|
69
|
+
"communication_channels.profile.columns.push": "푸시",
|
|
67
70
|
"communication_channels.profile.connect.blocked": "유효성 검사로 인해 연결이 차단되었습니다",
|
|
68
71
|
"communication_channels.profile.connect.cancel": "취소",
|
|
69
72
|
"communication_channels.profile.connect.connected": "채널이 연결되었습니다.",
|
|
@@ -126,6 +129,9 @@
|
|
|
126
129
|
"communication_channels.push.status.active": "푸시 활성",
|
|
127
130
|
"communication_channels.push.status.failed": "푸시 실패 — 폴링 사용 중",
|
|
128
131
|
"communication_channels.push.status.inactive": "폴링만 사용",
|
|
132
|
+
"communication_channels.push.status.notRegistered": "푸시 미등록",
|
|
133
|
+
"communication_channels.push.status.pushDriven": "푸시 기반",
|
|
134
|
+
"communication_channels.push.status.pushDrivenFailed": "푸시 연결 실패",
|
|
129
135
|
"communication_channels.reaction.bar.aria": "반응",
|
|
130
136
|
"communication_channels.reaction.cannotRemoveExternal": "외부 참여자의 반응은 제공업체 앱에서만 제거할 수 있습니다.",
|
|
131
137
|
"communication_channels.reaction.toggleAria": "{emoji} 반응 전환",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"communication_channels.emptyState": "Brak kanałów współdzielonych. Ta strona pokazuje współdzielone kanały obejmujące całą organizację. Twoja osobista skrzynka e-mail jest prywatna — połącz ją i zarządzaj nią w Profil → Kanały komunikacji.",
|
|
24
24
|
"communication_channels.errors.loadDetail": "Nie udało się załadować kanału",
|
|
25
25
|
"communication_channels.errors.loadList": "Nie udało się załadować kanałów",
|
|
26
|
+
"communication_channels.errors.pollNowPushDriven": "Kanał działa w trybie push — odpytywanie go nie dotyczy. Wiadomości przychodzące docierają połączeniem push dostawcy.",
|
|
26
27
|
"communication_channels.errors.reactionFailed": "Reakcja nie powiodła się",
|
|
27
28
|
"communication_channels.errors.undoBlockedPrimaryConflict": "Inny kanał jest teraz głównym dla tego użytkownika. Ustaw go jako niegłówny, zanim przywrócisz rozłączony kanał jako główny.",
|
|
28
29
|
"communication_channels.infoPanel.aria": "Informacje o kanale",
|
|
@@ -53,6 +54,7 @@
|
|
|
53
54
|
"communication_channels.profile.actions.importHistory": "Importuj historię",
|
|
54
55
|
"communication_channels.profile.actions.pollNow": "Pobierz teraz",
|
|
55
56
|
"communication_channels.profile.actions.pollNowFailed": "Nie udało się uruchomić pobierania",
|
|
57
|
+
"communication_channels.profile.actions.pollNowPushDriven": "Ten kanał działa w trybie push — wiadomości przychodzące docierają połączeniem dostawcy, więc odpytywanie go nie dotyczy.",
|
|
56
58
|
"communication_channels.profile.actions.pollNowSuccess": "Pobieranie uruchomione — nowe wiadomości pojawią się na osiach czasu osób w ciągu kilku sekund.",
|
|
57
59
|
"communication_channels.profile.actions.retryPoll": "Ponów",
|
|
58
60
|
"communication_channels.profile.actions.setPrimary": "Ustaw jako główny",
|
|
@@ -64,6 +66,7 @@
|
|
|
64
66
|
"communication_channels.profile.columns.lastPolled": "Ostatnia synchronizacja",
|
|
65
67
|
"communication_channels.profile.columns.pollNow": "Synchronizacja",
|
|
66
68
|
"communication_channels.profile.columns.primary": "Główny",
|
|
69
|
+
"communication_channels.profile.columns.push": "Push",
|
|
67
70
|
"communication_channels.profile.connect.blocked": "Połączenie zablokowane przez walidację",
|
|
68
71
|
"communication_channels.profile.connect.cancel": "Anuluj",
|
|
69
72
|
"communication_channels.profile.connect.connected": "Kanał połączony.",
|
|
@@ -126,6 +129,9 @@
|
|
|
126
129
|
"communication_channels.push.status.active": "Push aktywny",
|
|
127
130
|
"communication_channels.push.status.failed": "Push nieudany — używam odpytywania",
|
|
128
131
|
"communication_channels.push.status.inactive": "Tylko odpytywanie",
|
|
132
|
+
"communication_channels.push.status.notRegistered": "Push niezarejestrowany",
|
|
133
|
+
"communication_channels.push.status.pushDriven": "Sterowany push",
|
|
134
|
+
"communication_channels.push.status.pushDrivenFailed": "Połączenie push nie działa",
|
|
129
135
|
"communication_channels.reaction.bar.aria": "Reakcje",
|
|
130
136
|
"communication_channels.reaction.cannotRemoveExternal": "Reakcje uczestników zewnętrznych można usunąć tylko w aplikacji dostawcy.",
|
|
131
137
|
"communication_channels.reaction.toggleAria": "Przełącz reakcję {emoji}",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for "does the hub poll this channel?".
|
|
3
|
+
*
|
|
4
|
+
* `ChannelCapabilities.realtimePush` is optional and defaults to `true` for
|
|
5
|
+
* back-compat (chat providers predating the flag omit it), so only an explicit
|
|
6
|
+
* `false` opts a channel into hub-managed polling. The poll worker, the manual
|
|
7
|
+
* `poll-now` route and the profile grid all derive their behaviour from this
|
|
8
|
+
* predicate, so the UI can never label a channel the opposite of what the worker
|
|
9
|
+
* actually does (#4980).
|
|
10
|
+
*/
|
|
11
|
+
export function isHubPolledChannel(capabilities: unknown): boolean {
|
|
12
|
+
if (!capabilities || typeof capabilities !== 'object' || Array.isArray(capabilities)) return false
|
|
13
|
+
return (capabilities as { realtimePush?: unknown }).realtimePush === false
|
|
14
|
+
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
} from '../commands/ingest-inbound-message'
|
|
10
10
|
import { COMMUNICATION_CHANNELS_QUEUES, getCommunicationChannelsQueue } from '../lib/queue'
|
|
11
11
|
import { preservePushState } from '../lib/push-state'
|
|
12
|
+
import { isHubPolledChannel } from '../lib/polling-eligibility'
|
|
12
13
|
import { writeIngestDeadLetter } from '../lib/dead-letter'
|
|
13
14
|
import { classifyOutboundError, computeBackoffMs, isReauthError } from '../lib/error-classification'
|
|
14
15
|
import { refreshCredentialsIfNeeded } from '../lib/credential-refresh'
|
|
@@ -123,9 +124,8 @@ export default async function handle(
|
|
|
123
124
|
logger.warn('no adapter for provider', { providerKey: channel.providerKey, channelId })
|
|
124
125
|
return
|
|
125
126
|
}
|
|
126
|
-
// Adapter opted out of polling — webhook providers.
|
|
127
|
-
|
|
128
|
-
if (capabilities?.realtimePush !== false) {
|
|
127
|
+
// Adapter opted out of polling — webhook and gateway providers.
|
|
128
|
+
if (!isHubPolledChannel(channel.capabilities)) {
|
|
129
129
|
// realtimePush is `true` (default for back-compat) — don't poll push providers.
|
|
130
130
|
return
|
|
131
131
|
}
|
|
@@ -158,12 +158,12 @@ function printCacheHelp() {
|
|
|
158
158
|
console.log(' yarn mercato configs cache purge --key <key1,key2> [--tenant <id> | --global | --all-tenants] [--dry-run] [--json]')
|
|
159
159
|
console.log(' yarn mercato configs cache purge --id <token1,token2> [--tenant <id> | --global | --all-tenants] [--dry-run] [--json]')
|
|
160
160
|
console.log(' yarn mercato configs cache purge --pattern <glob> [--tenant <id> | --global | --all-tenants] [--dry-run] [--json]')
|
|
161
|
-
console.log(' yarn mercato configs cache structural [--tenant <id> | --global | --all-tenants] [--dry-run] [--json]')
|
|
161
|
+
console.log(' yarn mercato configs cache structural [--tenant <id> | --global | --all-tenants] [--touch-generated] [--dry-run] [--json]')
|
|
162
162
|
console.log('')
|
|
163
163
|
console.log('ℹ️ Notes:')
|
|
164
164
|
console.log(' `stats` mirrors the cache admin page segment overview for CRUD/widget caches.')
|
|
165
165
|
console.log(' `purge --id` removes every key whose name contains the provided token (for example a user id or entity id).')
|
|
166
|
-
console.log(' `structural` targets navigation/sidebar caches
|
|
166
|
+
console.log(' `structural` targets navigation/sidebar caches. Add `--touch-generated` only for explicit stale-compiler recovery.')
|
|
167
167
|
console.log(' When no scope flag is supplied, this command uses the global cache scope only.')
|
|
168
168
|
}
|
|
169
169
|
|
|
@@ -278,14 +278,16 @@ async function runStructuralCachePurge(args: ParsedArgs) {
|
|
|
278
278
|
if (json) {
|
|
279
279
|
console.log(JSON.stringify(structuralResults, null, 2))
|
|
280
280
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
281
|
+
if (flagEnabled(args, 'touch-generated', 'touchGenerated')) {
|
|
282
|
+
const quiet = flagEnabled(args, 'quiet')
|
|
283
|
+
try {
|
|
284
|
+
touchGeneratedBarrels({ quiet: quiet || json })
|
|
285
|
+
} catch (err) {
|
|
286
|
+
if (!quiet && !json) {
|
|
287
|
+
console.warn(
|
|
288
|
+
`[structural] failed to touch generated barrels: ${(err as Error).message ?? err}`,
|
|
289
|
+
)
|
|
290
|
+
}
|
|
289
291
|
}
|
|
290
292
|
}
|
|
291
293
|
}
|
|
@@ -43,13 +43,16 @@ export function touchGeneratedBarrels(
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
const touched: string[] = []
|
|
46
|
+
const touchedAt = new Date()
|
|
46
47
|
const entries = fs.readdirSync(generatedDir, { withFileTypes: true })
|
|
47
48
|
for (const entry of entries) {
|
|
48
49
|
if (!entry.isFile()) continue
|
|
49
50
|
if (!TOUCHABLE_PATTERN.test(entry.name)) continue
|
|
50
51
|
const filePath = path.join(generatedDir, entry.name)
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
// Advance mtime without rewriting the bytes: rewriting truncates then refills
|
|
53
|
+
// multi-megabyte registries, and a concurrent Turbopack compile reading one
|
|
54
|
+
// mid-write sees a partial file. Matches `packages/cli/src/lib/post-generate-invalidation.ts`.
|
|
55
|
+
fs.utimesSync(filePath, touchedAt, touchedAt)
|
|
53
56
|
touched.push(filePath)
|
|
54
57
|
}
|
|
55
58
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MessageObjectTypeDefinition } from '@open-mercato/shared/modules/messages/types'
|
|
2
|
-
import { MessageObjectDetail, MessageObjectPreview } from '@open-mercato/ui'
|
|
2
|
+
import { MessageObjectDetail, MessageObjectPreview } from '@open-mercato/ui/backend/messages'
|
|
3
3
|
|
|
4
4
|
const objectMessageTypes = ['default', 'messages.defaultWithObjects']
|
|
5
5
|
|