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.
This commit is contained in:
dc committed 2026-10-26 11:42:35 +00:00
1 parent f08c2dff0e
commit c7626b2797
7 files changed
+126 -83

No files matched your search

+4 -83
View File
@@ -150,95 +150,16 @@ The button exists because of how Viya runs a request from JavaScript: it can go
## Support Diagnostics
The frontend sends two input tables with the startup service and with the services that execute customer-provided code - the [hook scripts](macros.md) (pre/post edit and approve hooks) and the dynamic cell dropdown programs. They are available in those services as `work.browser_info` and `work.browser_url_vars`, so a support ticket can be diagnosed from the job log without asking the user follow-up questions, and a hook can adapt to its context. Other services do not receive them.
The services that receive the tables are: `public/startupservice`, `editors/getdata`, `editors/getdynamiccolvals`, `editors/stagedata`, `editors/loadfile`, `editors/restore` and `auditors/postdata`.
`editors/loadfile` is the exception in practice: the app reaches it through the adapter's file-upload call (multipart), 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 the service were ever invoked through the ordinary request path.
The frontend sends two input tables with the startup service and with the services that execute customer-provided code - the [hook scripts](dcc-tables.md#hook-scripts) (pre/post edit and approve hooks) and the dynamic cell dropdown programs. See [Browser Variables](/developer-guide/browser-variables/) for the column lists, the log lines, how to read them from a hook, and which services receive them.
### browser_info
A single-row table 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. |
The browser context: page URL, embedding document, timezone, locale, versions, and the user agent. Columns and examples: [Browser Variables](/developer-guide/browser-variables/#browser_info).
### browser_url_vars
The page URL parameters as 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 DC's. Read a parameter by name rather than assuming every row belongs to Data Controller - `embed` and `labels` are DC's, `_FILE` and `_program` are the platform's.
The values in both tables are supplied by the client, so treat them as diagnostics hints rather than a security boundary.
To see the tables, run any of the services above with debug on. The debug 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 the 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 an older frontend, has no `work.browser_info` - the log then records that fact instead.
The page URL parameters, one row per parameter. Read a parameter by name rather than parsing the page URL - the platform's own parameters are in there too. Columns and examples: [Browser Variables](/developer-guide/browser-variables/#browser_url_vars).
### Reading the tables from a hook
A [hook script](macros.md) 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 by a frontend older than this feature, has no `work.browser_info` at all, so an unguarded `set` or `select` fails the job.
Guard the read with `%sysfunc(exist())` - a service called directly has no `work.browser_info`. Worked examples: [Browser Variables](/developer-guide/browser-variables/#reading-them-from-a-hook).