browser_info and browser_url_vars were only described inside the troubleshooting page, under Support Diagnostics. That is the right place to find them if you already know they exist and are looking for a log line; it is not where anyone looks when writing a hook, which is what they are mostly for. The new Developer Guide page carries the whole subject: which services receive the two tables, the column lists, the debug values per platform and the MPEINIT log lines, reading them from a hook with the exist() guard, and the reminder that they are client-supplied and so diagnostics rather than a security boundary. Troubleshooting keeps three stub headings - browser_info, browser_url_vars, reading-them-from-a-hook - pointing at the page, so the anchors that are already published keep resolving, and the detail is not maintained twice. Linked from the three places a hook author actually starts: the HOOK Scripts section of the MPE_TABLES page, the pre-edit hook guide (where the editor's inputs are listed), the two-rule-sets guide (post-edit hook), and the VA embed page, where embed=va is exactly the kind of thing a hook wants to branch on.
114 lines
5.5 KiB
Markdown
114 lines
5.5 KiB
Markdown
---
|
|
layout: article
|
|
title: Browser Variables
|
|
description: The frontend sends the browser context to the backend as two input tables - browser_info and browser_url_vars - available to the startup service, the edit services and hook scripts.
|
|
---
|
|
|
|
# Browser Variables
|
|
|
|
Two input tables travel with most requests, so a SAS program can see the browser it is serving rather than asking the user:
|
|
|
|
| Table | Holds |
|
|
|---|---|
|
|
| `work.browser_info` | one row - the page URL, the document embedding it, the browser timezone and locale, and the versions in play |
|
|
| `work.browser_url_vars` | one row per parameter on the page URL |
|
|
|
|
They exist for two jobs: diagnosing a support ticket from the job log, and letting a [hook script](/dcc-tables/#hook-scripts) adapt to how the app was opened - embedded in a VA report, opened with `?labels=true`, or used by someone in another timezone.
|
|
|
|
## Which services receive them
|
|
|
|
`public/startupservice`, `editors/getdata`, `editors/getdynamiccolvals`, `editors/stagedata`, `editors/loadfile`, `editors/restore` and `auditors/postdata`. Other services do not receive them.
|
|
|
|
`editors/loadfile` is the exception in practice: the app reaches it through the adapter's multipart upload, which carries no input tables, so the service - and the post edit hook it runs - does not receive them in normal use. It is on the list for completeness, and would receive them if it were ever invoked through the ordinary request path.
|
|
|
|
## browser_info
|
|
|
|
A single row with these columns:
|
|
|
|
| Column | Description |
|
|
|---|---|
|
|
| `url` | The URL of the Data Controller page itself (the iframe), not the document embedding it. When the editor is embedded in a report, the embedding URL is visible here. |
|
|
| `referrer` | The embedding document, from `document.referrer`. For an embedded report this is the report URL, so the embedding report can be told apart from the editor URL. |
|
|
| `timezone` | The browser's IANA timezone name, e.g. `Europe/Berlin`. |
|
|
| `tz_offset` | The browser's UTC offset in minutes, as `Date.getTimezoneOffset()` returns it (positive west of UTC, so `Europe/Berlin` in summer is -120). |
|
|
| `locale` | The browser locale, e.g. `en-GB`. |
|
|
| `dc_version` | The Data Controller build, as shown by `appinfo()`. |
|
|
| `adapter_version` | The `@sasjs/adapter` version the client was built with. |
|
|
| `browser` | The browser family, parsed from the user agent. |
|
|
| `browser_version` | The browser version. |
|
|
| `platform` | The operating system, parsed from the user agent. |
|
|
| `user_agent` | The raw `navigator.userAgent` string. |
|
|
|
|
## browser_url_vars
|
|
|
|
The page URL parameters, one row per parameter, so a SAS program can read a parameter by name rather than parse the `url` string:
|
|
|
|
| Column | Description |
|
|
|---|---|
|
|
| `name` | The URL parameter name. |
|
|
| `value` | The URL parameter value. |
|
|
|
|
Parameters from both the search string and the hash query string (Angular routes carry them after the `#`) are included; on a name collision the search string value wins. The table is sent only when the page URL has at least one parameter.
|
|
|
|
On SAS 9 and Viya the app is reached through the platform's own job URL, so the platform's parameters (`_FILE`, `_program`, `_debug`, `_webout` and friends) are in the table alongside Data Controller's. Read a parameter by name rather than assuming every row belongs to the app - `embed` and `labels` are Data Controller's, `_FILE` and `_program` are the platform's.
|
|
|
|
## Seeing them in the job log
|
|
|
|
Run any of the services above with debug on. The value depends on the path: `&_debug=131` on the Compute API and SAS 9, or `&_debug=128` on the Viya web (JES) path with `runAsTask` enabled - which is what the frontend sends there, so turning debug on in the app is enough. Either value makes the session initialisation write both tables to the job log:
|
|
|
|
```
|
|
NOTE: MPEINIT: work.browser_url_vars:
|
|
name=embed value=va
|
|
NOTE: MPEINIT: work.browser_info:
|
|
url=... referrer=... timezone=Europe/Berlin tz_offset=-120 locale=en-GB
|
|
dc_version=7.15.0 adapter_version=4.19.0 browser=Chrome browser_version=120.0
|
|
platform=Linux user_agent=...
|
|
```
|
|
|
|
A service called directly (for instance from a URL, or by a script), or by a frontend older than this feature, has no `work.browser_info` - the log then records that fact instead.
|
|
|
|
## Reading them from a hook
|
|
|
|
A [hook script](/dcc-tables/#hook-scripts) is `%include`d into the running service, so it shares the service's WORK library and can read the tables directly. Take an embedded editor opened at:
|
|
|
|
```
|
|
https://datacontroller.io/#/editor/SASHELP.CLASS?labels=true&embed=va
|
|
```
|
|
|
|
`browser_url_vars` then holds a row per parameter - `labels=true` and `embed=va` - so a hook can pick up either by name:
|
|
|
|
```sas
|
|
%let labels=N;
|
|
%let embed=N;
|
|
|
|
%if %sysfunc(exist(work.browser_url_vars)) %then %do;
|
|
proc sql noprint;
|
|
select value into :labels
|
|
from work.browser_url_vars
|
|
where name = 'labels';
|
|
select value into :embed
|
|
from work.browser_url_vars
|
|
where name = 'embed';
|
|
quit;
|
|
%end;
|
|
```
|
|
|
|
To branch on what the client reports:
|
|
|
|
```sas
|
|
%if %sysfunc(exist(work.browser_info)) %then %do;
|
|
data _null_;
|
|
set work.browser_info;
|
|
call symputx('dc_timezone', timezone);
|
|
call symputx('dc_locale', locale);
|
|
run;
|
|
%end;
|
|
```
|
|
|
|
!!! warning
|
|
Guard with `%sysfunc(exist())` before reading. A service called directly, or a frontend older than this feature, has no `work.browser_info` at all, so an unguarded `set` or `select` fails the job.
|
|
|
|
## What they are not
|
|
|
|
Both tables are supplied by the client, so treat them as diagnostic hints rather than a security boundary. Never authorise on anything in them - a caller can send whatever it likes.
|