Skip to content

Commit e81939d

Browse files
committed
Fix #5678 "Improve handling of expired session on the client"
- server sends 440 - client detects 440 and shows dialog
1 parent 33fce1d commit e81939d

File tree

6 files changed

+55
-44
lines changed

6 files changed

+55
-44
lines changed

common/shared/src/main/scala/org/orbeon/oxf/http/HttpStatusCodeException.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,6 @@ case class HttpRedirectException(
3636
}
3737

3838
case class SessionExpiredException(message: String) extends HttpStatusCode {
39-
val code = StatusCode.Forbidden
39+
val code = StatusCode.LoginTimeOut
4040
override def toString = s"SessionExpiredException(message= $message)"
4141
}

common/shared/src/main/scala/org/orbeon/oxf/http/definitions.scala

+1
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ object StatusCode {
240240
val Gone = 410
241241
val RequestEntityTooLarge = 413
242242
val Locked = 423
243+
val LoginTimeOut = 440 // not standard
243244
val InternalServerError = 500
244245
val ServiceUnavailable = 503
245246

xforms-runtime/jvm/src/main/java/org/orbeon/oxf/xforms/processor/XFormsAssetServer.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ class XFormsAssetServer extends ProcessorImpl with Logging {
154154
}
155155
case None =>
156156
info(s"document not found in store while building dynamic form initialization")
157-
response.setStatus(StatusCode.Forbidden)
157+
response.setStatus(StatusCode.LoginTimeOut)
158158
}
159159
}
160160
}

xforms-runtime/shared/src/main/scala/org/orbeon/oxf/xforms/event/XFormsServer.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ object XFormsServer {
310310
allEvents option XFormsStateManager.createInitialDocumentFromStore(requestParametersForAll) match {
311311
case Some(None) =>
312312
info(s"document not found in store while computing all initialization events")
313-
ClientEvents.errorResponse(StatusCode.Forbidden) // status code debatable
313+
ClientEvents.errorResponse(StatusCode.LoginTimeOut) // status code debatable
314314
case initialContainingDocumentOptOpt =>
315315
withDocument {
316316
xmlReceiver.startPrefixMapping(XXFORMS_SHORT_PREFIX, XXFORMS_NAMESPACE_URI)

xforms-web/src/main/scala/org/orbeon/xforms/AjaxClient.scala

+38-37
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,14 @@ object AjaxClient {
113113
//
114114
// Public for `UploaderClient`
115115
def handleFailure(
116-
response : String Either dom.Document,
117-
formId : String,
118-
ignoreErrors : Boolean
116+
response : String Either dom.Document,
117+
namespacedFormId: String,
118+
ignoreErrors : Boolean
119119
): Boolean = {
120120

121121
object LoginRegexpMatcher {
122122
def unapply(s: String): Boolean = {
123-
val loginRegexp = Page.getXFormsFormFromNamespacedIdOrThrow(formId).configuration.loginPageDetectionRegexp
123+
val loginRegexp = Page.getXFormsFormFromNamespacedIdOrThrow(namespacedFormId).configuration.loginPageDetectionRegexp
124124
loginRegexp.exists(re => new js.RegExp(re).test(s))
125125
}
126126
}
@@ -138,50 +138,51 @@ object AjaxClient {
138138
val title = responseXml.getElementsByTagName("title") map (_.textContent) mkString ""
139139
val body = responseXml.getElementsByTagName("body") map (_.textContent) mkString ""
140140

141-
showError(title, body, formId, ignoreErrors)
141+
showError(title, body, namespacedFormId, ignoreErrors)
142142

143143
true
144144

145145
case Left(LoginRegexpMatcher()) =>
146-
147-
// It seems we got a login page back, so display dialog and reload form
148-
val dialogEl = $(s"#$formId .xforms-login-detected-dialog")
149-
150-
def getUniqueId(prefix: String): String = {
151-
var i = 0
152-
var r: String = null
153-
do {
154-
r = prefix + i
155-
i += 1
156-
} while (dom.document.getElementById(r) ne null)
157-
r
158-
}
159-
160-
// Link dialog with title for ARIA
161-
val title = dialogEl.find("h4")
162-
if (title.attr("id").isEmpty) {
163-
val titleId = getUniqueId("xf-aria-dialog-title-")
164-
title.attr("id", titleId)
165-
dialogEl.attr("aria-labelledby", titleId)
166-
}
167-
168-
dialogEl.find("button").one("click.xf", ((_: JQueryEventObject) => {
169-
// Reloading the page will redirect us to the login page if necessary
170-
dom.window.location.href = dom.window.location.href
171-
}): js.Function1[JQueryEventObject, js.Any])
172-
dialogEl.asInstanceOf[js.Dynamic].modal(new js.Object {
173-
val backdrop = "static" // Click on the background doesn't hide dialog
174-
val keyboard = false // Can't use esc to close the dialog
175-
})
176-
146+
showLoginDetectedDialog(namespacedFormId)
177147
true
178-
179148
case _ =>
180149
// This will cause a retry for event requests (but not uploads)
181150
false
182151
}
183152
}
184153

154+
def showLoginDetectedDialog(formId: String): Unit = {
155+
// It seems we got a login page back, so display dialog and reload form
156+
val dialogEl = $(s"#$formId .xforms-login-detected-dialog")
157+
158+
def getUniqueId(prefix: String): String = {
159+
var i = 0
160+
var r: String = null
161+
do {
162+
r = prefix + i
163+
i += 1
164+
} while (dom.document.getElementById(r) ne null)
165+
r
166+
}
167+
168+
// Link dialog with title for ARIA
169+
val title = dialogEl.find("h4")
170+
if (title.attr("id").isEmpty) {
171+
val titleId = getUniqueId("xf-aria-dialog-title-")
172+
title.attr("id", titleId)
173+
dialogEl.attr("aria-labelledby", titleId)
174+
}
175+
176+
dialogEl.find("button").one("click.xf", ((_: JQueryEventObject) => {
177+
// Reloading the page will redirect us to the login page if necessary
178+
dom.window.location.href = dom.window.location.href
179+
}): js.Function1[JQueryEventObject, js.Any])
180+
dialogEl.asInstanceOf[js.Dynamic].modal(new js.Object {
181+
val backdrop = "static" // Click on the background doesn't hide dialog
182+
val keyboard = false // Can't use esc to close the dialog
183+
})
184+
}
185+
185186
// Create a timer which after the specified delay will fire a server event
186187
// 2020-07-21: Only for upload response
187188
@JSExport

xforms-web/src/main/scala/org/orbeon/xforms/rpc/RemoteClientServerChannel.scala

+13-4
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ package org.orbeon.xforms.rpc
1515

1616
import cats.data.NonEmptyList
1717
import cats.syntax.option._
18+
import org.orbeon.oxf.http.StatusCode.{LoginTimeOut, ServiceUnavailable}
1819
import org.orbeon.oxf.util.ContentTypes
1920
import org.orbeon.xforms
2021
import org.orbeon.xforms.AjaxClient.handleFailure
21-
import org.orbeon.xforms.{AjaxRequest, Page, Support}
22+
import org.orbeon.xforms.{AjaxClient, AjaxRequest, Page, Support}
2223
import org.scalajs.dom
2324
import org.scalajs.dom.FormData
2425
import org.scalajs.dom.experimental.AbortController
@@ -92,22 +93,30 @@ object RemoteClientServerChannel extends ClientServerChannel[xforms.Form, dom.Do
9293
// We ignore HTTP status and just check that we have a well-formed response document
9394
Page.loadingIndicator().requestEnded(showProgress)
9495
promise.success(responseXml)
95-
case Success((503, _, _)) =>
96+
case Success((LoginTimeOut, _, _)) =>
97+
// https://github.com/orbeon/orbeon-forms/issues/5678
98+
AjaxClient.showLoginDetectedDialog(requestForm.namespacedFormId)
99+
Page.loadingIndicator().requestEnded(showProgress)
100+
// The `Failure` is ignored by the caller of `sendEvents()`
101+
promise.failure(new Throwable) // TODO: It would be good to return another error type.
102+
case Success((ServiceUnavailable, _, _)) =>
96103
// The server returns an explicit 503 when the Ajax server is still busy
97104
retryRequestAfterDelay(requestForm, () =>
98105
asyncAjaxRequestWithRetry(requestForm, requestBody, showProgress, ignoreErrors = ignoreErrors) onComplete
99106
promise.complete
100107
)
101108
case Success((_, responseText, responseXmlOpt)) =>
102109
// Retry if we DON'T have an explicit error doc or a login
103-
if (! handleFailure(responseXmlOpt.toRight(responseText), requestForm.namespacedFormId, ignoreErrors))
110+
if (! handleFailure(responseXmlOpt.toRight(responseText), requestForm.namespacedFormId, ignoreErrors)) {
104111
retryRequestAfterDelay(requestForm, () =>
105112
asyncAjaxRequestWithRetry(requestForm, requestBody, showProgress, ignoreErrors = ignoreErrors) onComplete
106113
promise.complete
107114
)
108-
else {
115+
} else {
109116
Page.loadingIndicator().requestEnded(showProgress)
110117
// This was handled by showing a dialog or login
118+
// 2023-08-21: What does the above comment mean?
119+
// The `Failure` is ignored by the caller of `sendEvents()`
111120
promise.failure(new Throwable) // TODO: It would be good to return another error type.
112121
}
113122
case Failure(_) =>

0 commit comments

Comments
 (0)