@salesforce/webapp-template-feature-react-authentication-experimental 1.109.6 → 1.109.7
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/dist/CHANGELOG.md +8 -0
- package/dist/force-app/main/default/webapplications/feature-react-authentication/package.json +3 -3
- package/dist/force-app/main/default/webapplications/feature-react-authentication/src/features/authentication/sessionTimeout/SessionTimeoutValidator.tsx +8 -16
- package/dist/force-app/main/default/webapplications/feature-react-authentication/src/features/authentication/sessionTimeout/sessionTimeService.ts +30 -42
- package/dist/package-lock.json +2 -2
- package/dist/package.json +1 -1
- package/package.json +2 -2
package/dist/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [1.109.7](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.109.6...v1.109.7) (2026-03-19)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
6
14
|
## [1.109.6](https://github.com/salesforce-experience-platform-emu/webapps/compare/v1.109.5...v1.109.6) (2026-03-19)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @salesforce/webapp-template-base-sfdx-project-experimental
|
package/dist/force-app/main/default/webapplications/feature-react-authentication/package.json
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"graphql:schema": "node scripts/get-graphql-schema.mjs"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@salesforce/sdk-data": "^1.109.
|
|
19
|
-
"@salesforce/webapp-experimental": "^1.109.
|
|
18
|
+
"@salesforce/sdk-data": "^1.109.7",
|
|
19
|
+
"@salesforce/webapp-experimental": "^1.109.7",
|
|
20
20
|
"@tailwindcss/vite": "^4.1.17",
|
|
21
21
|
"class-variance-authority": "^0.7.1",
|
|
22
22
|
"clsx": "^2.1.1",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"@graphql-eslint/eslint-plugin": "^4.1.0",
|
|
44
44
|
"@graphql-tools/utils": "^11.0.0",
|
|
45
45
|
"@playwright/test": "^1.49.0",
|
|
46
|
-
"@salesforce/vite-plugin-webapp-experimental": "^1.109.
|
|
46
|
+
"@salesforce/vite-plugin-webapp-experimental": "^1.109.7",
|
|
47
47
|
"@testing-library/jest-dom": "^6.6.3",
|
|
48
48
|
"@testing-library/react": "^16.1.0",
|
|
49
49
|
"@testing-library/user-event": "^14.5.2",
|
|
@@ -253,19 +253,14 @@ function useSessionTimeout(config: SessionTimeoutConfig): SessionTimeoutResult {
|
|
|
253
253
|
isPollingRef.current = true;
|
|
254
254
|
setIsPolling(true);
|
|
255
255
|
|
|
256
|
-
|
|
257
|
-
|
|
256
|
+
const response = await pollSessionTimeServlet(basePath);
|
|
257
|
+
isPollingRef.current = false;
|
|
258
|
+
setIsPolling(false);
|
|
258
259
|
|
|
259
|
-
|
|
260
|
-
isPollingRef.current = false;
|
|
261
|
-
setIsPolling(false);
|
|
260
|
+
if (response) {
|
|
262
261
|
retry.resetRetry();
|
|
263
262
|
processTimeoutResponse(response.sr);
|
|
264
|
-
}
|
|
265
|
-
console.error("[useSessionTimeout] Poll failed:", error);
|
|
266
|
-
// Reset polling flags before retry so handleRetryWithBackoff doesn't skip
|
|
267
|
-
isPollingRef.current = false;
|
|
268
|
-
setIsPolling(false);
|
|
263
|
+
} else {
|
|
269
264
|
handleRetryWithBackoff(() => checkSessionRef.current?.());
|
|
270
265
|
}
|
|
271
266
|
}, [basePath, retry, processTimeoutResponse, handleRetryWithBackoff]);
|
|
@@ -274,15 +269,12 @@ function useSessionTimeout(config: SessionTimeoutConfig): SessionTimeoutResult {
|
|
|
274
269
|
* Extend the session (called when user clicks "Continue Working")
|
|
275
270
|
*/
|
|
276
271
|
const extendSession = useCallback(async () => {
|
|
277
|
-
|
|
278
|
-
const response = await extendSessionTime(basePath);
|
|
272
|
+
const response = await extendSessionTime(basePath);
|
|
279
273
|
|
|
280
|
-
|
|
274
|
+
if (response) {
|
|
281
275
|
retry.resetRetry();
|
|
282
276
|
processTimeoutResponse(response.sr);
|
|
283
|
-
}
|
|
284
|
-
console.error("[useSessionTimeout] Failed to extend session:", error);
|
|
285
|
-
// On failure, retry extending session (not checkSession)
|
|
277
|
+
} else {
|
|
286
278
|
handleRetryWithBackoff(() => extendSessionRef.current?.());
|
|
287
279
|
}
|
|
288
280
|
}, [basePath, retry, processTimeoutResponse, handleRetryWithBackoff]);
|
|
@@ -20,10 +20,9 @@ export interface SessionResponse {
|
|
|
20
20
|
* Handles CSRF protection prefix
|
|
21
21
|
*
|
|
22
22
|
* @param text - Raw response text from servlet
|
|
23
|
-
* @returns Parsed session response
|
|
24
|
-
* @throws Error if response cannot be parsed
|
|
23
|
+
* @returns Parsed session response, or undefined if parsing fails
|
|
25
24
|
*/
|
|
26
|
-
function parseResponseResult(text: string): SessionResponse {
|
|
25
|
+
function parseResponseResult(text: string): SessionResponse | undefined {
|
|
27
26
|
let cleanedText = text;
|
|
28
27
|
|
|
29
28
|
// Strip CSRF protection prefix if present
|
|
@@ -45,25 +44,23 @@ function parseResponseResult(text: string): SessionResponse {
|
|
|
45
44
|
return parsed;
|
|
46
45
|
} catch (error) {
|
|
47
46
|
console.error("[sessionTimeService] Failed to parse response:", error, "Text:", cleanedText);
|
|
48
|
-
throw new Error(
|
|
49
|
-
`Failed to parse session response: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
50
|
-
);
|
|
51
47
|
}
|
|
52
48
|
}
|
|
53
49
|
|
|
54
50
|
/**
|
|
55
51
|
* Call SessionTimeServlet API
|
|
56
|
-
* Internal function used by both poll and extend functions
|
|
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.
|
|
57
55
|
*
|
|
58
56
|
* @param basePath - Community base path (e.g., "/sfsites/c/")
|
|
59
57
|
* @param extend - Whether to extend the session (updateTimedOutSession param)
|
|
60
|
-
* @returns Session response with remaining time
|
|
61
|
-
* @throws Error if API call fails or security checks fail
|
|
58
|
+
* @returns Session response with remaining time, or undefined on failure
|
|
62
59
|
*/
|
|
63
60
|
async function callSessionTimeServlet(
|
|
64
61
|
basePath: string,
|
|
65
62
|
extend: boolean = false,
|
|
66
|
-
): Promise<SessionResponse> {
|
|
63
|
+
): Promise<SessionResponse | undefined> {
|
|
67
64
|
// Build URL with cache-busting timestamp
|
|
68
65
|
const timestamp = Date.now();
|
|
69
66
|
let url = `${basePath}${SESSION_CONFIG.SERVLET_URL}?buster=${timestamp}`;
|
|
@@ -75,47 +72,37 @@ async function callSessionTimeServlet(
|
|
|
75
72
|
try {
|
|
76
73
|
const response = await fetch(url, {
|
|
77
74
|
method: "GET",
|
|
78
|
-
credentials: "same-origin",
|
|
75
|
+
credentials: "same-origin",
|
|
79
76
|
cache: "no-cache",
|
|
80
|
-
// Security headers
|
|
81
77
|
headers: {
|
|
82
|
-
"X-Requested-With": "XMLHttpRequest",
|
|
78
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
83
79
|
},
|
|
84
80
|
});
|
|
85
81
|
|
|
86
82
|
if (!response.ok) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
throw new Error("Session expired or unauthorized");
|
|
90
|
-
} else if (response.status === 403) {
|
|
91
|
-
throw new Error("Access forbidden");
|
|
92
|
-
} else if (response.status === 404) {
|
|
93
|
-
throw new Error("Session endpoint not found");
|
|
94
|
-
} else {
|
|
95
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
96
|
-
}
|
|
83
|
+
console.error(`[sessionTimeService] HTTP ${response.status}: ${response.statusText}`);
|
|
84
|
+
return undefined;
|
|
97
85
|
}
|
|
98
86
|
|
|
99
|
-
// Security: Validate content type (should be text or JSON)
|
|
100
87
|
const contentType = response.headers.get("content-type");
|
|
101
88
|
if (contentType && !contentType.includes("text") && !contentType.includes("json")) {
|
|
102
|
-
|
|
89
|
+
console.error(`[sessionTimeService] Unexpected content type: ${contentType}`);
|
|
90
|
+
return undefined;
|
|
103
91
|
}
|
|
104
92
|
|
|
105
93
|
const text = await response.text();
|
|
106
94
|
const parsed = parseResponseResult(text);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
95
|
+
if (!parsed) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
110
98
|
|
|
111
99
|
return {
|
|
112
100
|
sp: parsed.sp,
|
|
113
|
-
sr:
|
|
101
|
+
sr: Math.max(0, parsed.sr - SESSION_CONFIG.LATENCY_BUFFER_SECONDS),
|
|
114
102
|
};
|
|
115
103
|
} catch (error) {
|
|
116
|
-
// Don't log the full URL in production to avoid leaking sensitive info
|
|
117
104
|
console.error("[sessionTimeService] API call failed:", error);
|
|
118
|
-
|
|
105
|
+
return undefined;
|
|
119
106
|
}
|
|
120
107
|
}
|
|
121
108
|
|
|
@@ -124,17 +111,17 @@ async function callSessionTimeServlet(
|
|
|
124
111
|
* Called periodically to monitor session status
|
|
125
112
|
*
|
|
126
113
|
* @param basePath - Community base path (e.g., "/sfsites/c/")
|
|
127
|
-
* @returns Session response with remaining time
|
|
128
|
-
* @throws Error if API call fails
|
|
114
|
+
* @returns Session response with remaining time, or undefined on failure
|
|
129
115
|
*
|
|
130
116
|
* @example
|
|
131
|
-
* const
|
|
132
|
-
* if (sr <= 300) {
|
|
133
|
-
* // Less than 5 minutes remaining
|
|
117
|
+
* const response = await pollSessionTimeServlet('/sfsites/c/');
|
|
118
|
+
* if (response && response.sr <= 300) {
|
|
134
119
|
* showWarning();
|
|
135
120
|
* }
|
|
136
121
|
*/
|
|
137
|
-
export async function pollSessionTimeServlet(
|
|
122
|
+
export async function pollSessionTimeServlet(
|
|
123
|
+
basePath: string,
|
|
124
|
+
): Promise<SessionResponse | undefined> {
|
|
138
125
|
return callSessionTimeServlet(basePath, false);
|
|
139
126
|
}
|
|
140
127
|
|
|
@@ -143,14 +130,15 @@ export async function pollSessionTimeServlet(basePath: string): Promise<SessionR
|
|
|
143
130
|
* Called when user clicks "Continue Working" in warning modal
|
|
144
131
|
*
|
|
145
132
|
* @param basePath - Community base path (e.g., "/sfsites/c/")
|
|
146
|
-
* @returns Session response with new remaining time
|
|
147
|
-
* @throws Error if API call fails
|
|
133
|
+
* @returns Session response with new remaining time, or undefined on failure
|
|
148
134
|
*
|
|
149
135
|
* @example
|
|
150
|
-
* const
|
|
151
|
-
*
|
|
136
|
+
* const response = await extendSessionTime('/sfsites/c/');
|
|
137
|
+
* if (response) {
|
|
138
|
+
* console.log(`Session extended. ${response.sr} seconds remaining.`);
|
|
139
|
+
* }
|
|
152
140
|
*/
|
|
153
|
-
export async function extendSessionTime(basePath: string): Promise<SessionResponse> {
|
|
141
|
+
export async function extendSessionTime(basePath: string): Promise<SessionResponse | undefined> {
|
|
154
142
|
return callSessionTimeServlet(basePath, true);
|
|
155
143
|
}
|
|
156
144
|
|
package/dist/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/webapp-template-base-sfdx-project-experimental",
|
|
3
|
-
"version": "1.109.
|
|
3
|
+
"version": "1.109.7",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@salesforce/webapp-template-base-sfdx-project-experimental",
|
|
9
|
-
"version": "1.109.
|
|
9
|
+
"version": "1.109.7",
|
|
10
10
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"@lwc/eslint-plugin-lwc": "^3.3.0",
|
package/dist/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/webapp-template-feature-react-authentication-experimental",
|
|
3
|
-
"version": "1.109.
|
|
3
|
+
"version": "1.109.7",
|
|
4
4
|
"description": "Authentication feature for web applications",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"author": "",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"clean": "rm -rf dist"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@salesforce/webapp-experimental": "^1.109.
|
|
19
|
+
"@salesforce/webapp-experimental": "^1.109.7",
|
|
20
20
|
"@tanstack/react-form": "^1.27.7",
|
|
21
21
|
"@types/react": "^19.2.7",
|
|
22
22
|
"@types/react-dom": "^19.2.3",
|