@k2b/cloud 0.6.0 → 0.7.0
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/README.md +2 -2
- package/package.json +19 -9
- package/scripts/browser-performance.ts +14 -0
- package/scripts/build-pdf-renderer.ts +39 -0
- package/scripts/build.ts +15 -35
- package/scripts/preload.ts +9 -12
- package/scripts/sync-recovery-smoke.ts +2 -1
- package/src/_internal/app-presentation.ts +14 -2
- package/src/_internal/capabilities.ts +108 -7
- package/src/_internal/capability-streams.ts +81 -0
- package/src/_internal/define-app.ts +62 -36
- package/src/_internal/heartbeat.ts +4 -0
- package/src/_internal/help-catalog.ts +27 -172
- package/src/_internal/help.ts +13 -76
- package/src/_internal/page-responses.ts +2 -0
- package/src/_internal/postgres-application-name.ts +30 -0
- package/src/_internal/process-identity.ts +15 -0
- package/src/_internal/process-sync.ts +1 -1
- package/src/_internal/registry-validation.ts +29 -25
- package/src/_internal/registry.ts +2 -53
- package/src/_internal/runtime-context.ts +5 -16
- package/src/_internal/server-timing.ts +13 -0
- package/src/_internal/status-preserving-ssr.ts +18 -6
- package/src/_internal/web-vitals-asset.ts +10 -0
- package/src/access/PermissionEditor.tsx +14 -153
- package/src/access/PrincipalPicker.tsx +104 -0
- package/src/access/ui.ts +2 -0
- package/src/ai/admin.ts +14 -13
- package/src/ai/assistant-models.ts +12 -1
- package/src/ai/audio-format.ts +34 -0
- package/src/ai/audio-tool.ts +90 -0
- package/src/ai/browser-code-contracts.ts +79 -0
- package/src/ai/browser.ts +2 -0
- package/src/ai/capabilities.ts +55 -36
- package/src/ai/capability-execution.ts +47 -12
- package/src/ai/chat/blocks.tsx +157 -95
- package/src/ai/chat/capability-result.ts +15 -0
- package/src/ai/chat/capability-table.tsx +2 -10
- package/src/ai/chat/composer-adapter.ts +48 -3
- package/src/ai/chat/message-actions.tsx +8 -7
- package/src/ai/chat/message-utils.ts +12 -1
- package/src/ai/chat/messages.ts +2 -0
- package/src/ai/chat/presentation.tsx +19 -48
- package/src/ai/chat/primitives.tsx +43 -1
- package/src/ai/chat/tool-disclosure.tsx +17 -2
- package/src/ai/chat/tool-groups.ts +52 -0
- package/src/ai/chat-quotas.ts +25 -0
- package/src/ai/chat-task-contracts.ts +9 -1
- package/src/ai/chat-tasks.ts +276 -43
- package/src/ai/client/controller.ts +110 -33
- package/src/ai/client/file-source.ts +12 -6
- package/src/ai/client/live-connection.ts +1 -0
- package/src/ai/client/projection.ts +22 -11
- package/src/ai/client/transport.ts +4 -0
- package/src/ai/code-capability-routes.ts +110 -0
- package/src/ai/code-capability-transport.ts +38 -0
- package/src/ai/code-mode-skill.ts +114 -0
- package/src/ai/code-runtime-tools.ts +166 -0
- package/src/ai/code-source-contracts.ts +202 -0
- package/src/ai/code-source-tools.ts +145 -0
- package/src/ai/compaction.ts +27 -6
- package/src/ai/data-analysis-skill.ts +10 -0
- package/src/ai/default-tools.ts +25 -2
- package/src/ai/dictation-runtime.ts +171 -0
- package/src/ai/dictations.ts +188 -0
- package/src/ai/draft-content.ts +34 -0
- package/src/ai/enrich.ts +6 -0
- package/src/ai/executor.ts +191 -31
- package/src/ai/file-content-version.ts +22 -0
- package/src/ai/file-context.ts +2 -1
- package/src/ai/file-media-type.ts +12 -0
- package/src/ai/file-reference-contracts.ts +15 -0
- package/src/ai/file-tools.ts +2 -1
- package/src/ai/files-store.ts +174 -71
- package/src/ai/fixtures/vision-encrypted.pdf +0 -0
- package/src/ai/grids-skill.ts +40 -10
- package/src/ai/http.ts +15 -8
- package/src/ai/index.ts +26 -2
- package/src/ai/inference-calls.ts +198 -0
- package/src/ai/live-events.ts +5 -1
- package/src/ai/memory-learning.ts +60 -28
- package/src/ai/message-queue.ts +167 -0
- package/src/ai/migrate.ts +187 -11
- package/src/ai/model-pricing.ts +43 -0
- package/src/ai/pdf-render-worker.ts +74 -0
- package/src/ai/pdf-render.fixture.ts +28 -0
- package/src/ai/pdf-render.ts +77 -0
- package/src/ai/projects-routes.ts +3 -3
- package/src/ai/projects.ts +20 -6
- package/src/ai/protocol.ts +3 -0
- package/src/ai/provider.ts +1 -8
- package/src/ai/quota-provider.ts +191 -0
- package/src/ai/quota-report.ts +152 -0
- package/src/ai/quotas-migrate.ts +45 -0
- package/src/ai/quotas.ts +168 -0
- package/src/ai/routes.ts +215 -11
- package/src/ai/run-timeout.ts +10 -0
- package/src/ai/runtime-tools.ts +3 -0
- package/src/ai/runtime.ts +55 -16
- package/src/ai/settings.ts +36 -8
- package/src/ai/short-id.ts +2 -2
- package/src/ai/skill-catalog.ts +0 -10
- package/src/ai/skill-search.ts +47 -0
- package/src/ai/skill-seeds.ts +105 -20
- package/src/ai/skill-tool.ts +16 -5
- package/src/ai/skills-routes.ts +32 -4
- package/src/ai/skills.ts +324 -155
- package/src/ai/store.ts +373 -191
- package/src/ai/stream.ts +5 -0
- package/src/ai/structured-runs.ts +4 -3
- package/src/ai/structured.ts +8 -1
- package/src/ai/system-prompt.ts +12 -2
- package/src/ai/timeline.ts +2 -5
- package/src/ai/todo-contracts.ts +20 -0
- package/src/ai/todo-tool.ts +16 -0
- package/src/ai/tool-audit.ts +30 -40
- package/src/ai/tools.ts +6 -0
- package/src/ai/transcription.ts +202 -0
- package/src/ai/turn-timing.ts +114 -0
- package/src/ai/types.ts +58 -10
- package/src/ai/ui.tsx +2 -0
- package/src/ai/usage.ts +118 -118
- package/src/ai/vision-tool.ts +39 -10
- package/src/api/admin-ai-projects.ts +2 -2
- package/src/api/admin-ai-quotas.ts +74 -0
- package/src/api/admin-ai-skills.ts +48 -5
- package/src/api/admin-ai-usage.ts +2 -2
- package/src/api/admin-core-settings.ts +25 -0
- package/src/api/admin-identity.ts +91 -51
- package/src/api/admin-rail.ts +58 -0
- package/src/api/announcements.ts +14 -0
- package/src/api/capabilities.ts +292 -43
- package/src/api/capability-streams.ts +262 -0
- package/src/api/help.ts +8 -44
- package/src/api/index.ts +6 -0
- package/src/api/mcp.ts +44 -26
- package/src/api/me-web-vitals.ts +35 -0
- package/src/api/me.ts +2 -0
- package/src/api/search/schemas.ts +26 -15
- package/src/api/search.bench.ts +1 -1
- package/src/api/search.ts +54 -17
- package/src/browser/CloudResourceSearch.tsx +640 -702
- package/src/browser/app-approval-vault.ts +19 -7
- package/src/browser/command-bridge.ts +109 -0
- package/src/browser/command-shortcuts.ts +76 -0
- package/src/browser/commands.ts +66 -0
- package/src/browser/navigation-search.ts +48 -0
- package/src/browser/notifications.ts +1 -1
- package/src/browser/resource-picker.tsx +5 -18
- package/src/browser/resource-search-dialog.ts +9 -0
- package/src/browser/resource-search-input.ts +46 -0
- package/src/browser/resource-search-messages.ts +76 -40
- package/src/browser/resource-search.ts +7 -0
- package/src/browser/search-bridge.ts +102 -0
- package/src/browser/search-commands.ts +59 -0
- package/src/browser/search.ts +13 -0
- package/src/browser/spotlight-position.ts +208 -0
- package/src/browser/testing.ts +5 -0
- package/src/browser/web-vitals.ts +25 -0
- package/src/capabilities/claims.ts +179 -0
- package/src/capabilities/client.ts +34 -0
- package/src/capabilities/command-link.ts +16 -0
- package/src/capabilities/executions.ts +298 -0
- package/src/capabilities/migrate.ts +81 -0
- package/src/capabilities/server.ts +114 -34
- package/src/capabilities/stream-body.ts +16 -0
- package/src/capabilities/streams.ts +28 -0
- package/src/cli/admin/ai-quotas.ts +161 -0
- package/src/cli/admin/ai-skills.ts +64 -0
- package/src/cli/admin/ai-usage.ts +53 -30
- package/src/cli/admin/app-credentials.ts +79 -0
- package/src/cli/admin/data.ts +17 -0
- package/src/cli/admin/index.ts +9 -0
- package/src/cli/capabilities.ts +29 -0
- package/src/cli/capability-streams.ts +25 -0
- package/src/cli/index.ts +3 -2
- package/src/config/env.ts +11 -8
- package/src/contracts/app.ts +23 -3
- package/src/contracts/capabilities.ts +68 -5
- package/src/contracts/capability-streams.ts +46 -0
- package/src/contracts/commands.ts +49 -0
- package/src/contracts/index.ts +6 -1
- package/src/contracts/rail-admin.ts +38 -0
- package/src/contracts/rail-preferences.ts +16 -0
- package/src/contracts/registry.ts +3 -27
- package/src/contracts/settings-types.ts +1 -1
- package/src/contracts/shared.ts +9 -5
- package/src/contracts/web-vitals.ts +27 -0
- package/src/index.ts +0 -4
- package/src/server/index.ts +3 -0
- package/src/server/middleware/auth.ts +3 -2
- package/src/server/middleware/route-template.ts +9 -3
- package/src/server/middleware/runtime.ts +2 -1
- package/src/server/middleware/settings.ts +17 -11
- package/src/server/services/access-revision.ts +10 -0
- package/src/server/services/access.ts +1 -1
- package/src/server/services/index.ts +1 -0
- package/src/services/account-category-policy.ts +9 -0
- package/src/services/account-lifecycle/audit.ts +1 -1
- package/src/services/accounts/app.ts +12 -2
- package/src/services/accounts/email-write.ts +23 -0
- package/src/services/accounts/groups.ts +15 -4
- package/src/services/accounts/identities.ts +249 -0
- package/src/services/accounts/identity-reconciliation.ts +87 -0
- package/src/services/accounts/ipa-data.ts +2 -2
- package/src/services/accounts/local-groups.ts +2 -2
- package/src/services/accounts/posix.ts +45 -29
- package/src/services/accounts/users.ts +2 -2
- package/src/services/announcements/index.ts +69 -8
- package/src/services/audit/index.ts +1 -1
- package/src/services/cache-fill.ts +35 -0
- package/src/services/help/index.ts +109 -0
- package/src/services/help/maintenance.ts +25 -0
- package/src/services/help/store.ts +180 -0
- package/src/services/help/types.ts +46 -0
- package/src/services/identity/invocation-actor.ts +2 -2
- package/src/services/identity/key-config.ts +2 -1
- package/src/services/identity/key-ring.ts +1 -1
- package/src/services/index.ts +15 -0
- package/src/services/ipa/users.ts +268 -221
- package/src/services/legal-consent.ts +1 -1
- package/src/services/logging/index.ts +15 -13
- package/src/services/logging/trace.ts +2 -1
- package/src/services/logging/web-vitals.ts +81 -0
- package/src/services/mandates/index.ts +18 -4
- package/src/services/mandates/policy.ts +65 -2
- package/src/services/notifications/batches.ts +124 -57
- package/src/services/notifications/browser.ts +12 -0
- package/src/services/notifications/index.ts +0 -2
- package/src/services/notifications/platform.ts +0 -4
- package/src/services/oauth-tokens.ts +3 -3
- package/src/services/pdf/gotenberg.ts +107 -84
- package/src/services/pdf/index.ts +3 -0
- package/src/services/providers/local/users.ts +25 -16
- package/src/services/public-http.ts +101 -0
- package/src/services/rail-shortcuts.ts +95 -0
- package/src/services/rail-snapshot.ts +53 -0
- package/src/services/request-cache-redis.ts +28 -0
- package/src/services/session/index.ts +15 -2
- package/src/services/session/user.ts +62 -37
- package/src/services/settings/core-settings.ts +21 -28
- package/src/services/settings/defaults.ts +5 -0
- package/src/services/settings/store.ts +58 -84
- package/src/shared/ai-costs.ts +42 -0
- package/src/shared/ai-platform-prompt.ts +1 -1
- package/src/shared/ai-quotas.ts +136 -0
- package/src/shared/ai-usage.ts +6 -16
- package/src/shared/app-presentation.ts +21 -15
- package/src/shared/capability-messages.ts +14 -0
- package/src/shared/help.ts +6 -42
- package/src/shared/index.ts +6 -0
- package/src/ssr/AdminLayout.tsx +7 -5
- package/src/ssr/AdminSidebar.tsx +29 -15
- package/src/ssr/AppLaunchpad.island.tsx +24 -137
- package/src/ssr/AppLaunchpadPanel.tsx +181 -0
- package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
- package/src/ssr/GlobalSearchDialog.tsx +167 -20
- package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
- package/src/ssr/Layout.tsx +17 -6
- package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
- package/src/ssr/LayoutFooter.tsx +1 -1
- package/src/ssr/LayoutHeader.tsx +46 -14
- package/src/ssr/LayoutHelp.tsx +23 -22
- package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
- package/src/ssr/LayoutRail.tsx +17 -9
- package/src/ssr/MobileNavigation.tsx +71 -0
- package/src/ssr/MobileProfileActions.tsx +49 -0
- package/src/ssr/ProfilePreferences.island.tsx +3 -36
- package/src/ssr/RailApps.island.tsx +2 -2
- package/src/ssr/RailEditor.tsx +17 -2
- package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
- package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
- package/src/ssr/admin-navigation.ts +7 -1
- package/src/ssr/help.ts +29 -0
- package/src/ssr/islands/SearchBar.tsx +67 -0
- package/src/ssr/islands/index.ts +3 -1
- package/src/ssr/layout-context.ts +2 -2
- package/src/ssr/layout-help-search.ts +15 -0
- package/src/ssr/mobile-menu-history.ts +70 -0
- package/src/ssr/platform-messages.ts +16 -8
- package/src/ssr/profile-actions.ts +42 -0
- package/src/ssr/rail-context.ts +8 -4
- package/src/ssr/rail-messages.ts +2 -0
- package/src/ssr/rail-navigation.ts +3 -3
- package/src/ssr/workspace-navigation.ts +75 -0
- package/src/styles/effects.css +6 -6
- package/src/styles/global.css +54 -0
- package/src/styles/input.css +43 -41
- package/src/styles/resource-search.css +505 -0
- package/src/styles/utilities-feedback.css +13 -0
- package/src/styles/utilities-layout.css +1 -5
- package/src/styles/utilities-navigation.css +3 -39
- package/src/types/ambient.d.ts +5 -0
- package/src/workflows/ai/runtime.ts +5 -1
- package/src/workflows/ai/store.ts +2 -0
- package/src/workflows/ai-actions.ts +16 -13
- package/src/workflows/definition.ts +10 -5
- package/src/workflows/store/actions.ts +44 -20
- package/src/workflows/store/index.ts +2 -0
- package/src/workflows/store/runs.ts +36 -0
- package/src/workflows/store/worker-pool.ts +55 -0
- package/src/workflows/store/worker-runtime.ts +90 -0
- package/src/ai/kit-skill.ts +0 -22
- package/src/ai/usage-migrate.ts +0 -81
- package/src/browser/notification-target.ts +0 -27
- package/src/contracts/notification-live.ts +0 -66
- package/src/services/notifications/live.ts +0 -50
- package/src/ssr/BrowserNotifications.island.tsx +0 -135
- package/src/ssr/islands/SearchBar.island.tsx +0 -80
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { isSafeNotificationTargetHref } from "./notification-types";
|
|
3
|
-
|
|
4
|
-
export const NOTIFICATION_LIVE_WS_TYPE = {
|
|
5
|
-
subscribe: "notifications.live.subscribe",
|
|
6
|
-
ready: "notifications.live.ready",
|
|
7
|
-
event: "notifications.live.event",
|
|
8
|
-
revoked: "notifications.live.revoked",
|
|
9
|
-
error: "notifications.live.error",
|
|
10
|
-
} as const;
|
|
11
|
-
|
|
12
|
-
export const NotificationStreamCursorSchema = z
|
|
13
|
-
.string()
|
|
14
|
-
.regex(/^s6t\.[A-Za-z0-9_-]+\.\d+$/)
|
|
15
|
-
.max(80);
|
|
16
|
-
|
|
17
|
-
const NotificationTargetHrefSchema = z.custom<`/${string}`>(
|
|
18
|
-
(value) => typeof value === "string" && isSafeNotificationTargetHref(value),
|
|
19
|
-
"Notification target must be a canonical same-origin path",
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
export const NotificationLiveEventSchema = z.object({
|
|
23
|
-
type: z.literal("cloud-notification"),
|
|
24
|
-
eventId: z.string().min(1),
|
|
25
|
-
title: z.string(),
|
|
26
|
-
targetHref: NotificationTargetHrefSchema.optional(),
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
export type NotificationLiveEvent = z.infer<typeof NotificationLiveEventSchema>;
|
|
30
|
-
|
|
31
|
-
export const NotificationLiveClientMessageSchema = z.object({
|
|
32
|
-
type: z.literal(NOTIFICATION_LIVE_WS_TYPE.subscribe),
|
|
33
|
-
payload: z.object({ fromCursor: NotificationStreamCursorSchema.nullable() }),
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
export type NotificationLiveClientMessage = z.infer<typeof NotificationLiveClientMessageSchema>;
|
|
37
|
-
|
|
38
|
-
export const NotificationLiveServerMessageSchema = z.discriminatedUnion("type", [
|
|
39
|
-
z.object({
|
|
40
|
-
type: z.literal(NOTIFICATION_LIVE_WS_TYPE.ready),
|
|
41
|
-
payload: z.object({ cursor: NotificationStreamCursorSchema }),
|
|
42
|
-
}),
|
|
43
|
-
z.object({
|
|
44
|
-
type: z.literal(NOTIFICATION_LIVE_WS_TYPE.event),
|
|
45
|
-
payload: z.object({ cursor: NotificationStreamCursorSchema, event: NotificationLiveEventSchema }),
|
|
46
|
-
}),
|
|
47
|
-
z.object({
|
|
48
|
-
type: z.literal(NOTIFICATION_LIVE_WS_TYPE.revoked),
|
|
49
|
-
payload: z.object({ code: z.string().min(1), message: z.string().min(1) }),
|
|
50
|
-
}),
|
|
51
|
-
z.object({
|
|
52
|
-
type: z.literal(NOTIFICATION_LIVE_WS_TYPE.error),
|
|
53
|
-
payload: z.object({ code: z.string().min(1), message: z.string().min(1) }),
|
|
54
|
-
}),
|
|
55
|
-
]);
|
|
56
|
-
|
|
57
|
-
export type NotificationLiveServerMessage = z.infer<typeof NotificationLiveServerMessageSchema>;
|
|
58
|
-
|
|
59
|
-
export const parseNotificationLiveServerMessage = (raw: string): NotificationLiveServerMessage | null => {
|
|
60
|
-
try {
|
|
61
|
-
const parsed = NotificationLiveServerMessageSchema.safeParse(JSON.parse(raw));
|
|
62
|
-
return parsed.success ? parsed.data : null;
|
|
63
|
-
} catch {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
};
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { lazySync } from "../../_internal/process-sync";
|
|
2
|
-
import type { NotificationLiveEvent } from "../../contracts/notification-live";
|
|
3
|
-
import type { NotificationPresentation } from "../../contracts/notification-types";
|
|
4
|
-
import { logger } from "../logging";
|
|
5
|
-
import { latestTopicCursor } from "../topic-cursor";
|
|
6
|
-
|
|
7
|
-
const LIVE_TOPIC_ID = "cloud:notifications:live";
|
|
8
|
-
const LIVE_RETENTION_MS = 60 * 60 * 1_000;
|
|
9
|
-
|
|
10
|
-
const log = logger("notifications:live");
|
|
11
|
-
|
|
12
|
-
const liveTopic = lazySync((sync) =>
|
|
13
|
-
sync.topic<NotificationLiveEvent>({
|
|
14
|
-
id: LIVE_TOPIC_ID,
|
|
15
|
-
owner: "core",
|
|
16
|
-
// One hour of foreground alerts fits the shared event-log storage budget.
|
|
17
|
-
retention: { maxAgeMs: LIVE_RETENTION_MS, maxBytes: 64 * 1024 * 1024 },
|
|
18
|
-
maxPayloadBytes: 9_000,
|
|
19
|
-
}),
|
|
20
|
-
);
|
|
21
|
-
|
|
22
|
-
const publish = async (input: { userId: string; eventId: string; presentation: NotificationPresentation }): Promise<void> => {
|
|
23
|
-
const event: NotificationLiveEvent = {
|
|
24
|
-
type: "cloud-notification",
|
|
25
|
-
eventId: input.eventId,
|
|
26
|
-
title: input.presentation.title,
|
|
27
|
-
...(input.presentation.targetHref ? { targetHref: input.presentation.targetHref } : {}),
|
|
28
|
-
};
|
|
29
|
-
try {
|
|
30
|
-
await liveTopic().publish({
|
|
31
|
-
tenantId: input.userId,
|
|
32
|
-
orderingKey: input.userId,
|
|
33
|
-
idempotencyKey: `event:${input.eventId}`,
|
|
34
|
-
data: event,
|
|
35
|
-
});
|
|
36
|
-
} catch (error) {
|
|
37
|
-
log.warn("Failed to publish foreground notification", {
|
|
38
|
-
eventId: input.eventId,
|
|
39
|
-
error: error instanceof Error ? error.message : "Foreground notification publish failed",
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export const notificationLive = {
|
|
45
|
-
publish,
|
|
46
|
-
latestCursor: (userId: string): Promise<string> => latestTopicCursor({ topic: liveTopic(), resourceId: LIVE_TOPIC_ID, tenantId: userId }),
|
|
47
|
-
emptyCursor: (): string => liveTopic().cursorAt(0),
|
|
48
|
-
events: (input: { userId: string; after?: string; signal?: AbortSignal }) =>
|
|
49
|
-
liveTopic().hub({ tenantId: input.userId }).subscribe({ after: input.after, signal: input.signal }),
|
|
50
|
-
} as const;
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { IconButton, useLocale } from "@k2b/ui";
|
|
2
|
-
import { createSignal, onCleanup, onMount, Show } from "solid-js";
|
|
3
|
-
import { createLiveWebSocket } from "../browser/live-websocket";
|
|
4
|
-
import { notificationTargetMatchesLocation } from "../browser/notification-target";
|
|
5
|
-
import { browserNotificationClient } from "../browser/notifications";
|
|
6
|
-
import {
|
|
7
|
-
NOTIFICATION_LIVE_WS_TYPE,
|
|
8
|
-
type NotificationLiveClientMessage,
|
|
9
|
-
type NotificationLiveEvent,
|
|
10
|
-
NotificationLiveEventSchema,
|
|
11
|
-
type NotificationLiveServerMessage,
|
|
12
|
-
NotificationStreamCursorSchema,
|
|
13
|
-
parseNotificationLiveServerMessage,
|
|
14
|
-
} from "../contracts/notification-live";
|
|
15
|
-
import { platformMessages } from "./platform-messages";
|
|
16
|
-
|
|
17
|
-
const cursorStorageKey = (userId: string): string => `cloud.notifications.live-cursor:${userId}`;
|
|
18
|
-
|
|
19
|
-
const readStoredCursor = (userId: string): string | null => {
|
|
20
|
-
try {
|
|
21
|
-
const cursor = sessionStorage.getItem(cursorStorageKey(userId));
|
|
22
|
-
return NotificationStreamCursorSchema.safeParse(cursor).success ? cursor : null;
|
|
23
|
-
} catch {
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const storeCursor = (userId: string, cursor: string) => {
|
|
29
|
-
try {
|
|
30
|
-
sessionStorage.setItem(cursorStorageKey(userId), cursor);
|
|
31
|
-
} catch {
|
|
32
|
-
// Private or restricted browsing may disable storage; in-memory resume still works.
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export default function BrowserNotifications(props: { userId: string }) {
|
|
37
|
-
const locale = useLocale();
|
|
38
|
-
const t = () => platformMessages.resolve([locale()]).t;
|
|
39
|
-
const [notification, setNotification] = createSignal<NotificationLiveEvent | null>(null);
|
|
40
|
-
const seen = new Set<string>();
|
|
41
|
-
|
|
42
|
-
onMount(() => {
|
|
43
|
-
const receive = (value: NotificationLiveEvent) => {
|
|
44
|
-
if (seen.has(value.eventId)) return true;
|
|
45
|
-
seen.add(value.eventId);
|
|
46
|
-
if (seen.size > 500) seen.delete(seen.values().next().value ?? "");
|
|
47
|
-
if (value.targetHref && notificationTargetMatchesLocation(value.targetHref, window.location.href)) return true;
|
|
48
|
-
setNotification(value);
|
|
49
|
-
return true;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
void browserNotificationClient.refreshExisting().catch((error) => {
|
|
53
|
-
console.warn("[notifications] Failed to refresh the browser endpoint", error);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const onMessage = (event: MessageEvent<unknown>) => {
|
|
57
|
-
const parsed = NotificationLiveEventSchema.safeParse(event.data);
|
|
58
|
-
if (!parsed.success) return;
|
|
59
|
-
receive(parsed.data);
|
|
60
|
-
event.ports[0]?.postMessage({ received: true });
|
|
61
|
-
};
|
|
62
|
-
navigator.serviceWorker?.addEventListener("message", onMessage);
|
|
63
|
-
|
|
64
|
-
const initialCursor = readStoredCursor(props.userId);
|
|
65
|
-
const connection = createLiveWebSocket<NotificationLiveServerMessage>({
|
|
66
|
-
url: "/api/me/notifications/ws",
|
|
67
|
-
initialCursor,
|
|
68
|
-
activity: "visible",
|
|
69
|
-
subscribe: (cursor) =>
|
|
70
|
-
({
|
|
71
|
-
type: NOTIFICATION_LIVE_WS_TYPE.subscribe,
|
|
72
|
-
payload: { fromCursor: cursor },
|
|
73
|
-
}) satisfies NotificationLiveClientMessage,
|
|
74
|
-
parse: parseNotificationLiveServerMessage,
|
|
75
|
-
onMessage: (message, controls) => {
|
|
76
|
-
if (message.type === NOTIFICATION_LIVE_WS_TYPE.ready) {
|
|
77
|
-
controls.markApplied(message.payload.cursor);
|
|
78
|
-
storeCursor(props.userId, message.payload.cursor);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
if (message.type === NOTIFICATION_LIVE_WS_TYPE.event) {
|
|
82
|
-
receive(message.payload.event);
|
|
83
|
-
controls.markApplied(message.payload.cursor);
|
|
84
|
-
storeCursor(props.userId, message.payload.cursor);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
if (message.type === NOTIFICATION_LIVE_WS_TYPE.revoked) {
|
|
88
|
-
controls.terminate({ code: message.payload.code, message: message.payload.message });
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
onFatal: (error) => {
|
|
92
|
-
if (error.code === "login_required" || error.code === "access_denied") window.location.reload();
|
|
93
|
-
else console.warn("[notifications] Foreground updates stopped", error);
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
connection.connect();
|
|
97
|
-
|
|
98
|
-
onCleanup(() => {
|
|
99
|
-
navigator.serviceWorker?.removeEventListener("message", onMessage);
|
|
100
|
-
connection.dispose();
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
return (
|
|
105
|
-
<Show when={notification()} keyed>
|
|
106
|
-
{(item) => (
|
|
107
|
-
<aside
|
|
108
|
-
class="paper fixed bottom-3 left-3 right-3 z-40 flex items-start gap-3 p-3 shadow-lg sm:left-auto sm:w-96"
|
|
109
|
-
aria-live="polite"
|
|
110
|
-
aria-label={t().newNotification}
|
|
111
|
-
>
|
|
112
|
-
<span class="flex h-8 w-8 shrink-0 items-center justify-center rounded-md bg-blue-500/10 text-blue-600 dark:bg-blue-400/15 dark:text-blue-400">
|
|
113
|
-
<i class="ti ti-bell" />
|
|
114
|
-
</span>
|
|
115
|
-
<div class="min-w-0 flex-1">
|
|
116
|
-
<p class="truncate text-sm font-medium text-primary">{item.title}</p>
|
|
117
|
-
<p class="mt-0.5 text-xs text-dimmed">{t().relatedItemReady}</p>
|
|
118
|
-
{item.targetHref && (
|
|
119
|
-
<a
|
|
120
|
-
href={item.targetHref}
|
|
121
|
-
class="mt-2 inline-flex items-center gap-1 text-xs font-medium text-blue-600 hover:underline dark:text-blue-400"
|
|
122
|
-
>
|
|
123
|
-
{t().open}
|
|
124
|
-
<i class="ti ti-arrow-right" />
|
|
125
|
-
</a>
|
|
126
|
-
)}
|
|
127
|
-
</div>
|
|
128
|
-
<IconButton size="xs" class="shrink-0" label={t().dismissNotification} onClick={() => setNotification(null)}>
|
|
129
|
-
<i class="ti ti-x" />
|
|
130
|
-
</IconButton>
|
|
131
|
-
</aside>
|
|
132
|
-
)}
|
|
133
|
-
</Show>
|
|
134
|
-
);
|
|
135
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { TextInput, useLocale } from "@k2b/ui";
|
|
2
|
-
import { createEffect, createSignal, onMount } from "solid-js";
|
|
3
|
-
import { platformMessages } from "../platform-messages";
|
|
4
|
-
|
|
5
|
-
type SearchBarProps = {
|
|
6
|
-
action?: string;
|
|
7
|
-
value?: string;
|
|
8
|
-
param?: string;
|
|
9
|
-
pageParam?: string;
|
|
10
|
-
placeholder?: string;
|
|
11
|
-
ariaLabel?: string;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
/** Search bar that filters content via URL query parameter. */
|
|
15
|
-
export default function SearchBar(props: SearchBarProps = {}) {
|
|
16
|
-
const locale = useLocale();
|
|
17
|
-
const t = () => platformMessages.resolve([locale()]).t;
|
|
18
|
-
const param = props.param ?? "search";
|
|
19
|
-
const pageParam = props.pageParam ?? "page";
|
|
20
|
-
const [query, setQuery] = createSignal(props.value ?? "");
|
|
21
|
-
|
|
22
|
-
createEffect(() => {
|
|
23
|
-
if (props.value !== undefined) {
|
|
24
|
-
setQuery(props.value);
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
onMount(() => {
|
|
29
|
-
if (props.value !== undefined) return;
|
|
30
|
-
const fallback = new URLSearchParams(window.location.search).get(param) ?? "";
|
|
31
|
-
setQuery(fallback);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const handleSubmit = (e: Event): void => {
|
|
35
|
-
e.preventDefault();
|
|
36
|
-
const current = new URL(window.location.href);
|
|
37
|
-
const url = props.action ? new URL(props.action, window.location.origin) : current;
|
|
38
|
-
const value = query().trim();
|
|
39
|
-
|
|
40
|
-
if (value.length > 0) {
|
|
41
|
-
url.searchParams.set(param, value);
|
|
42
|
-
} else {
|
|
43
|
-
url.searchParams.delete(param);
|
|
44
|
-
}
|
|
45
|
-
url.searchParams.delete(pageParam);
|
|
46
|
-
|
|
47
|
-
window.location.href = url.toString();
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const handleClear = (): void => {
|
|
51
|
-
const current = new URL(window.location.href);
|
|
52
|
-
const url = props.action ? new URL(props.action, window.location.origin) : current;
|
|
53
|
-
|
|
54
|
-
url.searchParams.delete(param);
|
|
55
|
-
url.searchParams.delete(pageParam);
|
|
56
|
-
|
|
57
|
-
window.location.href = url.toString();
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<form onSubmit={handleSubmit} role="search" class="w-full">
|
|
62
|
-
<TextInput
|
|
63
|
-
name={param}
|
|
64
|
-
type="search"
|
|
65
|
-
placeholder={props.placeholder ?? `${t().search}…`}
|
|
66
|
-
aria-label={props.ariaLabel ?? t().search}
|
|
67
|
-
icon="ti ti-search"
|
|
68
|
-
activeIcon="ti ti-search"
|
|
69
|
-
value={query}
|
|
70
|
-
onValueChange={setQuery}
|
|
71
|
-
clearable
|
|
72
|
-
clearLabel={t().clearSearch}
|
|
73
|
-
onClear={handleClear}
|
|
74
|
-
/>
|
|
75
|
-
<button type="submit" class="hidden">
|
|
76
|
-
{t().search}
|
|
77
|
-
</button>
|
|
78
|
-
</form>
|
|
79
|
-
);
|
|
80
|
-
}
|