diff --git a/.vscode/settings.json b/.vscode/settings.json
index 60703e2..ebd8854 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -30,6 +30,7 @@
"iframe",
"iframes",
"nlog",
- "webd"
+ "webd",
+ "webdirect"
]
}
\ No newline at end of file
diff --git a/README.md b/README.md
index f56cf59..c4e8979 100644
--- a/README.md
+++ b/README.md
@@ -6,25 +6,71 @@ If your needs are simple: one solution with all hosting on a single domain, you
## Features
-- Land multiple interfaces on a single site across different FileMaker Servers.
- Warn users before leaving the page that they may lose their work.
- Suppress browser back button exiting the application.
- Support for integrating browser back button into a solution to call app-specific "back button" functionality.
-
-WebDLanding acts as a sort of reverse proxy for Web-Direct. You can create user-friendly DNS records pointed at WebDLanding and then route them, behind the scenes to the correct FileMaker Server.
+- Single server and routed mode.
+ - Land multiple interfaces on a single site across different FileMaker Servers.
Provide a best effort to warn users that refreshing the page, or otherwise navigating away will lose their place in the web-direct application. Note: support varies by browser.
Avoid browser back button taking the user out of the solution. Provide support for solutions to listen to the native browser back button and react accordingly.
-## Getting Started
+WebDLanding can act as a sort of reverse proxy for Web-Direct. You can create user-friendly DNS records pointed at WebDLanding and then route them, behind the scenes to the correct FileMaker Server.
+
+## Getting Started: Single Server Mode
+
+You must install this project inside the FileMaker Server created website, be default located at `/FileMaker Server/HTTPServer/conf`.
+
+### Deploy to FileMaker Server Website
+
+1. Install ASP.NET Core Hosting Bundle.
+
+Read about the [hosting bundle here](https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/hosting-bundle?view=aspnetcore-6.0) and [download it here](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-aspnetcore-6.0.8-windows-hosting-bundle-installer).
+
+2. Copy the release files to the FMWebSite website located at `/FileMaker Server/HTTPServer/conf`.
+
+Copy the binaries from the release and deploy those to the directory.
+
+3. Create user-friendly DNS records for your Web-Direct solution pointed at the server.
+
+This must be configured in your DNS host.
+
+4. Ensure that the FMWebSite bindings are setup to handle this DNS record.
+
+This could be with a wildcard binding, or specific bindings. The key is that all DNS entries used are the same for both the landing (entry) and the web direct host.
+
+5. Update `appsettings.json` to include your site(s) in the AppModels section.
+
+```json
+"AppModels": [
+ {
+ "EntryHostName": "webdirect.example.com",
+ "DatabaseName": "FileMakerFile",
+ "WebDirectHostName": "webdirect.example.com"
+ }
+ ]
+```
+
+6. Modify the `web.config` file to include the ASP.NET Core hosting modules:
+
+```xml
+
+
+
+
+
+
+```
+
+## Getting Started: Pseudo Reverse Proxy Routed Mode
You must create and install this project as a separate website on a web server. It can be your FileMaker Server or a separate stand alone server. The main thing is separate DNS records.
### Install and configure WebDLanding
1. Setup the website on your web server, and copy the release files on to the website root.
-2. Create a cononical WebDLanding DNS entry for hosting the application.
+2. Create a canonical WebDLanding DNS entry for hosting the application.
3. Create user-friendly DNS records for your Web-Direct solutions pointed at this site.
4. Update the configuration (TODO: document options) for each site.
@@ -36,9 +82,9 @@ Update web.config in the FileMaker Server root site `/FileMaker Server/HTTPServe
```
-Replacing www.example.com with your cononical WebDLanding site from step #2 above. This allows WebDLanding to iframe in the content of the web direct session through it.
+Replacing www.example.com with your cononical WebDLanding site from step #2 above. This allows WebDLanding to iframe in the content of the web direct session through it.
-### Add HomeURL Support to enable iframe inception escape:
+### Add HomeURL Support to enable iframe inception escape
Since the landing page uses an iframe, the native homeurl logout option will just create a loop of putting another iframe inside another iframe. This could pose challenges if a user logs in and out repeatedly. To avoid this, we use a `top.location.href` in our custom HomeURL logout page to escape to the top level.
@@ -152,7 +198,7 @@ Add two PreConditions which are used by both outgoing rules.
-
+
diff --git a/src/WebDLanding/Constants.cs b/src/WebDLanding/Constants.cs
index d23c1f0..2f99aa9 100644
--- a/src/WebDLanding/Constants.cs
+++ b/src/WebDLanding/Constants.cs
@@ -1,6 +1,6 @@
namespace WebDLanding;
-public class Constants
+public class Globals
{
public const string BackButtonPostEventName = "BackButtonPressed";
}
\ No newline at end of file
diff --git a/src/WebDLanding/Controllers/HomeController.cs b/src/WebDLanding/Controllers/HomeController.cs
deleted file mode 100644
index 4f5d8b7..0000000
--- a/src/WebDLanding/Controllers/HomeController.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using Microsoft.AspNetCore.Mvc;
-
-namespace WebDLanding.Controllers;
-
-public class HomeController : Controller
-{
- private readonly ILogger _logger;
- private readonly IAppModelFinder _finder;
-
- public HomeController(IAppModelFinder finder, ILogger logger)
- {
- _finder = finder;
- _logger = logger;
- }
-
- public async Task Index()
- {
- var hostName = Request.Host.Value;
-
- var vm = await _finder.FindByEntryHostAsync(hostName);
-
- if (vm is null)
- {
- return View("Error");
- }
-
- _logger.LogInformation("Request received for host: {host}. Serving App/System: {db}", hostName, vm.DatabaseName);
-
- return View(vm);
- }
-
- public async Task Logoff()
- {
- // find the database from the referer [sic]
- var referrer = new Uri(Request.Headers.Referer.ToString());
-
- var vm = await _finder.FindByReferrerUriAsync(referrer);
-
- if (vm is null)
- {
- return View("Error");
- }
-
- // you'd think we want to redirect here, but we need to issue a javascript
- // `top.location.href = ` to escape the iframe inception we create for back button support/suppression
- return View(vm);
- }
-}
diff --git a/src/WebDLanding/Models/AppModel.cs b/src/WebDLanding/Models/AppModel.cs
index a01bcbf..3a62117 100644
--- a/src/WebDLanding/Models/AppModel.cs
+++ b/src/WebDLanding/Models/AppModel.cs
@@ -11,12 +11,10 @@ public class AppModel
public string WebDirectHostScheme { get; set; } = "https";
public string WebDirectHostName { get; set; } = default!;
- private readonly string _queryString = "?homeurl=https://webdlanding.wizardsoftware.net/home/logoff";
-
public Uri WebDirectHostUri => new($"{WebDirectHostScheme}://{WebDirectHostName}");
public Uri WebDirectFullUri => new(
- uriString: $"{WebDirectHostScheme}://{WebDirectHostName}/fmi/webd/{DatabaseName}{_queryString}"
+ uriString: $"{WebDirectHostScheme}://{WebDirectHostName}/fmi/webd/{DatabaseName}"
);
}
\ No newline at end of file
diff --git a/src/WebDLanding/Models/SiteConfiguration.cs b/src/WebDLanding/Models/SiteConfiguration.cs
new file mode 100644
index 0000000..884d6d4
--- /dev/null
+++ b/src/WebDLanding/Models/SiteConfiguration.cs
@@ -0,0 +1,8 @@
+namespace WebDLanding.Models;
+
+public class SiteConfiguration
+{
+ public bool SingleServerMode { get; set; } = true;
+
+ public string WebdLandingMainUrl { get; set; } = string.Empty;
+}
\ No newline at end of file
diff --git a/src/WebDLanding/Program.cs b/src/WebDLanding/Program.cs
index ece65f5..3f5e718 100644
--- a/src/WebDLanding/Program.cs
+++ b/src/WebDLanding/Program.cs
@@ -7,35 +7,54 @@
{
var builder = WebApplication.CreateBuilder(args);
- // Add services to the container.
- builder.Services.AddControllersWithViews();
-
// NLog: Setup NLog for Dependency injection
builder.Logging.ClearProviders();
builder.Host.UseNLog();
builder.Services.AddScoped();
+ var siteSettings = builder.Configuration.GetSection("WebDLandingConfig").Get();
+ builder.Services.AddSingleton(siteSettings);
+
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
- app.UseExceptionHandler("/Home/Error");
- // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
+
+ // show index.html from wwwroot
+ app.UseDefaultFiles();
+
app.UseStaticFiles();
app.UseRouting();
- app.UseAuthorization();
+ app.MapGet("/set-vars.js", async (IAppModelFinder finder, HttpContext context) =>
+ {
+ var model = await finder.FindByEntryHostAsync(context.Request.Host.Value);
+
+ var jsToWrite = $"let singleServerMode = {siteSettings.SingleServerMode.ToString().ToLower()};";
+ jsToWrite += $"let uri = '{model.WebDirectFullUri}';";
+ jsToWrite += $"let backEventName = '{Globals.BackButtonPostEventName}';";
+ var homeUri = siteSettings.SingleServerMode == true
+ ? $"{model.EntryHostScheme}://{model.EntryHostName}"
+ : $"{siteSettings.WebdLandingMainUrl}";
+ jsToWrite += $"let homeUri = '{homeUri}';";
- app.MapControllerRoute(
- name: "default",
- pattern: "{controller=Home}/{action=Index}/{id?}");
+ context.Response.ContentType = "application/javascript; charset=utf-8";
+ await context.Response.WriteAsync(jsToWrite);
+ });
+
+ app.MapGet("/logoff.js", async (IAppModelFinder finder, HttpContext context) =>
+ {
+ var model = await finder.FindByEntryHostAsync(context.Request.Host.Value);
+ context.Response.ContentType = "application/javascript; charset=utf-8";
+ await context.Response.WriteAsync($"top.location.href = '{model.EntryHostUri}';");
+ });
app.Run();
}
diff --git a/src/WebDLanding/Views/Home/Index.cshtml b/src/WebDLanding/Views/Home/Index.cshtml
deleted file mode 100644
index b4882bc..0000000
--- a/src/WebDLanding/Views/Home/Index.cshtml
+++ /dev/null
@@ -1,51 +0,0 @@
-@model AppModel;
-
-@{
- ViewData["Title"] = "Home Page";
-}
-
-
-
-
-@section Scripts {
-
-}
\ No newline at end of file
diff --git a/src/WebDLanding/Views/Home/Logoff.cshtml b/src/WebDLanding/Views/Home/Logoff.cshtml
deleted file mode 100644
index 023acf1..0000000
--- a/src/WebDLanding/Views/Home/Logoff.cshtml
+++ /dev/null
@@ -1,5 +0,0 @@
-@model AppModel;
-
-@section Scripts {
-
-}
\ No newline at end of file
diff --git a/src/WebDLanding/Views/Shared/_Layout.cshtml b/src/WebDLanding/Views/Shared/_Layout.cshtml
deleted file mode 100644
index df0c71f..0000000
--- a/src/WebDLanding/Views/Shared/_Layout.cshtml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- @RenderBody()
-
-
-
-
-
- @await RenderSectionAsync("Scripts", required: false)
-
-
diff --git a/src/WebDLanding/Views/_ViewImports.cshtml b/src/WebDLanding/Views/_ViewImports.cshtml
deleted file mode 100644
index b45ace1..0000000
--- a/src/WebDLanding/Views/_ViewImports.cshtml
+++ /dev/null
@@ -1,3 +0,0 @@
-@using WebDLanding
-@using WebDLanding.Models
-@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
diff --git a/src/WebDLanding/Views/_ViewStart.cshtml b/src/WebDLanding/Views/_ViewStart.cshtml
deleted file mode 100644
index a5f1004..0000000
--- a/src/WebDLanding/Views/_ViewStart.cshtml
+++ /dev/null
@@ -1,3 +0,0 @@
-@{
- Layout = "_Layout";
-}
diff --git a/src/WebDLanding/appsettings.Development.json b/src/WebDLanding/appsettings.Development.json
index 0c208ae..075c611 100644
--- a/src/WebDLanding/appsettings.Development.json
+++ b/src/WebDLanding/appsettings.Development.json
@@ -1,4 +1,15 @@
{
+ "WebDLandingConfig":{
+ "SingleServerMode": true,
+ "WebdLandingMainUrl": "https://webdlanding.wizardsoftware.net"
+ },
+ "AppModels": [
+ {
+ "EntryHostName": "localhost:7026",
+ "DatabaseName": "FileMakerFile",
+ "WebDirectHostName": "lade.wizardsoftware.net"
+ }
+ ],
"Logging": {
"LogLevel": {
"Default": "Information",
diff --git a/src/WebDLanding/wwwroot/index.html b/src/WebDLanding/wwwroot/index.html
new file mode 100644
index 0000000..13ca1d3
--- /dev/null
+++ b/src/WebDLanding/wwwroot/index.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/WebDLanding/wwwroot/logoff.html b/src/WebDLanding/wwwroot/logoff.html
new file mode 100644
index 0000000..1b33420
--- /dev/null
+++ b/src/WebDLanding/wwwroot/logoff.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/WebDLanding/wwwroot/message-forward.js b/src/WebDLanding/wwwroot/message-forward.js
new file mode 100644
index 0000000..23128bd
--- /dev/null
+++ b/src/WebDLanding/wwwroot/message-forward.js
@@ -0,0 +1,10 @@
+window.addEventListener("message", d => {
+ // find all frames inside the webdirect session
+ let frames = Array.from(document.getElementsByTagName("iframe"));
+ if (frames.length) {
+ // fore each frame found, relay the event by re-posting it down to the child frame
+ frames.forEach(frame => {
+ frame.contentWindow.postMessage(d.data, "*")
+ });
+ }
+});
\ No newline at end of file
diff --git a/src/WebDLanding/wwwroot/webdlanding.js b/src/WebDLanding/wwwroot/webdlanding.js
new file mode 100644
index 0000000..e0a16a2
--- /dev/null
+++ b/src/WebDLanding/wwwroot/webdlanding.js
@@ -0,0 +1,46 @@
+history.pushState(null, null, "");
+
+window.addEventListener("popstate", function () {
+ // Prevent Back button click
+ history.pushState(null, null, "");
+
+ // select back button integration mode
+ if (singleServerMode === false) {
+ // setup postMessage push to iframe (to be picked up and pushed into listening web viewer)
+ // via injected /message-forward.js
+ document
+ .getElementById("webdirect-frame")
+ .contentWindow.postMessage(backEventName, "*");
+ } else {
+ // find the main iframe, then navigate through its children iframes and post the message
+ // this only works on single origin solutions.
+ let frames = Array.from(
+ document
+ .getElementById("webdirect-frame")
+ .contentWindow.document.getElementsByTagName("iframe")
+ );
+ if (frames.length) {
+ // fore each frame found, relay the event by re-posting it down to the child frame
+ frames.forEach((frame) => {
+ frame.contentWindow.postMessage(backEventName, "*");
+ });
+ }
+ }
+});
+
+iframe = document.getElementById("webdirect-frame");
+iframe.addEventListener("load", function () {
+ history.pushState(null, null, "");
+});
+
+window.addEventListener("load", function () {
+ if (homeUri) {
+ uri = uri + "?homeurl=" + homeUri + "/logoff.html";
+ }
+ document.getElementById("webdirect-frame").src = uri;
+});
+
+window.addEventListener("beforeunload", function (e) {
+ e.preventDefault();
+ return (e.returnValue = "Are you sure you want to exit?");
+});