Can IntraWeb forms be synchronized on demand? - delphi

Scenario: I have a Delphi Intraweb application that has some edit components and buttons on a screen. In the TIWEdit.OnAsyncExit and TIWButton.OnClick a flag is set, and another thread in the application sets the enabled properties of the buttons depending on the flags and some other application data.
By the time the TIWButton.Enabled properties are set, the request has already finished and the next interaction is cancelled as IW finds out that internal representation and HTML form are out of sync. It resynchonizes and you have to click again.
I would like to refresh the screen somehow on demand.
A timer that finds out whether the two are synchronized and issues a refresh has drawbacks in traffic and timing (I can click a button before a timer run).
A method that could push data would be great.
Maybe IW has a possibility to do an non-save sync without cancelling the action I just committed.
As my screens are built model driven (I cannot predict what components will be on the screen and what the interdependencies between components are, that is in the business logic), I cannot add JavaScript to enable or disable a button depending on user actions.

I am not completely sure if your question is the same as mine, yet I think there is a lot in common. See the demo project (v2) I posted in the Intraweb forum.
Based on some comments from Jackson Gomes I enable a TIWTimer before a long running thread starts and disable this after the thread has ended. See: http://forums3.atozed.com/IntraWeb.aspx (atozedsoftware.intraweb.attachments), thread 'IWLabel update via Thread', Oct 15, 2009.
The OnASync timer event is fired every 500 ms and is using some bandwith. Acceptable in my situation (company intranet).
Gert

You could use the Interop Web Module from the IWElite component pack.
Essentially you would write a bit of Javascript using the XMLHTTPRequest (XHR) object to call into your IW app's Web Module Action which returns when the processing is finished. If you need your IW app to continue to function as normal while the process is running, your Javascript could open a progress window and make the XHR call from there.
IW Elite can be found here:
http://code.google.com/p/iwelite/
An XHR request would look something like this:
function NewXHR() {
if (typeof XMLHttpRequest == "undefined") {
try { return new ActiveXObject('Msxml2.XMLHTTP.6.0');} catch(e) {}
try { return new ActiveXObject('Msxml2.XMLHTTP.3.0');} catch(e) {}
try { return new ActiveXObject('Msxml2.XMLHTTP');} catch(e) {}
try { return new ActiveXObject('Microsoft.XMLHTTP');} catch(e) {}
throw new Error('AJAX not supported in this browser.');
} else {
return = new XMLHttpRequest();
}
var xhr = NewXHR();
xhr.open("get", '/mywebaction', false);
xhr.send(null);
window.alert(xhr.responseText);
The above code will block and wait for the response. If you would rather have it act asynchronously, you could instead do the following:
var xhr = NewXHR();
xhr.open("get", '/mywebaction', true);
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
if ((xhr.status == 200) || (xhr.status == 304) || (xhr.status === 0)) {
window.alert('Success: '+xhr.responseText);
} else {
window.alert('Error: ('+xhr.status+') '+xhr.statusText;
}
}
};
xhr.send(null);

Related

Vaadin 14: remove broadcast registration on page refresh

I followed the Vaadin tutorial (Creating Collaborative Views) for broadcasting events and register on them.
Registration eventRegistration;
#Override
protected void onAttach(AttachEvent attachEvent) {
log.debug("In attach...");
UI ui = attachEvent.getUI();
eventRegistration= Broadcaster.register(
"eventName",
message -> ui.access(() -> {
log.debug("Request to refresh grid...");
presenter.refreshGrid();
ui.push();
}));
}
#Override
protected void onDetach(DetachEvent detachEvent) {
log.debug("In detach...");
if(eventRegistration != null) {
eventRegistration.remove();
eventRegistration = null;
}
}
Everything works except the fact that when refreshing the page, the logic in the onDetach() is not executed. After refresh, however, you will enter the onAttach() method. Because of this you are actually going to register several of 'the same' listeners without removing the previous one and you actually get a doubling of listeners. The onDetach() method is only accessed if you go to another menu item, for example.
You can find an example log below.
What is the Vaadin recommended way to remove these listeners before/during refresh?
The onDetach method should be called eventually.
No event is sent to the server when you close or refresh a tab, and as such the server is not aware that the old UI should be detached.
This is where the heartbeat requests come in. The UIs send heartbeat requests every 5 minutes per default, and if the server notices that the old UI has missed three heartbeats, it will be detached. Alternatively, it will be detached when the session expires.
In other words, the onDetach method should be called after about 20 minutes.
The reason no event is sent to the server when the tab is closed or refreshed is that this could prevent the tab from refreshing/closing while the request is being handled, which is bad user experience. Also, this wouldn't cover the cases where the computer is turned off or the network disconnected.
There is something called the Beacon API that could be used to notify the server when a tab is refreshed or closed without causing a delay in the browser. There is an issue for using this to immediately detach UIs.
I'd recommend using the Unload Beacon add-on: https://vaadin.com/directory/component/unload-beacon-for-vaadin-flow or a similar approach which is demonstrated in the Cookbook: https://cookbook.vaadin.com/notice-closed - essentially, it's executing the JavaScript snippet to add an event listener for Window's unload event:
ui.getElement().executeJs(
"window.addEventListener('unload', function() {navigator.sendBeacon && navigator.sendBeacon($0)})", relativeBeaconPath);
and the beacon is sent to a custom SynchronizedRequestHandler.
Simplest way to workaround the problem would be checking if eventRegistration is null before adding one.
#Override
protected void onAttach(AttachEvent attachEvent) {
log.debug("In attach...");
UI ui = attachEvent.getUI();
if (eventRegistration == null) {
eventRegistration= Broadcaster.register(
"eventName",
message -> ui.access(() -> {
log.debug("Request to refresh grid...");
presenter.refreshGrid();
ui.push();
}));
}
}
Check the other answer by Erik why calling of onDetach is delayed.

.NET Core MVC/Azure AD - 302 Found when making an ajax request

I am using Azure AD along with asp.net core mvc. The following code is the same with a default MVC project generated with Work or School Accounts authentication.
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
Everything works just fine for the most time. The app is basically a notepad. A user logs in and adds notes/tasks. Everything after logging in is done using ajax requests. After some time the app stops working because there is a need for authentication again. Note that if I refresh the page everything is working again.
Am I doing this right? Am I missing something or this kind of use case is not supported.
Should I just create a javascript function that will auto refresh the page after some time?
Should I just create a javascript function that will auto refresh the page after some time?
You could try to create a hidden iframe in all the templates used by the Web App to make automatic calls to a MVC controller method that forces a call to renew the authentication data on a regular basis.
This is achieved very easily by configuring an automatic javascript process in the front-end executed in a loop on a regular basis of 45'. This value could be configured or extracted from a configuration file too. The only key condition is that it must be less than one hour.
Here is the simplified example code related to MVC Controller:
/* Action method, inside "Account" controller class, to force renewal of user authentication session */
public void ForceSignIn()
{
HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/" },
OpenIdConnectAuthenticationDefaults.AuthenticationType);
}
And here is the simplified example HTML and javascript code employed to call silently in a hidden iframe to MVC Controller:
<iframe id="renewSession" hidden></iframe>
<script>
setInterval( function ()
{ #if (Request.IsAuthenticated) {
<text>
var renewUrl = "/Account/ForceSignIn";
var element = document.getElementById("renewSession");
element.src = renewUrl;
</text>
}
},
1000*60*45
);
</script>
For more details, you could refer to this article which get the similar situation with you.
I found a simple solution by accident. My goal was to hit a check endpoint every minute and If I get a 302 status code I would redirect the user to the authentication page.
public IActionResult Check()
{
return Ok(new { });
}
I left the developer tools open and noticed that every 30 mins I get a bigger response.
And this actually refreshes the cookie and as a result there is no need to redirect the user.
So to sum up someone needs to do this check every 40-50 minutes because the expiration is set to ~1 hour by default.

Workboxjs precacheAndRoute not serving new content

I have created an offline documentation with MkDocs and Workboxjs.
I execute workbox generateSW on the files generated by MkDocs which generates a Service Worker with precache setup with the precacheAndRoute function.
This works fine but when I update the documentation and generate new html files and the Service Worker it does not serve the new content until I completely close the browser. Refreshing or just closing the tab is not enough.
The worker is updating the content to the Cache Storage correctly which I can see from the Chrome devtools (Application -> Cache Storage -> workbox-precache*) but no matter how many times I hit refresh the browser won't display the new content.
I use this function to register the Service Worker
async function register() {
const registration = await navigator.serviceWorker.register(SW_URL);
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === "installed") {
if (navigator.serviceWorker.controller) {
console.log(
"New content is available; please refresh."
);
} else {
console.log("Content is cached for offline use.");
}
}
};
};
}
I wonder if I have to do something extra to make the content refresh properly?
My workbox-config.js is
module.exports = {
globDirectory: ".doc_build",
globPatterns: ["**/*"],
swDest: ".doc_build/sw.js"
};
This happens on both Firefox and Chrome.
Thanks to Robert Rowntree's link in the question comment I figured this out.
I my case the content gets refreshed to cache the but old version of the precache service worker still keeps running which has a list of objects like this
{
"url": "index.html",
"revision": "e4919b0cd0e772b3beb2d1f3d09af437"
}
as you can see it has the checksum of the old version in it and it will keep serving that until the old service worker gets deactivated and the new one activated.
It is possible to see that by checking registration.waiting when the old service worker is waiting for to be deactivated and new one to be installed. It seems that browser does this "at some point". It actually seems to happen if I just keep the tabs closed long enough.
The solution for my question is to force the service worker to skip the waiting period. It is possible to do that by sending a message to the service worker from the update event
async function register() {
const registration = await navigator.serviceWorker.register(SW_URL);
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = async () => {
if (installingWorker.state === "installed") {
if (navigator.serviceWorker.controller) {
console.log(
"New content is available; please refresh."
);
// Send message to the service worker telling
// it should stop waiting for browser to deactivate it
registration.waiting.postMessage("skipWaiting");
} else {
console.log("Content is cached for offline use.");
}
}
};
};
}
Then in the Service Worker code I had to handle that message and call skipWaiting()
self.addEventListener("message", messageEvent => {
if (messageEvent.data === "skipWaiting") {
return skipWaiting();
}
});
To do this I had to move from workbox generateSW to workbox injectManifest to be able to add the skipping code.
But there are caveats in this solution. Read from the Robert's link onwards from
"The simplest and most dangerous approach is to just skip waiting during installation."
https://redfin.engineering/how-to-fix-the-refresh-button-when-using-service-workers-a8e27af6df68
Fortunately this is good enough for my case.

Interrupting application flow from a non-MVC event

I'm working on an application which uses a REST API backend. This API has a login step which creates a token used for all subsequent API requests. I store this token in the auth storage, and I have an event hook that checks if the user is logged in, and if not, renders the login page:
$eventManager->attach(MvcEvent::EVENT_ROUTE, function($e) use ($view, $auth) {
$match = $e->getRouteMatch();
// No route match, this is a 404
if (!$match instanceof RouteMatch) {
return;
}
// Route is whitelisted
$matchedRoute = $match->getMatchedRouteName();
if (in_array($matchedRoute, array('login'))) {
return;
}
// if they're logged in, all is good
if ($auth->hasIdentity()) {
return true;
}
[render login form and return response object]
}, -100);
This works great.
The API also sometimes expires the login tokens in a way that I can't easily predict, which means all API calls will return a 'Session expired' type error. I've written an event trigger after the API calls that I can hook into. I want to check for these 'session expired' responses and somehow render the login page in the same way I do above:
$events->attach('Api', 'call', function ($e) {
$api = $e->getTarget();
$params = $e->getParams();
$result = $params['apiResult'];
if ([result is a session expired response]) {
// what can I do here?
}
}, 999);
but since this isn't an MVC event, even if I could access the response object here, returning it wouldn't do anything. What would be the best way to interrupt the application flow in a non-MVC event?
I'm not sure but I'm assuming that your API events do occur in a dedicated EventManager instance (so your API may be an implementation of EventManagerAwareInterface) and not in the MVC one (which is the one you grab from the Zend\Mvc\Application instance).
If that's the case, you could inject both the main EventManager and the MvcEvent inside your API, and then short circuit the MVC cycle from the call listener.
I.e. assume your dependencies are in $mvcEvent and $mvcEventManager properties with getters, this is how you would listen for the call event:
$events->attach('call', function($e) {
$api = $e->getTarget();
$params = $e->getParams();
$result = $params['apiResult'];
if ([result is a session expired response]) {
$mvcEvent = $api->getMvcEvent();
$mvcEvent->setError('api error');
$mvcEvent->setParam('exception', new \Exception('Session expired'));
$api->getMvcEventManager()->trigger('dispatch.error', $mvcEvent);
}
}, 999);
There are better ways to do it for sure, choosing the best will depend on the architecture of your API class.
You could use Zend\EventManager\ResponseCollection returned by your trigger, rather than using the MVC event inside the listener; that would enable your API event cycle to continue even if some error occurs. That's actually how Zend\Mvc\Application uses its own event manager in the run() method, so you can peek at that for an example.

MSMQ, Windows Service and Server Restarts

I am working with a legacy Windows Service that reads messages from a private MSMQ queue processes them (does some database work, sends some emails) and then waits for the next message (PeekCompleted)
The service is problematic - whenever Windows Update requires a server reboot (so like almost always) the Service comes back up in a "Started" condition but has to be REstarted manually or the messages just pile up in the queue.
My first inclination is to think that there is something in the OnStart handler that isn't getting hit when the server comes back up and I am attempting to sort out the Logs (another story) but Windows Services and threading are not my normal domain so I am hoping someone can point me in the right direction....
Below are the OnStart Handler and message handling function, stripped inconsequential stuff.
Question: in OnStart the MessageRecieved function is attached to the PeekCompleted event.
I assume OnStart fires when the server comes back up so the handler must get attached, but I am not clear whether message that were (a) already in the queue at re-boot or (b) arrive during re-boot will actually trigger the event ?
If it should is there something else I should be looking for?
Any suggestions welcome!
protected override void OnStart(string[] args)
{
try
{
_inProcess = false;
_queueMessage = null;
_stopping = false;
_queue = ReadyQueue(_queueName);
if (_queue == null)
{
throw new Exception(string.Format("'ReadyFormQueue({0})' returned null", _queueName));
}
_queue.PeekCompleted += new PeekCompletedEventHandler(MessageReceived);
_queue.Formatter = new BinaryMessageFormatter();
_queue.BeginPeek();
}
catch (Exception exception)
{
//do cleanup and other recovery stuff
}
}
private void MessageReceived(object sender, PeekCompletedEventArgs e)
{
_currentMessage = null;
_inProcess = false;
try
{
_queueMessage = _queue.EndPeek(e.AsyncResult);
_queueMessage.Formatter = new BinaryMessageFormatter();
_currentMessage = (MyMessageType)_queueMessage.Body;
_queue.ReceiveById(_queueMessage.Id);
_inProcess = true;
_helper = new MessageHelper();
_currentMessage = _helper.Process(_currentMessage); //sets global _inProcess flag
if (_inProcess)
{
Thread.Sleep((int)(_retryWaitTime * 0x3e8));
SendFormMessageToQueue(FailedQueueName, _currentMessage);
}
else
{
_queue.BeginPeek();
}
}
catch (Exception exception)
{
_inProcess = false;
//do other recovery stuff
if (_currentMessage != null)
{
ReadyFormQueue(_poisonQueueName);
SendFormMessageToQueue(_poisonQueueName, _currentMessage);
}
}
}
This legacy windows service could be started before the queueing infrastructure is up and fully operational, must fail in the initial connection and therefore isn't processing messages.
The first thing that I would check (unless the windows service has proper logging) is if there is a windows service dependency that is properly set up - you don't want your legacy service to fully start until the MSMQ service has itself completely started.
I don't think there is a problem in the legacy service per say since once you restart it, it seems to work fine, I think you have a resource-available-race type of problem where the consumer starts before the resource and it wasn't completely designed to recover from that.
I would: create a service dependency (can be done in the SCM) and then reboot the server and see if you have any more MSMQ messages pilling up, my guess the answer will be no.
Hope this helps

Resources