Snowboard Utilities

Introduction

The Snowboard framework included several small utilities by default that help make development easier.

Cookie

The Cookie utility is a small wrapper around the js-cookie package that provides a simple, lightweight JS API for interacting with browser cookies.

Basic Usage

Create a cookie, valid across the entire site:

Snowboard.cookie().set('name', 'value')

Create a cookie that expires 7 days from now, valid across the entire site:

Snowboard.cookie().set('name', 'value', { expires: 7 })

Create an expiring cookie, valid to the path of the current page:

Snowboard.cookie().set('name', 'value', { expires: 7, path: '' })

Read cookie:

Snowboard.cookie().get('name') // => 'value'
Snowboard.cookie().get('nothing') // => undefined

Read all visible cookies:

Snowboard.cookie().get() // => { name: 'value' }

NOTE: Cookies can only be read if the place they are being read from has access to read the cookie according to the browser.

Delete cookie:

Snowboard.cookie().remove('name')

Delete a cookie valid to the path of the current page:

Snowboard.cookie().set('name', 'value', { path: '' })
Snowboard.cookie().remove('name') // fail!
Snowboard.cookie().remove('name', { path: '' }) // removed!

IMPORTANT! When deleting a cookie and you're not relying on the default attributes, you must pass the exact same path and domain attributes that were used to set the cookie

Snowboard.cookie().remove('name', { path: '', domain: '.yourdomain.com' })

NOTE: Removing a nonexistent cookie neither raises any exception nor returns any value.

Encoding

The package is RFC 6265 compliant. All special characters that are not allowed in the cookie-name or cookie-value are encoded with each one's UTF-8 Hex equivalent using percent-encoding. The only character in cookie-name or cookie-value that is allowed and still encoded is the percent % character, it is escaped in order to interpret percent input as literal. Please note that the default encoding/decoding strategy is meant to be interoperable only between cookies that are read/written by js-cookie. To override the default encoding/decoding strategy you need to use a converter.

NOTE: According to RFC 6265, your cookies may get deleted if they are too big or there are too many cookies in the same domain, more details here.

Cookie Attributes

Cookie attributes can be set globally by creating an instance of the API via withAttributes(), or individually for each call to Snowboard.cookie().set(...) by passing a plain object as the last argument. Per-call attributes override the default attributes.

NOTE: You should never allow untrusted input to set the cookie attributes or you might be exposed to a XSS attack.

expires

Defines when the cookie will be removed. Value must be a Number which will be interpreted as days from time of creation or a Date instance. If omitted, the cookie becomes a session cookie.

To create a cookie that expires in less than a day, you can check the FAQ on the Wiki.

Default: Cookie is removed when the user closes the browser.

Examples:

Snowboard.cookie().set('name', 'value', { expires: 365 })
Snowboard.cookie().get('name') // => 'value'
Snowboard.cookie().remove('name')

path

A String indicating the path where the cookie is visible.

Default: /

Examples:

Snowboard.cookie().set('name', 'value', { path: '' })
Snowboard.cookie().get('name') // => 'value'
Snowboard.cookie().remove('name', { path: '' })

domain

A String indicating a valid domain where the cookie should be visible. The cookie will also be visible to all subdomains.

Default: Cookie is visible only to the domain or subdomain of the page where the cookie was created

Examples:

Assuming a cookie that is being created on example.com:

Snowboard.cookie().set('name', 'value', { domain: 'subdomain.example.com' })
Snowboard.cookie().get('name') // => undefined (need to read at 'subdomain.example.com')

secure

Either true or false, indicating if the cookie transmission requires a secure protocol (https).

Default: No secure protocol requirement.

Examples:

Snowboard.cookie().set('name', 'value', { secure: true })
Snowboard.cookie().get('name') // => 'value'
Snowboard.cookie().remove('name')

sameSite

A String, allowing to control whether the browser is sending a cookie along with cross-site requests.

Default: not set.

NOTE: More recent browsers are making "Lax" the default value even without specifiying anything here.

Examples:

Snowboard.cookie().set('name', 'value', { sameSite: 'strict' })
Snowboard.cookie().get('name') // => 'value'
Snowboard.cookie().remove('name')

Setting up defaults

In order to set global defaults that are used for every cookie that is created with the Snowboard.cookie().set('name', 'value') method you can call the setDefaults(options) method on the Cookie plugin and it will set the provided options as the global defaults.

If you want to get the current defaults, call Snowboard.cookie().getDefaults().

Snowboard.cookie().setDefaults({ path: '/', domain: '.example.com' });
Snowboard.cookie().set('example', 'value');

Events

The Cookie plugin provides the ability to interact with cookies and modify their values during accessing or creating.

cookie.get

This event runs during Snowboard.cookie().get() and provides the (string) name & (string) value parameters, along with a callback method that can be used by a plugin to override the cookie value programatically. This can be used to manipulate or decode cookie values.

class CookieDecryptor extends Singleton
{
    listens() {
        return {
            'cookie.get': 'decryptCookie',
        };
    }

    decryptCookie(name, value, setValue) {
        if (name === 'secureCookie') {
            setValue(decrypt(value));
        }
    }
}

cookie.set

This event runs during Snowboard.cookie().set() and provides the (string) name & (string) value parameters, along with a callback method that can be used by a plugin to override the value saved to the cookie programatically. This will allow you to manipulate or encrypt cookie values before storing them with the browser.

class CookieEncryptor extends Singleton
{
    listens() {
        return {
            'cookie.set': 'encryptCookie',
        };
    }

    encryptCookie(name, value, setValue) {
        if (name === 'secureCookie') {
            setValue(encrypt(value));
        }
    }
}

JSON Parser

The JSON Parser utility is used to safely parse JSON-like (JS-object strings) data that does not strictly meet the JSON specifications. It is especially useful for parsing the values provided in the data-request-data attribute used by the Data Attributes functionality.

This is somewhat similar to JSON5 or RJSON, but not exactly. The key aspect is that it allows for data represented as a JavaScript Object in string form as if it was actively running JS to be parsed without the use of eval() which could cause issues with Content Security Policies that block the use of eval().

NOTE: Although this functionality is documented it is unlikely that regular developers will ever have need of interacting with this feature.

Usage

let data = "key: value, otherKey: 'other value';
let object = Snowboard.jsonParser().parse(`{${data}}`);

Sanitizer

The Sanitizer utility is a client-side HTML sanitizer designed mostly to prevent self-XSS attacks. Such an attack could look like a user copying content from a website that uses clipboard injection to hijack the values actually stored in the clipboard and then having the user paste the content into an environment where the content would be treated as HTML, typically in richeditor / WYSIWYG fields.

The sanitizer utility will strip all attributes that start with on (usually JS event handlers as attributes, i.e. onload or onerror) or that contain the javascript: pseudo protocol in their values.

It is available both as a global function (wnSanitize(html)) and as a Snowboard plugin.

The following example shows how the Froala WYSIWYG editor can be hooked into to protect against a clipboard injection / self-XSS attack.

$froalaEditor.on('froalaEditor.paste.beforeCleanup', function (ev, editor, clipboard_html) {
    return Snowboard.sanitizer().sanitize(clipboard_html);
});

URL Handling

Snowboard contains some useful URL handling functionality for ensuring URLs within Snowboard functionality are correctly based. This is provided by the Snowboard.url() plugin.

Base URL detection

When the URL plugin is loaded, it will automatically detect the base URL for the current project, based on three sources in order:

  • If Snowboard is loaded via the {% snowboard %} Twig tag, which is the recommended way of loading Snowboard, then a data-base-url attribute is added to the script tag, and will be passed through to Snowboard as the base URL.
  • If Snowboard is manually loaded, it will attempt to find a <base> tag in the HTML. If one is found, the href of this tag will be used as the base URL.
  • If neither option above work, then the base URL will be determined to be the hostname of the site per the current URL. This will not work if you host Winter in a subdirectory, so please use either of the options above in this situation.

You can retrieve the base URL by calling Snowboard.url().baseUrl() anywhere in your JavaScript.

Asset URL detection

When the URL plugin is loaded, it will automatically detect the asset URL for the current project, based on three sources in order:

  • If Snowboard is loaded via the {% snowboard %} Twig tag, which is the recommended way of loading Snowboard, then a data-asset-url attribute is added to the script tag, and will be passed through to Snowboard as the asset URL.
  • If Snowboard is manually loaded, it will attempt to find a <link rel="asset_url"> tag in the HTML. If one is found, the href of this tag will be used as the asset URL.
  • If neither option above work, then the asset URL will be determined to be the hostname of the site per the current URL. This will not work if you host Winter in a subdirectory, so please use either of the options above in this situation.

You can retrieve the base URL by calling Snowboard.url().assetUrl() anywhere in your JavaScript.

URL generation

You can generate a URL that is correctly based through the to and asset() methods in the URL plugin. This will ensure that any URLs you use in your JavaScript are pointing to the correct URL.

If an absolute URL is provided to this helper, it will be returned unchanged:

Snowboard.url().baseUrl();
// returns "https://example.com/"

Snowboard.url().to('/');
// returns "https://example.com/"

Snowboard.url().to('my-page/my-sub-page');
// returns "https://example.com/my-page/my-sub-page"

Snowboard.url().to('https://google.com');
// returns "https://google.com"

Snowboard.url().assetUrl();
// returns "https://cdn.example.com/"

Snowboard.url().asset('/');
// returns "https://cdn.example.com/"

Snowboard.url().asset('assets/images');
// returns "https://cdn.example.com/assets/images"

Snowboard.url().asset('https://cdn.example.com/external/lib/leftpad.js?v1.2');
// returns "https://cdn.example.com/external/lib/leftpad.js?v1.2"
Copyright © 2024 Winter CMS