@promptbook/cli 0.112.0-131 → 0.112.0-133
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/apps/agents-server/src/app/admin/api-tokens/ApiTokensClient.tsx +4 -1
- package/apps/agents-server/src/app/admin/chat-feedback/ChatFeedbackClient.tsx +3 -0
- package/apps/agents-server/src/app/admin/chat-feedback/ChatFeedbackTable.tsx +13 -8
- package/apps/agents-server/src/app/admin/chat-history/ChatHistoryClient.tsx +3 -0
- package/apps/agents-server/src/app/admin/chat-history/ChatHistoryTable.tsx +13 -8
- package/apps/agents-server/src/app/admin/custom-css/CustomCssClient.tsx +4 -0
- package/apps/agents-server/src/app/admin/custom-css/CustomCssEditorPanel.tsx +7 -1
- package/apps/agents-server/src/app/admin/custom-css/CustomCssFilesPanel.tsx +7 -1
- package/apps/agents-server/src/app/admin/custom-js/CustomJsClient.tsx +11 -0
- package/apps/agents-server/src/app/admin/custom-js/CustomJsEditorPanel.tsx +7 -1
- package/apps/agents-server/src/app/admin/custom-js/CustomJsFilesPanel.tsx +7 -1
- package/apps/agents-server/src/app/admin/files/FilesGalleryClient.tsx +4 -0
- package/apps/agents-server/src/app/admin/files/FilesGalleryGrid.tsx +9 -11
- package/apps/agents-server/src/app/admin/files/FilesGalleryTable.tsx +9 -8
- package/apps/agents-server/src/app/admin/images/ImagesGalleryClient.tsx +4 -0
- package/apps/agents-server/src/app/admin/images/ImagesGalleryGrid.tsx +16 -11
- package/apps/agents-server/src/app/admin/images/ImagesGalleryTable.tsx +18 -9
- package/apps/agents-server/src/app/admin/login-methods/shibboleth/page.tsx +9 -9
- package/apps/agents-server/src/app/admin/messages/MessagesClient.tsx +7 -6
- package/apps/agents-server/src/app/admin/servers/ServersRegistryTable.tsx +11 -10
- package/apps/agents-server/src/app/admin/task-manager/TaskManagerClient.tsx +4 -2
- package/apps/agents-server/src/app/admin/task-manager/TaskManagerTaskRow.tsx +14 -15
- package/apps/agents-server/src/app/admin/task-manager/TaskManagerTasksCard.tsx +4 -1
- package/apps/agents-server/src/app/admin/task-manager/useTaskManagerState.ts +6 -9
- package/apps/agents-server/src/app/admin/update/CustomCommitPicker.tsx +258 -0
- package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +305 -108
- package/apps/agents-server/src/app/admin/usage/UsageClient.tsx +4 -2
- package/apps/agents-server/src/app/admin/usage/UsageClientAnalyticsPanels.tsx +6 -4
- package/apps/agents-server/src/app/admin/usage/UsageClientFormatting.ts +6 -12
- package/apps/agents-server/src/app/admin/usage/UsageClientTimelineChart.tsx +6 -3
- package/apps/agents-server/src/app/admin/usage/useUsageClientState.ts +12 -4
- package/apps/agents-server/src/app/admin/users/[userId]/UserDetailClient.tsx +8 -3
- package/apps/agents-server/src/app/agents/[agentName]/AgentProfileChat.tsx +6 -20
- package/apps/agents-server/src/app/agents/[agentName]/book/useBookEditorHistory.ts +9 -2
- package/apps/agents-server/src/app/agents/[agentName]/chat/useAgentChatHistoryClientState.ts +2 -15
- package/apps/agents-server/src/app/agents/[agentName]/history/page.tsx +6 -1
- package/apps/agents-server/src/app/agents/[agentName]/integration/CalendarIntegrationSection.tsx +23 -5
- package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsClient.tsx +4 -2
- package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsFiltersCard.tsx +9 -4
- package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsTableCard.tsx +4 -1
- package/apps/agents-server/src/app/agents/[agentName]/timeouts/AgentTimeoutsTableRow.tsx +10 -3
- package/apps/agents-server/src/app/api/admin/update/commits/route.ts +35 -0
- package/apps/agents-server/src/app/api/admin/update/route.ts +10 -2
- package/apps/agents-server/src/app/api/internal/user-chat-jobs/run/route.ts +14 -2
- package/apps/agents-server/src/app/dashboard/page.tsx +11 -16
- package/apps/agents-server/src/app/layout.tsx +12 -3
- package/apps/agents-server/src/app/swagger/SwaggerApiKeysPanel.tsx +4 -1
- package/apps/agents-server/src/app/system/user-memory/UserMemoryClient.tsx +4 -2
- package/apps/agents-server/src/app/system/user-wallet/UserWalletClient.tsx +3 -0
- package/apps/agents-server/src/app/system/user-wallet/UserWalletRecordsTable.tsx +5 -1
- package/apps/agents-server/src/app/system/utilities/mocked-chats/MockedChatsEditorClient.tsx +3 -0
- package/apps/agents-server/src/app/system/utilities/mocked-chats/MockedChatsEditorSidebar.tsx +7 -8
- package/apps/agents-server/src/components/Footer/Footer.tsx +3 -2
- package/apps/agents-server/src/components/Footer/getCommitFooterEmoji.ts +112 -0
- package/apps/agents-server/src/components/PrintHeader/PrintHeader.tsx +4 -2
- package/apps/agents-server/src/components/UsersList/UsersList.tsx +4 -2
- package/apps/agents-server/src/languages/translations/czech.yaml +1 -1
- package/apps/agents-server/src/languages/translations/english.yaml +1 -1
- package/apps/agents-server/src/middleware/applyEmbeddingHeader.ts +4 -2
- package/apps/agents-server/src/middleware/contentSecurityPolicy.ts +97 -0
- package/apps/agents-server/src/tools/createAgentProgressTools.ts +8 -6
- package/apps/agents-server/src/utils/localization/formatServerLanguageHumanReadableDate.ts +70 -0
- package/apps/agents-server/src/utils/localization/getRequestServerLanguage.ts +32 -0
- package/apps/agents-server/src/utils/userChat/createRunUserChatJobExecutionContext.ts +17 -0
- package/apps/agents-server/src/utils/userChat/createRunUserChatJobPersistenceController.ts +0 -21
- package/apps/agents-server/src/utils/userChat/persistUserChatJobProgressCard.ts +31 -0
- package/apps/agents-server/src/utils/userChat/retryUserChatJob.ts +0 -4
- package/apps/agents-server/src/utils/userChat/runUserChatJob.ts +29 -0
- package/apps/agents-server/src/utils/userChat/userChatMessageLifecycle.ts +2 -2
- package/apps/agents-server/src/utils/userChat/userChatProgressCard.ts +99 -401
- package/apps/agents-server/src/utils/vpsSelfUpdate.ts +625 -51
- package/esm/index.es.js +252 -41
- package/esm/index.es.js.map +1 -1
- package/esm/scripts/run-codex-prompts/common/buildCoderRunProgressSnapshot.d.ts +5 -1
- package/esm/scripts/run-codex-prompts/common/cliProgressDisplay.d.ts +7 -0
- package/esm/scripts/run-codex-prompts/common/coderRunEstimateCache.d.ts +31 -0
- package/esm/scripts/run-codex-prompts/common/progressFormatting.d.ts +2 -0
- package/esm/scripts/run-codex-prompts/runners/claude-code/ClaudeCodeRunner.d.ts +3 -1
- package/esm/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +7 -0
- package/esm/src/book-3.0/cliAgentEnv.d.ts +1 -1
- package/esm/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
- package/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/book-3.0/cliAgentEnv.ts +1 -1
- package/src/cli/cli-commands/agents-server/startAgentsServer.ts +1 -1
- package/src/cli/cli-commands/coder/ThinkingLevel.ts +3 -1
- package/src/other/templates/getTemplatesPipelineCollection.ts +681 -766
- package/src/version.ts +2 -2
- package/src/versions.txt +2 -0
- package/umd/index.umd.js +254 -44
- package/umd/index.umd.js.map +1 -1
- package/umd/scripts/run-codex-prompts/common/buildCoderRunProgressSnapshot.d.ts +5 -1
- package/umd/scripts/run-codex-prompts/common/cliProgressDisplay.d.ts +7 -0
- package/umd/scripts/run-codex-prompts/common/coderRunEstimateCache.d.ts +31 -0
- package/umd/scripts/run-codex-prompts/common/progressFormatting.d.ts +2 -0
- package/umd/scripts/run-codex-prompts/runners/claude-code/ClaudeCodeRunner.d.ts +3 -1
- package/umd/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +7 -0
- package/umd/src/book-3.0/cliAgentEnv.d.ts +1 -1
- package/umd/src/cli/cli-commands/coder/ThinkingLevel.d.ts +1 -1
- package/umd/src/version.d.ts +1 -1
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
export const dynamic = 'force-dynamic';
|
|
5
5
|
|
|
6
6
|
import { getSingleLlmExecutionTools } from '@promptbook-local/core';
|
|
7
|
-
import moment from 'moment';
|
|
8
7
|
import { headers } from 'next/headers';
|
|
9
8
|
import { AboutPromptbookInformation } from '../../../../../src/utils/misc/xAboutPromptbookInformation';
|
|
10
9
|
import { HomepagePrimarySections } from '../../components/Homepage/HomepagePrimarySections';
|
|
@@ -17,21 +16,10 @@ import { getLongRunningTask } from '../../deamons/longRunningTask';
|
|
|
17
16
|
import { $provideExecutionToolsForServer } from '../../tools/$provideExecutionToolsForServer';
|
|
18
17
|
import { $provideServer } from '../../tools/$provideServer';
|
|
19
18
|
import { isUserAdmin } from '../../utils/isUserAdmin';
|
|
19
|
+
import { formatServerLanguageHumanReadableDate } from '../../utils/localization/formatServerLanguageHumanReadableDate';
|
|
20
|
+
import { getRequestServerLanguage } from '../../utils/localization/getRequestServerLanguage';
|
|
20
21
|
import { getHomePageAgents } from '../_data/getHomePageAgents';
|
|
21
22
|
|
|
22
|
-
// Add calendar formats that include seconds
|
|
23
|
-
/**
|
|
24
|
-
* Map of calendar with seconds.
|
|
25
|
-
*/
|
|
26
|
-
const calendarWithSeconds = {
|
|
27
|
-
sameDay: '[Today at] LTS',
|
|
28
|
-
nextDay: '[Tomorrow at] LTS',
|
|
29
|
-
nextWeek: 'dddd [at] LTS',
|
|
30
|
-
lastDay: '[Yesterday at] LTS',
|
|
31
|
-
lastWeek: '[Last] dddd [at] LTS',
|
|
32
|
-
sameElse: 'L [at] LTS',
|
|
33
|
-
};
|
|
34
|
-
|
|
35
23
|
/**
|
|
36
24
|
* Renders the legacy dashboard with system and admin details.
|
|
37
25
|
*/
|
|
@@ -40,6 +28,7 @@ export default async function DashboardPage() {
|
|
|
40
28
|
const { publicUrl } = server;
|
|
41
29
|
const isAdmin = await isUserAdmin(); /* <- TODO: [??] Here should be user permissions */
|
|
42
30
|
const { agents, folders, homepageMessage, currentUser } = await getHomePageAgents();
|
|
31
|
+
const language = await getRequestServerLanguage();
|
|
43
32
|
|
|
44
33
|
const longRunningTask = getLongRunningTask();
|
|
45
34
|
|
|
@@ -92,10 +81,16 @@ export default async function DashboardPage() {
|
|
|
92
81
|
<TechInfoCard title={`Long running task ${longRunningTask.taskId}`}>
|
|
93
82
|
<p className="text-gray-600">Tick: {longRunningTask.tick}</p>
|
|
94
83
|
<p className="text-gray-600">
|
|
95
|
-
Created At:
|
|
84
|
+
Created At:{' '}
|
|
85
|
+
{formatServerLanguageHumanReadableDate(longRunningTask.createdAt, language, {
|
|
86
|
+
isExactDateIncluded: true,
|
|
87
|
+
})}
|
|
96
88
|
</p>
|
|
97
89
|
<p className="text-gray-600">
|
|
98
|
-
Updated At:
|
|
90
|
+
Updated At:{' '}
|
|
91
|
+
{formatServerLanguageHumanReadableDate(longRunningTask.updatedAt, language, {
|
|
92
|
+
isExactDateIncluded: true,
|
|
93
|
+
})}
|
|
99
94
|
</p>
|
|
100
95
|
</TechInfoCard>
|
|
101
96
|
|
|
@@ -3,7 +3,8 @@ import { LayoutWrapper } from '@/src/components/LayoutWrapper/LayoutWrapper';
|
|
|
3
3
|
import { createThemeModeBootstrapScript } from '@/src/components/ThemeMode/createThemeModeBootstrapScript';
|
|
4
4
|
import { APPLICATION_FONT_VARIABLE_CLASS_NAME } from '@/src/utils/applicationFonts';
|
|
5
5
|
import type { Metadata } from 'next';
|
|
6
|
-
import { cookies } from 'next/headers';
|
|
6
|
+
import { cookies, headers } from 'next/headers';
|
|
7
|
+
import { CONTENT_SECURITY_POLICY_NONCE_REQUEST_HEADER } from '../middleware/contentSecurityPolicy';
|
|
7
8
|
import { getCustomJavascriptWithIntegrations } from '../database/customJavascript';
|
|
8
9
|
import { getAggregatedCustomStylesheetCss } from '../database/customStylesheet';
|
|
9
10
|
import { getMetadataMap } from '../database/getMetadata';
|
|
@@ -293,6 +294,7 @@ export default async function RootLayout({
|
|
|
293
294
|
getCustomJavascriptWithIntegrations,
|
|
294
295
|
);
|
|
295
296
|
const cookieStorePromise = cookies();
|
|
297
|
+
const requestHeadersPromise = headers();
|
|
296
298
|
const defaultThemeModePromise = Promise.all([currentUserPromise, cookieStorePromise, layoutMetadataPromise]).then(
|
|
297
299
|
async ([currentUser, cookieStore, layoutMetadata]) => {
|
|
298
300
|
const cookieThemeMode = cookieStore.get(THEME_MODE_COOKIE_NAME)?.value || null;
|
|
@@ -346,6 +348,7 @@ export default async function RootLayout({
|
|
|
346
348
|
customStylesheetCss,
|
|
347
349
|
customJavascript,
|
|
348
350
|
cookieStore,
|
|
351
|
+
requestHeaders,
|
|
349
352
|
defaultThemeMode,
|
|
350
353
|
federatedServers,
|
|
351
354
|
footerLinks,
|
|
@@ -363,6 +366,7 @@ export default async function RootLayout({
|
|
|
363
366
|
customStylesheetCssPromise,
|
|
364
367
|
customJavascriptPromise,
|
|
365
368
|
cookieStorePromise,
|
|
369
|
+
requestHeadersPromise,
|
|
366
370
|
defaultThemeModePromise,
|
|
367
371
|
federatedServersPromise,
|
|
368
372
|
footerLinksPromise,
|
|
@@ -379,7 +383,10 @@ export default async function RootLayout({
|
|
|
379
383
|
const feedbackMode = parseChatFeedbackMode(layoutMetadata.CHAT_FEEDBACK_MODE, layoutMetadata.IS_FEEDBACK_ENABLED);
|
|
380
384
|
const isExperimentalPwaAppEnabled = (layoutMetadata.IS_EXPERIMENTAL_PWA_APP_ENABLED ?? 'true') === 'true';
|
|
381
385
|
const isPublicServer = isPublicServerVisibility(serverVisibility);
|
|
382
|
-
|
|
386
|
+
// Note: The custom JavaScript is executed only when the browser sees the matching
|
|
387
|
+
// `script-src 'nonce-...'` directive that the middleware sets per request, so a
|
|
388
|
+
// fragile string-level sanitization is intentionally not applied here.
|
|
389
|
+
const contentSecurityPolicyNonce = requestHeaders.get(CONTENT_SECURITY_POLICY_NONCE_REQUEST_HEADER) ?? undefined;
|
|
383
390
|
const webPushPublicKey = process.env.NEXT_PUBLIC_WEB_PUSH_PUBLIC_KEY?.trim() || null;
|
|
384
391
|
const chatVisualModeCookie = cookieStore.get(CHAT_VISUAL_MODE_COOKIE_NAME)?.value || null;
|
|
385
392
|
const cookieLanguage = cookieStore.get(SERVER_LANGUAGE_COOKIE_NAME)?.value || null;
|
|
@@ -412,6 +419,7 @@ export default async function RootLayout({
|
|
|
412
419
|
<body className={`${APPLICATION_FONT_VARIABLE_CLASS_NAME} bg-background text-foreground antialiased`}>
|
|
413
420
|
<script
|
|
414
421
|
id="agents-server-theme-mode"
|
|
422
|
+
nonce={contentSecurityPolicyNonce}
|
|
415
423
|
dangerouslySetInnerHTML={{ __html: themeModeBootstrapScript }}
|
|
416
424
|
/>
|
|
417
425
|
{customStylesheetCss && <style id="agents-server-custom-css">{customStylesheetCss}</style>}
|
|
@@ -448,7 +456,8 @@ export default async function RootLayout({
|
|
|
448
456
|
{customJavascript && (
|
|
449
457
|
<script
|
|
450
458
|
id="agents-server-custom-js"
|
|
451
|
-
|
|
459
|
+
nonce={contentSecurityPolicyNonce}
|
|
460
|
+
dangerouslySetInnerHTML={{ __html: customJavascript }}
|
|
452
461
|
/>
|
|
453
462
|
)}
|
|
454
463
|
{/* Global portal root for modals/popups */}
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
import Link from 'next/link';
|
|
4
4
|
import { Plus } from 'lucide-react';
|
|
5
5
|
import { useEffect, useState } from 'react';
|
|
6
|
+
import { useServerLanguage } from '@/src/components/ServerLanguage/ServerLanguageProvider';
|
|
6
7
|
import { SecretInput } from '@/src/components/SecretInput/SecretInput';
|
|
7
8
|
import type { ApiTokenEntry } from '@/src/utils/apiTokensClient';
|
|
8
9
|
import { createApiToken, fetchApiTokens } from '@/src/utils/apiTokensClient';
|
|
10
|
+
import { formatServerLanguageHumanReadableDate } from '@/src/utils/localization/formatServerLanguageHumanReadableDate';
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Props for the compact Swagger API-key panel.
|
|
@@ -21,6 +23,7 @@ type SwaggerApiKeysPanelProps = {
|
|
|
21
23
|
* Compact API-key panel shown above Swagger UI.
|
|
22
24
|
*/
|
|
23
25
|
export function SwaggerApiKeysPanel({ isAdmin }: SwaggerApiKeysPanelProps) {
|
|
26
|
+
const { language } = useServerLanguage();
|
|
24
27
|
const [tokens, setTokens] = useState<ApiTokenEntry[]>([]);
|
|
25
28
|
const [isLoading, setIsLoading] = useState(isAdmin);
|
|
26
29
|
const [isCreating, setIsCreating] = useState(false);
|
|
@@ -110,7 +113,7 @@ export function SwaggerApiKeysPanel({ isAdmin }: SwaggerApiKeysPanelProps) {
|
|
|
110
113
|
{token.note || `API key #${token.id}`}
|
|
111
114
|
</span>
|
|
112
115
|
<span className="text-xs text-slate-400">
|
|
113
|
-
{
|
|
116
|
+
{formatServerLanguageHumanReadableDate(token.createdAt, language)}
|
|
114
117
|
</span>
|
|
115
118
|
</div>
|
|
116
119
|
<SecretInput value={token.token} readOnly aria-label={`API key ${token.id}`} />
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { showConfirm } from '@/src/components/AsyncDialogs/asyncDialogs';
|
|
4
|
+
import { useServerLanguage } from '@/src/components/ServerLanguage/ServerLanguageProvider';
|
|
5
|
+
import { formatServerLanguageHumanReadableDate } from '@/src/utils/localization/formatServerLanguageHumanReadableDate';
|
|
4
6
|
import { useEffect, useMemo, useState } from 'react';
|
|
5
7
|
|
|
6
8
|
/**
|
|
@@ -42,6 +44,7 @@ type UserMemoryClientProps = {
|
|
|
42
44
|
*/
|
|
43
45
|
export function UserMemoryClient(props: UserMemoryClientProps) {
|
|
44
46
|
const { agents } = props;
|
|
47
|
+
const { language } = useServerLanguage();
|
|
45
48
|
const [memories, setMemories] = useState<UserMemoryEntry[]>([]);
|
|
46
49
|
const [loading, setLoading] = useState<boolean>(true);
|
|
47
50
|
const [saving, setSaving] = useState<boolean>(false);
|
|
@@ -445,7 +448,7 @@ export function UserMemoryClient(props: UserMemoryClientProps) {
|
|
|
445
448
|
)}
|
|
446
449
|
</td>
|
|
447
450
|
<td className="px-4 py-3 text-xs text-gray-500">
|
|
448
|
-
{
|
|
451
|
+
{formatServerLanguageHumanReadableDate(memory.updatedAt, language)}
|
|
449
452
|
</td>
|
|
450
453
|
<td className="px-4 py-3 text-right text-sm">
|
|
451
454
|
{isEditing ? (
|
|
@@ -491,4 +494,3 @@ export function UserMemoryClient(props: UserMemoryClientProps) {
|
|
|
491
494
|
</div>
|
|
492
495
|
);
|
|
493
496
|
}
|
|
494
|
-
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { WalletRecordDialog } from '@/src/components/WalletRecordDialog/WalletRecordDialog';
|
|
4
|
+
import { useServerLanguage } from '@/src/components/ServerLanguage/ServerLanguageProvider';
|
|
4
5
|
import type { GithubAppStatusResponse } from '@/src/utils/githubAppClient';
|
|
5
6
|
import { UserWalletCreateCard } from './UserWalletCreateCard';
|
|
6
7
|
import type { UserWalletAgentOption } from './UserWalletAgentOption';
|
|
@@ -34,6 +35,7 @@ function resolveGithubAppStatusDescription(status: GithubAppStatusResponse | nul
|
|
|
34
35
|
*/
|
|
35
36
|
export function UserWalletClient(props: UserWalletClientProps) {
|
|
36
37
|
const { agents } = props;
|
|
38
|
+
const { language } = useServerLanguage();
|
|
37
39
|
const state = useUserWalletClientState({ agents });
|
|
38
40
|
|
|
39
41
|
return (
|
|
@@ -118,6 +120,7 @@ export function UserWalletClient(props: UserWalletClientProps) {
|
|
|
118
120
|
|
|
119
121
|
<UserWalletRecordsTable
|
|
120
122
|
agents={agents}
|
|
123
|
+
language={language}
|
|
121
124
|
editingDraft={state.editingDraft}
|
|
122
125
|
isEditingSmtpRecord={state.isEditingSmtpRecord}
|
|
123
126
|
isLoading={state.loading}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import type { UserWalletRecord } from '@/src/utils/userWallet';
|
|
4
|
+
import type { ServerLanguageCode } from '@/src/languages/ServerLanguageRegistry';
|
|
5
|
+
import { formatServerLanguageHumanReadableDate } from '@/src/utils/localization/formatServerLanguageHumanReadableDate';
|
|
4
6
|
import type { UserWalletAgentOption } from './UserWalletAgentOption';
|
|
5
7
|
import { formatWalletJsonSchemaForTextarea, type EditingUserWalletDraft, type UpdateUserWalletDraft } from './UserWalletDraft';
|
|
6
8
|
import { UserWalletRecordFormFields } from './UserWalletRecordFormFields';
|
|
@@ -11,6 +13,7 @@ import { UserWalletRecordValue } from './UserWalletRecordValue';
|
|
|
11
13
|
*/
|
|
12
14
|
type UserWalletRecordsTableProps = {
|
|
13
15
|
agents: ReadonlyArray<UserWalletAgentOption>;
|
|
16
|
+
language: ServerLanguageCode;
|
|
14
17
|
editingDraft: EditingUserWalletDraft | null;
|
|
15
18
|
isEditingSmtpRecord: boolean;
|
|
16
19
|
isLoading: boolean;
|
|
@@ -31,6 +34,7 @@ type UserWalletRecordsTableProps = {
|
|
|
31
34
|
*/
|
|
32
35
|
export function UserWalletRecordsTable({
|
|
33
36
|
agents,
|
|
37
|
+
language,
|
|
34
38
|
editingDraft,
|
|
35
39
|
isEditingSmtpRecord,
|
|
36
40
|
isLoading,
|
|
@@ -118,7 +122,7 @@ export function UserWalletRecordsTable({
|
|
|
118
122
|
)}
|
|
119
123
|
</td>
|
|
120
124
|
<td className="px-4 py-3 text-xs text-gray-500">
|
|
121
|
-
{
|
|
125
|
+
{formatServerLanguageHumanReadableDate(record.updatedAt, language)}
|
|
122
126
|
</td>
|
|
123
127
|
<td className="px-4 py-3 text-right text-sm">
|
|
124
128
|
{isEditing ? (
|
package/apps/agents-server/src/app/system/utilities/mocked-chats/MockedChatsEditorClient.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useEffect, useMemo, useState } from 'react';
|
|
4
|
+
import { useServerLanguage } from '@/src/components/ServerLanguage/ServerLanguageProvider';
|
|
4
5
|
import type { MockedChatPreset } from '@/src/utils/mockedChatsSchema';
|
|
5
6
|
import { MockedChatsEditorForm } from './MockedChatsEditorForm';
|
|
6
7
|
import { MockedChatsEditorSidebar } from './MockedChatsEditorSidebar';
|
|
@@ -17,6 +18,7 @@ type MockedChatsEditorClientProps = {
|
|
|
17
18
|
* Mocked chat editor under `System -> Utilities`.
|
|
18
19
|
*/
|
|
19
20
|
export function MockedChatsEditorClient({ initialMockedChats }: MockedChatsEditorClientProps) {
|
|
21
|
+
const { language } = useServerLanguage();
|
|
20
22
|
const mockedChatsEditorState = useMockedChatsEditorState({ initialMockedChats });
|
|
21
23
|
const viewerHref = useMemo(
|
|
22
24
|
() => buildMockedChatViewerHref(mockedChatsEditorState.draftChat.id),
|
|
@@ -47,6 +49,7 @@ export function MockedChatsEditorClient({ initialMockedChats }: MockedChatsEdito
|
|
|
47
49
|
|
|
48
50
|
<div className="grid gap-6 lg:grid-cols-[340px_minmax(0,1fr)]">
|
|
49
51
|
<MockedChatsEditorSidebar
|
|
52
|
+
language={language}
|
|
50
53
|
savedChats={mockedChatsEditorState.savedChats}
|
|
51
54
|
selectedChatId={mockedChatsEditorState.selectedChatId}
|
|
52
55
|
createNewDraft={mockedChatsEditorState.createNewDraft}
|
package/apps/agents-server/src/app/system/utilities/mocked-chats/MockedChatsEditorSidebar.tsx
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import Link from 'next/link';
|
|
2
|
+
import type { ServerLanguageCode } from '@/src/languages/ServerLanguageRegistry';
|
|
3
|
+
import { formatServerLanguageHumanReadableDate } from '@/src/utils/localization/formatServerLanguageHumanReadableDate';
|
|
2
4
|
import type { UseMockedChatsEditorState } from './useMockedChatsEditorState';
|
|
3
5
|
|
|
4
6
|
/**
|
|
@@ -8,6 +10,7 @@ type MockedChatsEditorSidebarProps = Pick<
|
|
|
8
10
|
UseMockedChatsEditorState,
|
|
9
11
|
'savedChats' | 'selectedChatId' | 'createNewDraft' | 'selectSavedChat'
|
|
10
12
|
> & {
|
|
13
|
+
language: ServerLanguageCode;
|
|
11
14
|
buildViewerHref: (mockedChatId: string) => string;
|
|
12
15
|
};
|
|
13
16
|
|
|
@@ -16,13 +19,8 @@ type MockedChatsEditorSidebarProps = Pick<
|
|
|
16
19
|
*
|
|
17
20
|
* @private function of <MockedChatsEditorClient/>
|
|
18
21
|
*/
|
|
19
|
-
function formatMockedChatUpdatedAt(updatedAt: string): string {
|
|
20
|
-
|
|
21
|
-
if (Number.isNaN(date.getTime())) {
|
|
22
|
-
return 'just now';
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return date.toLocaleString();
|
|
22
|
+
function formatMockedChatUpdatedAt(updatedAt: string, language: ServerLanguageCode): string {
|
|
23
|
+
return formatServerLanguageHumanReadableDate(updatedAt, language, { fallbackLabel: 'just now' });
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
/**
|
|
@@ -31,6 +29,7 @@ function formatMockedChatUpdatedAt(updatedAt: string): string {
|
|
|
31
29
|
* @private function of <MockedChatsEditorClient/>
|
|
32
30
|
*/
|
|
33
31
|
export function MockedChatsEditorSidebar({
|
|
32
|
+
language,
|
|
34
33
|
savedChats,
|
|
35
34
|
selectedChatId,
|
|
36
35
|
createNewDraft,
|
|
@@ -74,7 +73,7 @@ export function MockedChatsEditorSidebar({
|
|
|
74
73
|
<div className="min-w-0">
|
|
75
74
|
<p className="truncate text-sm font-semibold text-slate-900">{chat.name}</p>
|
|
76
75
|
<p className="mt-1 text-xs text-slate-500">
|
|
77
|
-
Updated {formatMockedChatUpdatedAt(chat.updatedAt)}
|
|
76
|
+
Updated {formatMockedChatUpdatedAt(chat.updatedAt, language)}
|
|
78
77
|
</p>
|
|
79
78
|
</div>
|
|
80
79
|
<Link
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF } from '@/config';
|
|
3
|
+
import { NEXT_PUBLIC_VERCEL_GIT_COMMIT_REF, NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA } from '@/config';
|
|
4
4
|
import { CLAIM, NAME, PROMPTBOOK_ENGINE_VERSION } from '@promptbook-local/core';
|
|
5
5
|
import { useServerLanguage } from '../ServerLanguage/ServerLanguageProvider';
|
|
6
6
|
import { HeadlessLink } from '../_utils/headlessParam';
|
|
7
|
+
import { getCommitFooterEmoji } from './getCommitFooterEmoji';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Title/url pair used to extend the footer via metadata.
|
|
@@ -169,7 +170,7 @@ export function Footer(props: FooterProps) {
|
|
|
169
170
|
<br />
|
|
170
171
|
{t('footer.allRightsReserved')}
|
|
171
172
|
<br />
|
|
172
|
-
{t('footer.madeInCzechRepublic')}
|
|
173
|
+
{t('footer.madeInCzechRepublic', { emoji: getCommitFooterEmoji(NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA) })}
|
|
173
174
|
{/* <- TODO: !!!!!!!! Put here Prague outline */}
|
|
174
175
|
</p>
|
|
175
176
|
<p className="mt-2 text-xs text-gray-400">
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curated list of positive or neutral emojis displayed in the footer's "Made with {emoji} in Europe" line.
|
|
3
|
+
*
|
|
4
|
+
* The order of this list is part of the deterministic mapping from commit SHA to emoji, so existing
|
|
5
|
+
* entries should not be reordered. New emojis can be appended at the end without affecting the
|
|
6
|
+
* indexes of existing ones, but each commit SHA will then map to a different emoji.
|
|
7
|
+
*
|
|
8
|
+
* Hearts, cute animals, plants, fruits, sweets, and celebratory symbols are intentionally chosen so
|
|
9
|
+
* that the footer never shows anything aggressive, scary, or negative.
|
|
10
|
+
*/
|
|
11
|
+
const POSITIVE_FOOTER_EMOJIS = [
|
|
12
|
+
'❤️',
|
|
13
|
+
'🧡',
|
|
14
|
+
'💛',
|
|
15
|
+
'💚',
|
|
16
|
+
'💙',
|
|
17
|
+
'💜',
|
|
18
|
+
'🤍',
|
|
19
|
+
'💖',
|
|
20
|
+
'💝',
|
|
21
|
+
'🐙',
|
|
22
|
+
'🦄',
|
|
23
|
+
'🐶',
|
|
24
|
+
'🐱',
|
|
25
|
+
'🐰',
|
|
26
|
+
'🦊',
|
|
27
|
+
'🐻',
|
|
28
|
+
'🐼',
|
|
29
|
+
'🐨',
|
|
30
|
+
'🐯',
|
|
31
|
+
'🦁',
|
|
32
|
+
'🐸',
|
|
33
|
+
'🐵',
|
|
34
|
+
'🐧',
|
|
35
|
+
'🦉',
|
|
36
|
+
'🐢',
|
|
37
|
+
'🦋',
|
|
38
|
+
'🐝',
|
|
39
|
+
'🐞',
|
|
40
|
+
'🐳',
|
|
41
|
+
'🐬',
|
|
42
|
+
'🦒',
|
|
43
|
+
'🐘',
|
|
44
|
+
'🌸',
|
|
45
|
+
'🌻',
|
|
46
|
+
'🌷',
|
|
47
|
+
'🌹',
|
|
48
|
+
'🌈',
|
|
49
|
+
'🌟',
|
|
50
|
+
'✨',
|
|
51
|
+
'☀️',
|
|
52
|
+
'🌙',
|
|
53
|
+
'🍀',
|
|
54
|
+
'🌳',
|
|
55
|
+
'🍎',
|
|
56
|
+
'🍓',
|
|
57
|
+
'🍑',
|
|
58
|
+
'🍒',
|
|
59
|
+
'🍋',
|
|
60
|
+
'🍊',
|
|
61
|
+
'🍇',
|
|
62
|
+
'🍉',
|
|
63
|
+
'🍰',
|
|
64
|
+
'🍪',
|
|
65
|
+
'🍩',
|
|
66
|
+
'🍕',
|
|
67
|
+
'🍔',
|
|
68
|
+
'🍦',
|
|
69
|
+
'🍫',
|
|
70
|
+
'🎉',
|
|
71
|
+
'🎈',
|
|
72
|
+
'🎁',
|
|
73
|
+
'🎨',
|
|
74
|
+
'🚀',
|
|
75
|
+
'⭐',
|
|
76
|
+
'💎',
|
|
77
|
+
'🪐',
|
|
78
|
+
'🎵',
|
|
79
|
+
] as const;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Default emoji used in the footer when no commit SHA is available (for example during local
|
|
83
|
+
* development outside Vercel). Kept as the historic `❤️` so the footer text does not look
|
|
84
|
+
* unusual when the deployment metadata is missing.
|
|
85
|
+
*/
|
|
86
|
+
const DEFAULT_FOOTER_EMOJI: string = POSITIVE_FOOTER_EMOJIS[0]!;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Derives a stable emoji from a commit SHA using a small djb2-style string hash.
|
|
90
|
+
*
|
|
91
|
+
* The same SHA always produces the same emoji, so users can spot a server update by noticing that
|
|
92
|
+
* the footer emoji changed. The result is taken modulo the curated list, so it is always one of
|
|
93
|
+
* the positive/neutral emojis in {@link POSITIVE_FOOTER_EMOJIS}.
|
|
94
|
+
*
|
|
95
|
+
* @param commitSha - Full or short commit SHA. May be `null` or `undefined` when running outside
|
|
96
|
+
* a Vercel deployment, in which case {@link DEFAULT_FOOTER_EMOJI} is returned.
|
|
97
|
+
* @returns One emoji from {@link POSITIVE_FOOTER_EMOJIS}.
|
|
98
|
+
* @private Internal to `apps/agents-server`
|
|
99
|
+
*/
|
|
100
|
+
export function getCommitFooterEmoji(commitSha: string | null | undefined): string {
|
|
101
|
+
if (!commitSha) {
|
|
102
|
+
return DEFAULT_FOOTER_EMOJI;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let hash = 0;
|
|
106
|
+
for (let characterIndex = 0; characterIndex < commitSha.length; characterIndex++) {
|
|
107
|
+
hash = (hash * 31 + commitSha.charCodeAt(characterIndex)) | 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const emojiIndex = Math.abs(hash) % POSITIVE_FOOTER_EMOJIS.length;
|
|
111
|
+
return POSITIVE_FOOTER_EMOJIS[emojiIndex]!;
|
|
112
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { formatAgentNamingText } from '../../utils/agentNaming';
|
|
2
2
|
import { getAgentNaming } from '../../utils/getAgentNaming';
|
|
3
|
+
import { formatServerLanguageHumanReadableDate } from '../../utils/localization/formatServerLanguageHumanReadableDate';
|
|
4
|
+
import { getRequestServerLanguage } from '../../utils/localization/getRequestServerLanguage';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Renders a print-only header with server branding.
|
|
@@ -8,7 +10,7 @@ import { getAgentNaming } from '../../utils/getAgentNaming';
|
|
|
8
10
|
* @returns Print header markup.
|
|
9
11
|
*/
|
|
10
12
|
export async function PrintHeader({ title }: { title?: string }) {
|
|
11
|
-
const agentNaming = await getAgentNaming();
|
|
13
|
+
const [agentNaming, language] = await Promise.all([getAgentNaming(), getRequestServerLanguage()]);
|
|
12
14
|
return (
|
|
13
15
|
<div className="hidden print:block mb-6 border-b-2 border-blue-600 pb-2">
|
|
14
16
|
<div className="flex justify-between items-end">
|
|
@@ -23,7 +25,7 @@ export async function PrintHeader({ title }: { title?: string }) {
|
|
|
23
25
|
{title && <h2 className="text-lg font-semibold text-gray-700">{title}</h2>}
|
|
24
26
|
</div>
|
|
25
27
|
<div className="text-xs text-gray-400 mt-1 text-right">
|
|
26
|
-
{new Date()
|
|
28
|
+
{formatServerLanguageHumanReadableDate(new Date(), language, { isExactDateIncluded: true })}
|
|
27
29
|
</div>
|
|
28
30
|
</div>
|
|
29
31
|
);
|
|
@@ -6,6 +6,7 @@ import { Card } from '../Homepage/Card';
|
|
|
6
6
|
import { Section } from '../Homepage/Section';
|
|
7
7
|
import { useServerLanguage } from '../ServerLanguage/ServerLanguageProvider';
|
|
8
8
|
import { SecretInput } from '@/src/components/SecretInput/SecretInput';
|
|
9
|
+
import { formatServerLanguageHumanReadableDate } from '@/src/utils/localization/formatServerLanguageHumanReadableDate';
|
|
9
10
|
import { useUsersAdmin } from './useUsersAdmin';
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -26,7 +27,7 @@ type UsersListProps = {
|
|
|
26
27
|
*/
|
|
27
28
|
export function UsersList({ allowCreate = true }: UsersListProps) {
|
|
28
29
|
const { users, loading, error, createUser, deleteUser, toggleAdmin } = useUsersAdmin();
|
|
29
|
-
const { t } = useServerLanguage();
|
|
30
|
+
const { language, t } = useServerLanguage();
|
|
30
31
|
|
|
31
32
|
const [newUsername, setNewUsername] = useState('');
|
|
32
33
|
const [newPassword, setNewPassword] = useState('');
|
|
@@ -91,7 +92,8 @@ export function UsersList({ allowCreate = true }: UsersListProps) {
|
|
|
91
92
|
</p>
|
|
92
93
|
)}
|
|
93
94
|
<p className="text-gray-500 text-sm mt-2">
|
|
94
|
-
{t('users.createdLabel')}:
|
|
95
|
+
{t('users.createdLabel')}:{' '}
|
|
96
|
+
{formatServerLanguageHumanReadableDate(user.createdAt, language)}
|
|
95
97
|
</p>
|
|
96
98
|
</div>
|
|
97
99
|
<div className="space-x-2">
|
|
@@ -380,7 +380,7 @@ footer.logosAndBranding: Loga a vizuální identita
|
|
|
380
380
|
footer.connectSectionTitle: Spojte se s námi
|
|
381
381
|
footer.linksSectionTitle: Odkazy
|
|
382
382
|
footer.allRightsReserved: Všechna práva vyhrazena.
|
|
383
|
-
footer.madeInCzechRepublic: Vytvořeno s
|
|
383
|
+
footer.madeInCzechRepublic: Vytvořeno s {emoji} v České republice.
|
|
384
384
|
footer.engineVersion: Verze jádra Promptbooku
|
|
385
385
|
forbidden.title: 403 Zakázáno
|
|
386
386
|
forbidden.message: Nemáte oprávnění k přístupu na tuto stránku.
|
|
@@ -384,7 +384,7 @@ footer.logosAndBranding: Logos & Branding
|
|
|
384
384
|
footer.connectSectionTitle: Connect
|
|
385
385
|
footer.linksSectionTitle: Links
|
|
386
386
|
footer.allRightsReserved: All rights reserved.
|
|
387
|
-
footer.madeInCzechRepublic: Made with
|
|
387
|
+
footer.madeInCzechRepublic: Made with {emoji} in Europe
|
|
388
388
|
footer.engineVersion: Promptbook engine version
|
|
389
389
|
forbidden.title: 403 Forbidden
|
|
390
390
|
forbidden.message: You do not have permission to access this page.
|
|
@@ -21,13 +21,15 @@ export function applyEmbeddingHeader(response: NextResponse, url: URL, isAllowed
|
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
// Note: The base strict CSP (script-src with nonce) is preserved by appending a separate
|
|
25
|
+
// Content-Security-Policy header that only carries the `frame-ancestors` directive.
|
|
24
26
|
if (isAllowed) {
|
|
25
|
-
response.headers.
|
|
27
|
+
response.headers.append('Content-Security-Policy', 'frame-ancestors https: http:');
|
|
26
28
|
response.headers.delete('X-Frame-Options');
|
|
27
29
|
return;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
response.headers.
|
|
32
|
+
response.headers.append('Content-Security-Policy', "frame-ancestors 'none'");
|
|
31
33
|
response.headers.set('X-Frame-Options', 'DENY');
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { NextResponse } from 'next/server';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Name of the request header that carries the per-request CSP nonce to server components.
|
|
5
|
+
*
|
|
6
|
+
* Server components read it via `next/headers` and must pass it to every inline `<script>` tag so
|
|
7
|
+
* the browser executes only scripts explicitly rendered by the server.
|
|
8
|
+
*
|
|
9
|
+
* @private function of middleware
|
|
10
|
+
*/
|
|
11
|
+
export const CONTENT_SECURITY_POLICY_NONCE_REQUEST_HEADER = 'x-csp-nonce';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Length (in bytes) of the random material used to derive each per-request CSP nonce.
|
|
15
|
+
*
|
|
16
|
+
* 16 bytes (128 bits) is the value recommended by the W3C CSP3 specification — large enough to
|
|
17
|
+
* make guessing infeasible while keeping the base64 representation short.
|
|
18
|
+
*
|
|
19
|
+
* @private function of middleware
|
|
20
|
+
*/
|
|
21
|
+
const CONTENT_SECURITY_POLICY_NONCE_BYTE_LENGTH = 16;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Generates a fresh, single-use CSP nonce encoded as base64.
|
|
25
|
+
*
|
|
26
|
+
* @returns Cryptographically random nonce safe to inline into a CSP header.
|
|
27
|
+
*
|
|
28
|
+
* @private function of middleware
|
|
29
|
+
*/
|
|
30
|
+
export function generateContentSecurityPolicyNonce(): string {
|
|
31
|
+
const randomBytes = new Uint8Array(CONTENT_SECURITY_POLICY_NONCE_BYTE_LENGTH);
|
|
32
|
+
crypto.getRandomValues(randomBytes);
|
|
33
|
+
|
|
34
|
+
let binaryString = '';
|
|
35
|
+
for (const byte of randomBytes) {
|
|
36
|
+
binaryString += String.fromCharCode(byte);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Note: `btoa` is available in the Next.js Edge runtime where this middleware executes.
|
|
40
|
+
return btoa(binaryString);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Builds the strict Content Security Policy header value for the given nonce.
|
|
45
|
+
*
|
|
46
|
+
* The policy locks down `script-src` to nonced inline scripts and scripts loaded by them
|
|
47
|
+
* (`'strict-dynamic'`). The legacy fallbacks (`'unsafe-inline'`, `http:`, `https:`) are ignored
|
|
48
|
+
* by modern browsers that honor the nonce, but keep the page functional in older browsers
|
|
49
|
+
* without weakening security in modern ones.
|
|
50
|
+
*
|
|
51
|
+
* @param nonce - Per-request CSP nonce.
|
|
52
|
+
* @returns Serialized CSP header value.
|
|
53
|
+
*
|
|
54
|
+
* @private function of middleware
|
|
55
|
+
*/
|
|
56
|
+
function buildContentSecurityPolicyHeaderValue(nonce: string): string {
|
|
57
|
+
const directives = [
|
|
58
|
+
`script-src 'nonce-${nonce}' 'strict-dynamic' 'unsafe-inline' 'unsafe-eval' https: http:`,
|
|
59
|
+
`object-src 'none'`,
|
|
60
|
+
`base-uri 'self'`,
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
return directives.join('; ');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Adds the CSP nonce headers to the forwarded request so server components and the Next.js
|
|
68
|
+
* runtime can detect the nonce and apply it to their own inline scripts.
|
|
69
|
+
*
|
|
70
|
+
* @param requestHeaders - Mutable headers object that is forwarded to the route handler.
|
|
71
|
+
* @param nonce - Per-request CSP nonce.
|
|
72
|
+
*
|
|
73
|
+
* @private function of middleware
|
|
74
|
+
*/
|
|
75
|
+
export function applyContentSecurityPolicyToRequestHeaders(requestHeaders: Headers, nonce: string): void {
|
|
76
|
+
requestHeaders.set(CONTENT_SECURITY_POLICY_NONCE_REQUEST_HEADER, nonce);
|
|
77
|
+
|
|
78
|
+
// Note: Next.js detects the nonce automatically when the CSP is also present on the
|
|
79
|
+
// forwarded request headers — see https://nextjs.org/docs/app/guides/content-security-policy
|
|
80
|
+
requestHeaders.set('Content-Security-Policy', buildContentSecurityPolicyHeaderValue(nonce));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Adds the strict CSP response header so the browser enforces the nonced script policy.
|
|
85
|
+
*
|
|
86
|
+
* Helpers that add narrower directives (e.g. `applyEmbeddingHeader` with `frame-ancestors`)
|
|
87
|
+
* must `append` a separate CSP header rather than `set` so the strict `script-src` policy is
|
|
88
|
+
* preserved.
|
|
89
|
+
*
|
|
90
|
+
* @param response - Outgoing response.
|
|
91
|
+
* @param nonce - Per-request CSP nonce.
|
|
92
|
+
*
|
|
93
|
+
* @private function of middleware
|
|
94
|
+
*/
|
|
95
|
+
export function applyContentSecurityPolicyHeader(response: NextResponse, nonce: string): void {
|
|
96
|
+
response.headers.set('Content-Security-Policy', buildContentSecurityPolicyHeaderValue(nonce));
|
|
97
|
+
}
|