--- 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.