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 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 is %included 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:
%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:
%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.