infusion
Version:
Infusion is an application framework for developing flexible stuff with JavaScript
255 lines (230 loc) • 9.03 kB
JavaScript
/*
Copyright The Infusion copyright holders
See the AUTHORS.md file at the top-level directory of this distribution and at
https://github.com/fluid-project/infusion/raw/main/AUTHORS.md.
Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.
You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/main/Infusion-LICENSE.txt
*/
"use strict";
fluid.defaults("fluid.prefs.store", {
gradeNames: ["fluid.dataSource", "fluid.contextAware"],
contextAwareness: {
strategy: {
defaultGradeNames: "fluid.prefs.cookieStore"
}
}
});
fluid.prefs.store.decodeURIComponent = function (payload) {
if (typeof payload === "string") {
return decodeURIComponent(payload);
}
};
fluid.prefs.store.encodeURIComponent = function (payload) {
if (typeof payload === "string") {
return encodeURIComponent(payload);
}
};
/****************
* Cookie Store *
****************/
/**
* Options for reading and/or writing a cookie using the `Document.cookie` web api through a {fluid.dataSource}.
* In most cases these are used directly for setting attributes of the same name on the cookie. However, the `name`
* and `directModel` properties are used for reading/writing the specific cookie. The `directModel` is typically
* included via calls to the `dataSource`'s `get` and `set` methods. The `directModel` is typically prioritized for
* sourcing the cookie's name, followed by the `name` property.
*
* Note: Other than `name` and `directModel` all property names are case insensitive.
* See: https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie for more details on `Document.cookie`
*
* @typedef {Object} CookieOpts
*
* @property {Object} [directModel] - An object defining the coordinates to the cookie
* see: `fluid.dataSource` for more information.
* @property {String} [directModel.cookieName] - The name of the cookie to read/write
* @property {String} [domain] - The domain to scope the cookie's permissions to.
* (e.g. "example.com" or "subdomain.example.com"). Defaults to the host portion of
* the current `document.location`.
* @property {GMTString} [expires] - The cookie expiry date.
* @property {Integer} [max-age] - The maximum age, in seconds, for the cookie before it expires. A negative value
* will cause the cookie to expire immediately.
* @property {String} [name] - The name of the cookie to read/write; typically used if `directModel.cookieName` is
* not provided.
* @property {String} [path] - The URL path that must be included. Defaults to the current
* `document.location.pathname`.
* @property {String} [samesite] - Manages how cookies are sent with cross site requests. Possible values are `lax`,
* `strict`, or `none`. In the future this may be a required field or default to
* `lax`.
* @property {String} [secure] - If enabled, will only allow cookies to be sent over secure protocols (e.g. https).
* In the Cookie specification this property doesn't take any values and will be
* enabled regardless of the value assigned. However, the specific {fluid.dataSource}
* implementation may handle `falsey` values as a means to remove it.
*/
/**
* A type of {fluid.dataSource} that uses a cookie for persistence.
*/
fluid.defaults("fluid.prefs.cookieStore", {
gradeNames: ["fluid.dataSource"],
writableGrade: "fluid.prefs.cookieStore.writable",
cookie: {
name: "fluid-ui-settings"
},
listeners: {
"onRead.impl": {
listener: "fluid.prefs.cookieStore.getCookie",
args: ["{arguments}.1"]
},
"onRead.decodeURI": {
listener: "fluid.prefs.store.decodeURIComponent",
priority: "before:encoding"
}
},
invokers: {
get: {
args: ["{that}", "{arguments}.0", "{that}.options.cookie"] // directModel, options/callback
}
}
});
fluid.defaults("fluid.prefs.cookieStore.writable", {
gradeNames: ["fluid.dataSource.writable"],
cookie: {
// Any attribute specified will be interpolated into the cookie during a write
path: "/",
samesite: "strict"
},
listeners: {
"onWrite.encodeURI": {
func: "fluid.prefs.store.encodeURIComponent",
priority: "before:impl"
},
"onWrite.impl": {
listener: "fluid.prefs.cookieStore.writeCookie"
},
"onWriteResponse.decodeURI": {
listener: "fluid.prefs.store.decodeURIComponent",
priority: "before:encoding"
}
},
invokers: {
set: {
args: ["{that}", "{arguments}.0", "{arguments}.1", "{that}.options.cookie"] // directModel, model, options/callback
}
}
});
/**
* Retrieve and return the value of the cookie
*
* @param {CookieOpts} options - the cookie attributes; used to get the cookie name.
* @return {String|undefined} - the serialized cookie, or `undefined` if no cookie is found.
*/
fluid.prefs.cookieStore.getCookie = function (options) {
var cookieName = fluid.get(options, ["directModel", "cookieName"]) || options.name;
var cookie = document.cookie;
if (cookie.length <= 0) {
return;
}
var cookiePrefix = cookieName + "=";
var startIndex = cookie.indexOf(cookiePrefix);
if (startIndex < 0) {
return;
}
startIndex = startIndex + cookiePrefix.length;
var endIndex = cookie.indexOf(";", startIndex);
if (endIndex < startIndex) {
endIndex = cookie.length;
}
return cookie.substring(startIndex, endIndex);
};
/**
* Assembles the cookie string
* @param {String} cookieName - name of the cookie
* @param {String} payload - the serialized data to be stored in the cookie
* @param {CookieOpts} options - the cookie attributes
* @return {String} - A string representing the assembled cookie.
*/
fluid.prefs.cookieStore.assembleCookie = function (cookieName, payload, options) {
options = options || {};
var cookieStr = cookieName + "=" + payload;
fluid.each(options, function (value, attribute) {
// skip name because it was already set as the first portion of the cookieStr
var isNameAttr = attribute.toLowerCase() === "name";
var isSecureAttr = attribute.toLowerCase() === "secure";
if (!isNameAttr && !isSecureAttr) {
cookieStr += "; " + attribute + "=" + value;
} else if (isSecureAttr && value) {
cookieStr += "; " + attribute;
}
});
return cookieStr;
};
/**
* Saves the settings into a cookie
* @param {String} payload - the serialized data to be stored in the cookie
* @param {CookieOpts} options - settings
* @return {Object} - The original payload.
*/
fluid.prefs.cookieStore.writeCookie = function (payload, options) {
var cookieName = fluid.get(options, ["directModel", "cookieName"]) || options.name;
var cookieStr = fluid.prefs.cookieStore.assembleCookie(cookieName, payload, options);
document.cookie = cookieStr;
return payload;
};
/**************
* Temp Store *
**************/
fluid.defaults("fluid.dataSource.encoding.model", {
gradeNames: "fluid.component",
invokers: {
parse: "fluid.identity",
render: "fluid.identity"
},
contentType: "application/json"
});
/*
* A type of {fluid.dataSource} mock that doesn't do persistence.
*/
fluid.defaults("fluid.prefs.tempStore", {
gradeNames: ["fluid.dataSource", "fluid.modelComponent"],
writableGrade: "fluid.prefs.tempStore.writable",
components: {
encoding: {
type: "fluid.dataSource.encoding.model"
}
},
listeners: {
"onRead.impl": {
listener: "fluid.identity",
args: ["{that}.model"]
}
}
});
fluid.defaults("fluid.prefs.tempStore.writable", {
gradeNames: ["fluid.dataSource.writable", "fluid.modelComponent"],
components: {
encoding: {
type: "fluid.dataSource.encoding.model"
}
},
listeners: {
"onWrite.impl": {
listener: "fluid.replaceModelValue",
args: ["{that}.applier", "", "{arguments}.0"]
}
}
});
fluid.defaults("fluid.prefs.globalSettingsStore", {
gradeNames: ["fluid.component"],
components: {
settingsStore: {
type: "fluid.prefs.store",
options: {
gradeNames: ["fluid.resolveRootSingle"],
writable: true,
singleRootType: "fluid.prefs.store"
}
}
}
});