Files
docs.datacontroller.io/docs/developer-guide/browser-variables.md
T
dc c7626b2797 docs(developer): a page for the browser variables, and links from where they are used
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.
2026-10-26 11:42:35 +00:00

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.