@salesforce/ui-bundle-template-app-react-template-b2x 1.117.2
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/LICENSE.txt +82 -0
- package/README.md +52 -0
- package/dist/.forceignore +15 -0
- package/dist/.husky/pre-commit +4 -0
- package/dist/.prettierignore +11 -0
- package/dist/.prettierrc +17 -0
- package/dist/AGENT.md +193 -0
- package/dist/CHANGELOG.md +2128 -0
- package/dist/README.md +72 -0
- package/dist/config/project-scratch-def.json +13 -0
- package/dist/eslint.config.js +7 -0
- package/dist/force-app/main/default/classes/UIBundleAuthUtils.cls +68 -0
- package/dist/force-app/main/default/classes/UIBundleAuthUtils.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/UIBundleChangePassword.cls +77 -0
- package/dist/force-app/main/default/classes/UIBundleChangePassword.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/UIBundleForgotPassword.cls +71 -0
- package/dist/force-app/main/default/classes/UIBundleForgotPassword.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/UIBundleLogin.cls +105 -0
- package/dist/force-app/main/default/classes/UIBundleLogin.cls-meta.xml +5 -0
- package/dist/force-app/main/default/classes/UIBundleRegistration.cls +162 -0
- package/dist/force-app/main/default/classes/UIBundleRegistration.cls-meta.xml +5 -0
- package/dist/force-app/main/default/digitalExperienceConfigs/reactexternalapp1.digitalExperienceConfig-meta.xml +8 -0
- package/dist/force-app/main/default/digitalExperiences/site/reactexternalapp1/reactexternalapp1.digitalExperience-meta.xml +11 -0
- package/dist/force-app/main/default/digitalExperiences/site/reactexternalapp1/sfdc_cms__site/reactexternalapp1/_meta.json +5 -0
- package/dist/force-app/main/default/digitalExperiences/site/reactexternalapp1/sfdc_cms__site/reactexternalapp1/content.json +10 -0
- package/dist/force-app/main/default/networks/reactexternalapp.network-meta.xml +60 -0
- package/dist/force-app/main/default/package.xml +20 -0
- package/dist/force-app/main/default/sites/reactexternalapp.site-meta.xml +31 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/.forceignore +15 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/.graphqlrc.yml +2 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/.prettierignore +9 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/.prettierrc +11 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/CHANGELOG.md +10 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/README.md +35 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/codegen.yml +95 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/components.json +18 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/e2e/app.spec.ts +17 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/eslint.config.js +169 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/index.html +12 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/package.json +70 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/playwright.config.ts +24 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/reactexternalapp.uibundle-meta.xml +8 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/scripts/get-graphql-schema.mjs +68 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/scripts/rewrite-e2e-assets.mjs +23 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/account/accountSearchService.ts +46 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/account/query/distinctAccountIndustries.graphql +19 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/account/query/distinctAccountTypes.graphql +19 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/account/query/getAccountDetail.graphql +121 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/account/query/searchAccounts.graphql +51 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/graphql-operations-types.ts +11260 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/api/graphqlClient.ts +25 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/app.tsx +16 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/appLayout.tsx +83 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/assets/icons/book.svg +3 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/assets/icons/copy.svg +4 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/assets/icons/rocket.svg +3 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/assets/icons/star.svg +3 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/assets/images/codey-1.png +0 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/assets/images/codey-2.png +0 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/assets/images/codey-3.png +0 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/assets/images/vibe-codey.svg +194 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/alerts/status-alert.tsx +49 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/layouts/card-layout.tsx +29 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/alert.tsx +76 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/badge.tsx +48 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/breadcrumb.tsx +109 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/button.tsx +67 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/calendar.tsx +232 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/card.tsx +103 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/checkbox.tsx +32 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/collapsible.tsx +33 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/datePicker.tsx +127 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/dialog.tsx +162 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/field.tsx +237 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/index.ts +84 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/input.tsx +19 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/label.tsx +22 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/pagination.tsx +132 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/popover.tsx +89 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/select.tsx +193 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/separator.tsx +26 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/skeleton.tsx +14 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/sonner.tsx +20 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/spinner.tsx +16 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/table.tsx +114 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/components/ui/tabs.tsx +88 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/api/userProfileApi.ts +95 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/authHelpers.ts +73 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/authenticationConfig.ts +61 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/context/AuthContext.tsx +95 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/footers/footer-link.tsx +36 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/forms/auth-form.tsx +81 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/forms/submit-button.tsx +49 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/hooks/form.tsx +120 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/hooks/useCountdownTimer.ts +266 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/hooks/useRetryWithBackoff.ts +109 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/layout/card-skeleton.tsx +38 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/layout/centered-page-layout.tsx +87 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/layouts/AuthAppLayout.tsx +12 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/layouts/authenticationRouteLayout.tsx +21 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/layouts/privateRouteLayout.tsx +44 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/pages/ChangePassword.tsx +107 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/pages/ForgotPassword.tsx +73 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/pages/Login.tsx +97 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/pages/Profile.tsx +161 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/pages/Register.tsx +133 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/pages/ResetPassword.tsx +107 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/sessionTimeout/SessionTimeoutValidator.tsx +602 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/sessionTimeout/sessionTimeService.ts +149 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/sessionTimeout/sessionTimeoutConfig.ts +77 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/authentication/utils/helpers.ts +121 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/api/accountSearchService.ts +46 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/api/query/distinctAccountIndustries.graphql +19 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/api/query/distinctAccountTypes.graphql +19 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/api/query/getAccountDetail.graphql +121 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/api/query/searchAccounts.graphql +51 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/pages/AccountObjectDetailPage.tsx +357 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/pages/AccountSearch.tsx +312 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/__examples__/pages/Home.tsx +34 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/api/objectSearchService.ts +84 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/ActiveFilters.tsx +89 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/FilterContext.tsx +83 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/ObjectBreadcrumb.tsx +66 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/PaginationControls.tsx +109 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/SearchBar.tsx +41 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/SortControl.tsx +143 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/BooleanFilter.tsx +78 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/DateFilter.tsx +128 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/DateRangeFilter.tsx +70 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/FilterFieldWrapper.tsx +33 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/MultiSelectFilter.tsx +97 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/NumericRangeFilter.tsx +163 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/SearchFilter.tsx +50 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/SelectFilter.tsx +97 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/components/filters/TextFilter.tsx +91 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/hooks/useAsyncData.ts +54 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/hooks/useCachedAsyncData.ts +184 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/hooks/useDebouncedCallback.ts +34 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/hooks/useObjectSearchParams.ts +252 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/utils/debounce.ts +25 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/utils/fieldUtils.ts +29 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/utils/filterUtils.ts +395 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/features/object-search/utils/sortUtils.ts +38 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/lib/utils.ts +6 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/navigationMenu.tsx +80 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/pages/AccountObjectDetailPage.tsx +361 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/pages/AccountSearch.tsx +305 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/pages/Home.tsx +34 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/pages/NotFound.tsx +18 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/router-utils.tsx +35 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/routes.tsx +81 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/src/styles/global.css +135 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/tsconfig.json +42 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/tsconfig.node.json +13 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/ui-bundle.json +7 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/vite-env.d.ts +1 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/vite.config.ts +106 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/vitest-env.d.ts +2 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/vitest.config.ts +11 -0
- package/dist/force-app/main/default/uiBundles/reactexternalapp/vitest.setup.ts +1 -0
- package/dist/jest.config.js +6 -0
- package/dist/package-lock.json +9995 -0
- package/dist/package.json +40 -0
- package/dist/scripts/apex/hello.apex +10 -0
- package/dist/scripts/graphql-search.sh +191 -0
- package/dist/scripts/prepare-import-unique-fields.js +122 -0
- package/dist/scripts/setup-cli.mjs +563 -0
- package/dist/scripts/sf-project-setup.mjs +66 -0
- package/dist/scripts/soql/account.soql +6 -0
- package/dist/sfdx-project.json +12 -0
- package/package.json +40 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SessionTimeServlet API service
|
|
3
|
+
* Handles communication with the session validation endpoint
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { SESSION_CONFIG } from "./sessionTimeoutConfig";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Response from SessionTimeServlet API
|
|
10
|
+
*/
|
|
11
|
+
export interface SessionResponse {
|
|
12
|
+
/** Session phase */
|
|
13
|
+
sp: number;
|
|
14
|
+
/** Seconds remaining in session */
|
|
15
|
+
sr: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Parse the servlet response text into SessionResponse object
|
|
20
|
+
* Handles CSRF protection prefix
|
|
21
|
+
*
|
|
22
|
+
* @param text - Raw response text from servlet
|
|
23
|
+
* @returns Parsed session response, or undefined if parsing fails
|
|
24
|
+
*/
|
|
25
|
+
function parseResponseResult(text: string): SessionResponse | undefined {
|
|
26
|
+
let cleanedText = text;
|
|
27
|
+
|
|
28
|
+
// Strip CSRF protection prefix if present
|
|
29
|
+
if (cleanedText.startsWith(SESSION_CONFIG.CSRF_TOKEN)) {
|
|
30
|
+
cleanedText = cleanedText.substring(SESSION_CONFIG.CSRF_TOKEN.length);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Trim whitespace
|
|
34
|
+
cleanedText = cleanedText.trim();
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(cleanedText) as SessionResponse;
|
|
38
|
+
|
|
39
|
+
// Validate response structure
|
|
40
|
+
if (typeof parsed.sp !== "number" || typeof parsed.sr !== "number") {
|
|
41
|
+
throw new Error("Invalid response structure: missing sp or sr properties");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return parsed;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error("[sessionTimeService] Failed to parse response:", error, "Text:", cleanedText);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Call SessionTimeServlet API
|
|
52
|
+
* Internal function used by both poll and extend functions.
|
|
53
|
+
* Returns undefined on any failure so the session timeout feature
|
|
54
|
+
* never crashes the running application.
|
|
55
|
+
*
|
|
56
|
+
* @param basePath - Community base path (e.g., "/sfsites/c/")
|
|
57
|
+
* @param extend - Whether to extend the session (updateTimedOutSession param)
|
|
58
|
+
* @returns Session response with remaining time, or undefined on failure
|
|
59
|
+
*/
|
|
60
|
+
async function callSessionTimeServlet(
|
|
61
|
+
basePath: string,
|
|
62
|
+
extend: boolean = false,
|
|
63
|
+
): Promise<SessionResponse | undefined> {
|
|
64
|
+
// Build URL with cache-busting timestamp
|
|
65
|
+
const timestamp = Date.now();
|
|
66
|
+
let url = `${basePath}${SESSION_CONFIG.SERVLET_URL}?buster=${timestamp}`;
|
|
67
|
+
|
|
68
|
+
if (extend) {
|
|
69
|
+
url += "&updateTimedOutSession=true";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const response = await fetch(url, {
|
|
74
|
+
method: "GET",
|
|
75
|
+
credentials: "same-origin",
|
|
76
|
+
cache: "no-cache",
|
|
77
|
+
headers: {
|
|
78
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!response.ok) {
|
|
83
|
+
console.error(`[sessionTimeService] HTTP ${response.status}: ${response.statusText}`);
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const contentType = response.headers.get("content-type");
|
|
88
|
+
if (contentType && !contentType.includes("text") && !contentType.includes("json")) {
|
|
89
|
+
console.error(`[sessionTimeService] Unexpected content type: ${contentType}`);
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const text = await response.text();
|
|
94
|
+
const parsed = parseResponseResult(text);
|
|
95
|
+
if (!parsed) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
sp: parsed.sp,
|
|
101
|
+
sr: Math.max(0, parsed.sr - SESSION_CONFIG.LATENCY_BUFFER_SECONDS),
|
|
102
|
+
};
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error("[sessionTimeService] API call failed:", error);
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Poll SessionTimeServlet to check remaining session time
|
|
111
|
+
* Called periodically to monitor session status
|
|
112
|
+
*
|
|
113
|
+
* @param basePath - Community base path (e.g., "/sfsites/c/")
|
|
114
|
+
* @returns Session response with remaining time, or undefined on failure
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* const response = await pollSessionTimeServlet('/sfsites/c/');
|
|
118
|
+
* if (response && response.sr <= 300) {
|
|
119
|
+
* showWarning();
|
|
120
|
+
* }
|
|
121
|
+
*/
|
|
122
|
+
export async function pollSessionTimeServlet(
|
|
123
|
+
basePath: string,
|
|
124
|
+
): Promise<SessionResponse | undefined> {
|
|
125
|
+
return callSessionTimeServlet(basePath, false);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Extend the current session time
|
|
130
|
+
* Called when user clicks "Continue Working" in warning modal
|
|
131
|
+
*
|
|
132
|
+
* @param basePath - Community base path (e.g., "/sfsites/c/")
|
|
133
|
+
* @returns Session response with new remaining time, or undefined on failure
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* const response = await extendSessionTime('/sfsites/c/');
|
|
137
|
+
* if (response) {
|
|
138
|
+
* console.log(`Session extended. ${response.sr} seconds remaining.`);
|
|
139
|
+
* }
|
|
140
|
+
*/
|
|
141
|
+
export async function extendSessionTime(basePath: string): Promise<SessionResponse | undefined> {
|
|
142
|
+
return callSessionTimeServlet(basePath, true);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Export parseResponseResult for testing purposes
|
|
147
|
+
* @internal
|
|
148
|
+
*/
|
|
149
|
+
export { parseResponseResult };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration constants for session timeout monitoring
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// Retry Configuration
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
/** Initial delay for first retry attempt (2 seconds) */
|
|
10
|
+
export const INITIAL_RETRY_DELAY = 2000;
|
|
11
|
+
|
|
12
|
+
/** Maximum number of retry attempts before giving up */
|
|
13
|
+
export const MAX_RETRY_ATTEMPTS = 10;
|
|
14
|
+
|
|
15
|
+
/** Maximum retry delay (30 minutes) */
|
|
16
|
+
export const MAX_RETRY_DELAY = 30 * 60 * 1000;
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Session Storage Keys
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
/** sessionStorage keys used by session validator */
|
|
23
|
+
export const STORAGE_KEYS = {
|
|
24
|
+
/** Flag to show session expired message on login page */
|
|
25
|
+
SHOW_SESSION_MESSAGE: "lwrSessionValidator.showSessionMessage",
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Servlet Configuration
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
/** SessionTimeServlet configuration */
|
|
33
|
+
export const SESSION_CONFIG = {
|
|
34
|
+
/** Relative URL to SessionTimeServlet */
|
|
35
|
+
SERVLET_URL: "/sfsites/c/_nc_external/system/security/session/SessionTimeServlet",
|
|
36
|
+
|
|
37
|
+
/** Latency buffer to subtract from server response (seconds) */
|
|
38
|
+
LATENCY_BUFFER_SECONDS: 3,
|
|
39
|
+
|
|
40
|
+
/** CSRF protection prefix in servlet responses */
|
|
41
|
+
CSRF_TOKEN: "while(1);\n",
|
|
42
|
+
} as const;
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// UI Labels
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* UI labels for session timeout components
|
|
50
|
+
*/
|
|
51
|
+
export const LABELS = {
|
|
52
|
+
/** Title for session warning modal */
|
|
53
|
+
sessionWarningTitle: "Session Timeout Warning",
|
|
54
|
+
|
|
55
|
+
/** Message text in session warning modal */
|
|
56
|
+
sessionWarningMessage:
|
|
57
|
+
"For security, we log you out if you’re inactive for too long. To continue working, click Continue before the time expires.",
|
|
58
|
+
|
|
59
|
+
/** Text for "Continue" button */
|
|
60
|
+
continueButton: "Continue",
|
|
61
|
+
|
|
62
|
+
/** Text for "Log Out" button */
|
|
63
|
+
logoutButton: "Log Out",
|
|
64
|
+
|
|
65
|
+
/** Message shown on login page after session expires */
|
|
66
|
+
invalidSessionMessage: "Your session has expired. Please log in again.",
|
|
67
|
+
|
|
68
|
+
/** Accessibility label for close button */
|
|
69
|
+
closeLabel: "Close",
|
|
70
|
+
} as const;
|
|
71
|
+
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// Session Timeout Configuration
|
|
74
|
+
// ============================================================================
|
|
75
|
+
|
|
76
|
+
/** Session warning time in seconds (30 seconds) */
|
|
77
|
+
export const SESSION_WARNING_TIME = 30;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MAINTAINABILITY: Robust error extraction.
|
|
3
|
+
* Handles strings, objects, and standard Error instances.
|
|
4
|
+
*
|
|
5
|
+
* @param err - The error object (unknown type)
|
|
6
|
+
* @param fallback - Fallback message if error doesn't have a message property
|
|
7
|
+
* @returns The error message string
|
|
8
|
+
*/
|
|
9
|
+
export function getErrorMessage(err: unknown, fallback: string): string {
|
|
10
|
+
if (err instanceof Error) return err.message;
|
|
11
|
+
if (typeof err === "string") return err;
|
|
12
|
+
// Check if it's an object with a message property
|
|
13
|
+
if (typeof err === "object" && err !== null && "message" in err) {
|
|
14
|
+
return String((err as { message: unknown }).message);
|
|
15
|
+
}
|
|
16
|
+
return fallback;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* [Dev Note] Helper to parse the fetch Response.
|
|
21
|
+
* It handles the distinction between success (JSON) and failure (throwing Error).
|
|
22
|
+
*/
|
|
23
|
+
export async function handleApiResponse<T = unknown>(
|
|
24
|
+
response: Response,
|
|
25
|
+
fallbackError: string,
|
|
26
|
+
): Promise<T> {
|
|
27
|
+
// 1. Robustness: Handle 204 No Content gracefully
|
|
28
|
+
if (response.status === 204) {
|
|
29
|
+
return {} as T;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let data: any = null;
|
|
33
|
+
|
|
34
|
+
const contentType = response.headers.get("content-type");
|
|
35
|
+
if (contentType?.includes("application/json")) {
|
|
36
|
+
data = await response.json();
|
|
37
|
+
} else {
|
|
38
|
+
// [Dev Note] If Salesforce returns HTML (e.g. standard error page),
|
|
39
|
+
// we consume text to avoid parsing errors.
|
|
40
|
+
await response.text();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
// [Dev Note] Throwing here allows the calling component to catch and
|
|
45
|
+
// display the error via getErrorMessage()
|
|
46
|
+
throw new Error(parseApiResponseError(data, fallbackError));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return data as T;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* UI API Record response structure.
|
|
54
|
+
*/
|
|
55
|
+
export type RecordResponse = {
|
|
56
|
+
fields: Record<
|
|
57
|
+
string,
|
|
58
|
+
{
|
|
59
|
+
value: string;
|
|
60
|
+
}
|
|
61
|
+
>;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* [Dev Note] GraphQL can return a complex nested structure.
|
|
66
|
+
* This helper flattens it to a simple object for easier form binding.
|
|
67
|
+
*
|
|
68
|
+
* @param data - Extracted payload from the GraphQL response.
|
|
69
|
+
* @param fallbackError - Fallback error message if data is null/undefined or not an object.
|
|
70
|
+
* @throws {Error} If data is not valid.
|
|
71
|
+
* @returns Flattened object with values mapped directly to the fields.
|
|
72
|
+
*/
|
|
73
|
+
export function flattenGraphQLRecord<T>(
|
|
74
|
+
data: any,
|
|
75
|
+
fallbackError: string = "An unknown error occurred",
|
|
76
|
+
): T {
|
|
77
|
+
if (!data || typeof data !== "object") {
|
|
78
|
+
throw new Error(fallbackError);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return Object.fromEntries(
|
|
82
|
+
Object.entries(data).map(([key, field]) => [
|
|
83
|
+
key,
|
|
84
|
+
field !== null && typeof field === "object" && "value" in field
|
|
85
|
+
? (field as { value: unknown }).value
|
|
86
|
+
: (field ?? null),
|
|
87
|
+
]),
|
|
88
|
+
) as T;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* [Dev Note] Salesforce APIs may return errors as an array or a single object.
|
|
93
|
+
* This helper standardizes the extraction of the error message string.
|
|
94
|
+
*
|
|
95
|
+
* @param data - The response data.
|
|
96
|
+
* @param fallbackError - Fallback error message if response doesn't have a message property
|
|
97
|
+
* @returns The error message string
|
|
98
|
+
*/
|
|
99
|
+
function parseApiResponseError(
|
|
100
|
+
data: any,
|
|
101
|
+
fallbackError: string = "An unknown error occurred",
|
|
102
|
+
): string {
|
|
103
|
+
if (data?.message) {
|
|
104
|
+
return data.message;
|
|
105
|
+
}
|
|
106
|
+
if (data?.error) {
|
|
107
|
+
return data.error;
|
|
108
|
+
}
|
|
109
|
+
if (data?.errors && Array.isArray(data.errors) && data.errors.length > 0) {
|
|
110
|
+
return data.errors.join(" ") || fallbackError;
|
|
111
|
+
}
|
|
112
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
113
|
+
return (
|
|
114
|
+
data
|
|
115
|
+
.map((e) => e?.message)
|
|
116
|
+
.filter(Boolean)
|
|
117
|
+
.join(" ") || fallbackError
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return fallbackError;
|
|
121
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import SEARCH_ACCOUNTS_QUERY from "./query/searchAccounts.graphql?raw";
|
|
2
|
+
import DISTINCT_INDUSTRIES_QUERY from "./query/distinctAccountIndustries.graphql?raw";
|
|
3
|
+
import DISTINCT_TYPES_QUERY from "./query/distinctAccountTypes.graphql?raw";
|
|
4
|
+
import {
|
|
5
|
+
searchObjects,
|
|
6
|
+
fetchDistinctValues,
|
|
7
|
+
type ObjectSearchOptions,
|
|
8
|
+
type PicklistOption,
|
|
9
|
+
} from "../../api/objectSearchService";
|
|
10
|
+
import type {
|
|
11
|
+
SearchAccountsQuery,
|
|
12
|
+
SearchAccountsQueryVariables,
|
|
13
|
+
DistinctAccountIndustriesQuery,
|
|
14
|
+
DistinctAccountTypesQuery,
|
|
15
|
+
} from "../../../../api/graphql-operations-types";
|
|
16
|
+
|
|
17
|
+
export type AccountSearchResult = NonNullable<SearchAccountsQuery["uiapi"]["query"]["Account"]>;
|
|
18
|
+
|
|
19
|
+
export type AccountSearchOptions = ObjectSearchOptions<
|
|
20
|
+
SearchAccountsQueryVariables["where"],
|
|
21
|
+
SearchAccountsQueryVariables["orderBy"]
|
|
22
|
+
>;
|
|
23
|
+
|
|
24
|
+
export type { PicklistOption };
|
|
25
|
+
|
|
26
|
+
export async function searchAccounts(
|
|
27
|
+
options: AccountSearchOptions = {},
|
|
28
|
+
): Promise<AccountSearchResult> {
|
|
29
|
+
return searchObjects<AccountSearchResult, SearchAccountsQuery, SearchAccountsQueryVariables>(
|
|
30
|
+
SEARCH_ACCOUNTS_QUERY,
|
|
31
|
+
"Account",
|
|
32
|
+
options,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function fetchDistinctIndustries(): Promise<PicklistOption[]> {
|
|
37
|
+
return fetchDistinctValues<DistinctAccountIndustriesQuery>(
|
|
38
|
+
DISTINCT_INDUSTRIES_QUERY,
|
|
39
|
+
"Account",
|
|
40
|
+
"Industry",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function fetchDistinctTypes(): Promise<PicklistOption[]> {
|
|
45
|
+
return fetchDistinctValues<DistinctAccountTypesQuery>(DISTINCT_TYPES_QUERY, "Account", "Type");
|
|
46
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
query DistinctAccountIndustries {
|
|
2
|
+
uiapi {
|
|
3
|
+
aggregate {
|
|
4
|
+
Account(groupBy: { Industry: { group: true } }) {
|
|
5
|
+
edges {
|
|
6
|
+
node {
|
|
7
|
+
aggregate @optional {
|
|
8
|
+
Industry @optional {
|
|
9
|
+
value
|
|
10
|
+
displayValue
|
|
11
|
+
label
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
query GetAccountDetail($id: ID!) {
|
|
2
|
+
uiapi {
|
|
3
|
+
query {
|
|
4
|
+
Account(where: { Id: { eq: $id } }) {
|
|
5
|
+
edges {
|
|
6
|
+
node {
|
|
7
|
+
Id
|
|
8
|
+
Name @optional {
|
|
9
|
+
value
|
|
10
|
+
displayValue
|
|
11
|
+
}
|
|
12
|
+
Owner @optional {
|
|
13
|
+
Name @optional {
|
|
14
|
+
value
|
|
15
|
+
displayValue
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
Phone @optional {
|
|
19
|
+
value
|
|
20
|
+
displayValue
|
|
21
|
+
}
|
|
22
|
+
Fax @optional {
|
|
23
|
+
value
|
|
24
|
+
displayValue
|
|
25
|
+
}
|
|
26
|
+
Parent @optional {
|
|
27
|
+
Name @optional {
|
|
28
|
+
value
|
|
29
|
+
displayValue
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
Website @optional {
|
|
33
|
+
value
|
|
34
|
+
displayValue
|
|
35
|
+
}
|
|
36
|
+
Type @optional {
|
|
37
|
+
value
|
|
38
|
+
displayValue
|
|
39
|
+
}
|
|
40
|
+
NumberOfEmployees @optional {
|
|
41
|
+
value
|
|
42
|
+
displayValue
|
|
43
|
+
}
|
|
44
|
+
Industry @optional {
|
|
45
|
+
value
|
|
46
|
+
displayValue
|
|
47
|
+
}
|
|
48
|
+
AnnualRevenue @optional {
|
|
49
|
+
value
|
|
50
|
+
displayValue
|
|
51
|
+
}
|
|
52
|
+
Description @optional {
|
|
53
|
+
value
|
|
54
|
+
displayValue
|
|
55
|
+
}
|
|
56
|
+
BillingStreet @optional {
|
|
57
|
+
value
|
|
58
|
+
displayValue
|
|
59
|
+
}
|
|
60
|
+
BillingCity @optional {
|
|
61
|
+
value
|
|
62
|
+
displayValue
|
|
63
|
+
}
|
|
64
|
+
BillingState @optional {
|
|
65
|
+
value
|
|
66
|
+
displayValue
|
|
67
|
+
}
|
|
68
|
+
BillingPostalCode @optional {
|
|
69
|
+
value
|
|
70
|
+
displayValue
|
|
71
|
+
}
|
|
72
|
+
BillingCountry @optional {
|
|
73
|
+
value
|
|
74
|
+
displayValue
|
|
75
|
+
}
|
|
76
|
+
ShippingStreet @optional {
|
|
77
|
+
value
|
|
78
|
+
displayValue
|
|
79
|
+
}
|
|
80
|
+
ShippingCity @optional {
|
|
81
|
+
value
|
|
82
|
+
displayValue
|
|
83
|
+
}
|
|
84
|
+
ShippingState @optional {
|
|
85
|
+
value
|
|
86
|
+
displayValue
|
|
87
|
+
}
|
|
88
|
+
ShippingPostalCode @optional {
|
|
89
|
+
value
|
|
90
|
+
displayValue
|
|
91
|
+
}
|
|
92
|
+
ShippingCountry @optional {
|
|
93
|
+
value
|
|
94
|
+
displayValue
|
|
95
|
+
}
|
|
96
|
+
CreatedBy @optional {
|
|
97
|
+
Name @optional {
|
|
98
|
+
value
|
|
99
|
+
displayValue
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
CreatedDate @optional {
|
|
103
|
+
value
|
|
104
|
+
displayValue
|
|
105
|
+
}
|
|
106
|
+
LastModifiedBy @optional {
|
|
107
|
+
Name @optional {
|
|
108
|
+
value
|
|
109
|
+
displayValue
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
LastModifiedDate @optional {
|
|
113
|
+
value
|
|
114
|
+
displayValue
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
query SearchAccounts(
|
|
2
|
+
$first: Int
|
|
3
|
+
$after: String
|
|
4
|
+
$where: Account_Filter
|
|
5
|
+
$orderBy: Account_OrderBy
|
|
6
|
+
) {
|
|
7
|
+
uiapi {
|
|
8
|
+
query {
|
|
9
|
+
Account(first: $first, after: $after, where: $where, orderBy: $orderBy) {
|
|
10
|
+
edges {
|
|
11
|
+
node {
|
|
12
|
+
Id
|
|
13
|
+
Name @optional {
|
|
14
|
+
value
|
|
15
|
+
displayValue
|
|
16
|
+
}
|
|
17
|
+
Industry @optional {
|
|
18
|
+
value
|
|
19
|
+
displayValue
|
|
20
|
+
}
|
|
21
|
+
Type @optional {
|
|
22
|
+
value
|
|
23
|
+
displayValue
|
|
24
|
+
}
|
|
25
|
+
Phone @optional {
|
|
26
|
+
value
|
|
27
|
+
displayValue
|
|
28
|
+
}
|
|
29
|
+
Owner @optional {
|
|
30
|
+
Name @optional {
|
|
31
|
+
value
|
|
32
|
+
displayValue
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
AnnualRevenue @optional {
|
|
36
|
+
value
|
|
37
|
+
displayValue
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
pageInfo {
|
|
42
|
+
hasNextPage
|
|
43
|
+
hasPreviousPage
|
|
44
|
+
endCursor
|
|
45
|
+
startCursor
|
|
46
|
+
}
|
|
47
|
+
totalCount
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|