Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(typescript): move remaining addon and test-app code #1015

Merged
merged 4 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/ember-cookies/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"default": "./dist/*.js"
},
"./test-support": {
"types": "./declarations/*.d.ts",
"types": "./declarations/test-support/index.d.ts",
"default": "./dist/test-support/index.js"
},
"./addon-main.js": "./addon-main.cjs"
Expand Down Expand Up @@ -82,6 +82,9 @@
"rollup": "4.29.1",
"typescript": "^5.7.2"
},
"peerDependencies": {
"ember-source": ">=4.0"
},
"engines": {
"node": ">= 16.*"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,61 @@
import { isNone, isPresent, isEmpty } from '@ember/utils';
import { get } from '@ember/object';
import { assert } from '@ember/debug';
import { getOwner } from '@ember/application';
import Service from '@ember/service';
import { serializeCookie } from '../utils/serialize-cookie.ts';
const { keys } = Object;

const DEFAULTS = { raw: false };
const MAX_COOKIE_BYTE_LENGTH = 4096;

type ReadOptions = {
raw?: boolean;
domain?: never;
expires?: never;
maxAge?: never;
path?: never;
};

export type WriteOptions = {
domain?: string;
path?: string;
secure?: boolean;
raw?: boolean;
sameSite?: 'Strict' | 'Lax' | 'None';
signed?: never;
httpOnly?: boolean;
} & ({ expires?: Date; maxAge?: never } | { maxAge?: number; expires?: never });

type ClearOptions = {
domain?: string;
path?: string;
secure?: boolean;

expires?: never;
maxAge?: never;
raw?: never;
};

type CookiePair = [string, string];

type FastbootCookies = Record<string, { value?: string; options?: any }>;

export default class CookiesService extends Service {
_fastBoot:
| any
| {
request?: {
cookies?: Record<string, string>;
};
};
_fastBootCookiesCache?: FastbootCookies;

protected _document: Document = window.document;

constructor() {
super(...arguments);

this._document = this._document || window.document;
if (typeof this._fastBoot === 'undefined') {
let owner = getOwner(this);

let owner = getOwner(this);
if (typeof this._fastBoot === 'undefined' && owner) {
this._fastBoot = owner.lookup('service:fastboot');
}
}
Expand All @@ -24,31 +64,42 @@ export default class CookiesService extends Service {
let all = this._document.cookie.split(';');
let filtered = this._filterDocumentCookies(all);

return filtered.reduce((acc, cookie) => {
if (!isEmpty(cookie)) {
let [key, value] = cookie;
acc[key.trim()] = (value || '').trim();
}
return acc;
}, {});
return filtered.reduce(
(acc, cookie) => {
if (!isEmpty(cookie)) {
let [key, value] = cookie;
acc[key.trim()] = (value || '').trim();
}
return acc;
},
{} as Record<string, string>
);
}

_getFastBootCookies() {
let fastBootCookies = this._fastBoot.request.cookies;
fastBootCookies = keys(fastBootCookies).reduce((acc, name) => {
let value = fastBootCookies[name];
acc[name] = { value };
return acc;
}, {});
const cookies = this._fastBoot.request.cookies;
const fastBootCookies = Object.keys(cookies).reduce((acc, name) => {
const value = cookies[name];

if (typeof value === 'object') {
acc[name] = value;
} else {
acc[name] = { value };
}

let fastBootCookiesCache = this._fastBootCookiesCache || {};
fastBootCookies = Object.assign({}, fastBootCookies, fastBootCookiesCache);
this._fastBootCookiesCache = fastBootCookies;
return acc;
}, {} as FastbootCookies);
const fastBootCookiesCache = this._fastBootCookiesCache || {};

return this._filterCachedFastBootCookies(fastBootCookies);
const mergedFastBootCookies = Object.assign({}, fastBootCookies, fastBootCookiesCache);
this._fastBootCookiesCache = mergedFastBootCookies;
return this._filterCachedFastBootCookies(mergedFastBootCookies);
}

read(name, options = {}) {
read(
name?: string,
options: ReadOptions = {}
): string | undefined | Record<string, string | undefined> {
options = Object.assign({}, DEFAULTS, options || {});
assert(
'Domain, Expires, Max-Age, and Path options cannot be set when reading cookies',
Expand All @@ -58,7 +109,7 @@ export default class CookiesService extends Service {
isEmpty(options.path)
);

let all;
let all: Record<string, string | undefined> = {};
if (this._isFastBoot()) {
all = this._getFastBootCookies();
} else {
Expand All @@ -68,12 +119,12 @@ export default class CookiesService extends Service {
if (name) {
return this._decodeValue(all[name], options.raw);
} else {
keys(all).forEach(name => (all[name] = this._decodeValue(all[name], options.raw)));
Object.keys(all).forEach(name => (all[name] = this._decodeValue(all[name], options.raw)));
return all;
}
}

write(name, value, options = {}) {
write(name: string, value: unknown, options?: WriteOptions) {
options = Object.assign({}, DEFAULTS, options || {});
assert(
"Cookies cannot be set as signed as signed cookies would not be modifyable in the browser as it has no knowledge of the express server's signing key!",
Expand All @@ -84,11 +135,11 @@ export default class CookiesService extends Service {
isEmpty(options.expires) || isEmpty(options.maxAge)
);

value = this._encodeValue(value, options.raw);
value = this._encodeValue(value as string, options.raw);

assert(
`Cookies larger than ${MAX_COOKIE_BYTE_LENGTH} bytes are not supported by most browsers!`,
this._isCookieSizeAcceptable(value)
typeof value === 'string' && this._isCookieSizeAcceptable(value)
);

if (this._isFastBoot()) {
Expand All @@ -101,19 +152,19 @@ export default class CookiesService extends Service {
}
}

clear(name, options = {}) {
clear(name: string, options?: ClearOptions) {
options = Object.assign({}, options || {});
assert(
'Expires, Max-Age, and raw options cannot be set when clearing cookies',
isEmpty(options.expires) && isEmpty(options.maxAge) && isEmpty(options.raw)
);

options.expires = new Date('1970-01-01');
options.expires = new Date('1970-01-01') as never;
options.path = options.path || this._normalizedDefaultPath();
this.write(name, null, options);
}

exists(name) {
exists(name: string) {
let all;
if (this._isFastBoot()) {
all = this._getFastBootCookies();
Expand All @@ -124,20 +175,20 @@ export default class CookiesService extends Service {
return Object.prototype.hasOwnProperty.call(all, name);
}

_writeDocumentCookie(name, value, options = {}) {
_writeDocumentCookie(name: string, value: unknown, options: WriteOptions = {}) {
let serializedCookie = this._serializeCookie(name, value, options);
this._document.cookie = serializedCookie;
}

_writeFastBootCookie(name, value, options = {}) {
_writeFastBootCookie(name: string, value: unknown, options: WriteOptions = {}) {
let responseHeaders = this._fastBoot.response.headers;
let serializedCookie = this._serializeCookie(...arguments);
let serializedCookie = this._serializeCookie(name, value, options);

if (!isEmpty(options.maxAge)) {
if (options.maxAge) {
options.maxAge *= 1000;
}

this._cacheFastBootCookie(...arguments);
this._cacheFastBootCookie(name, value, options);

let replaced = false;
let existing = responseHeaders.getAll('set-cookie');
Expand All @@ -155,52 +206,54 @@ export default class CookiesService extends Service {
}
}

_cacheFastBootCookie(name, value, options = {}) {
_cacheFastBootCookie(name: string, value: unknown, options: WriteOptions = {}) {
let fastBootCache = this._fastBootCookiesCache || {};
let cachedOptions = Object.assign({}, options);
let cachedOptions: WriteOptions = Object.assign({}, options);

if (cachedOptions.maxAge) {
if (cachedOptions.maxAge && options.maxAge) {
let expires = new Date();
expires.setSeconds(expires.getSeconds() + options.maxAge);
cachedOptions.expires = expires;

delete cachedOptions.maxAge;
(cachedOptions as WriteOptions & { expires?: Date; maxAge?: never }).expires = expires;
}

fastBootCache[name] = { value, options: cachedOptions };
fastBootCache[name] = { value: value as string, options: cachedOptions };
this._fastBootCookiesCache = fastBootCache;
}

_filterCachedFastBootCookies(fastBootCookies) {
_filterCachedFastBootCookies(fastBootCookies: FastbootCookies) {
let { path: requestPath } = this._fastBoot.request;

// cannot use deconstruct here
// eslint-disable-next-line ember/no-get
let host = get(this._fastBoot, 'request.host');
let host = this._fastBoot?.request?.host;

return keys(fastBootCookies).reduce((acc, name) => {
let { value, options } = fastBootCookies[name];
options = options || {};
return Object.keys(fastBootCookies).reduce(
(acc, name) => {
let { value, options } = fastBootCookies[name] as { value: string; options: ReadOptions };
options = options || {};

let { path: optionsPath, domain, expires } = options;
let { path: optionsPath, domain, expires } = options;

if (optionsPath && requestPath.indexOf(optionsPath) !== 0) {
return acc;
}
if (optionsPath && requestPath.indexOf(optionsPath) !== 0) {
return acc;
}

if (domain && host.indexOf(domain) + domain.length !== host.length) {
return acc;
}
if (domain && host.indexOf(domain) + (domain as string).length !== host.length) {
return acc;
}

if (expires && expires < new Date()) {
return acc;
}
if (expires && (expires as Date) < new Date()) {
return acc;
}

acc[name] = value;
return acc;
}, {});
acc[name] = value;
return acc;
},
{} as Record<string, string>
);
}

_encodeValue(value, raw) {
_encodeValue(value: string | undefined, raw?: boolean) {
if (isNone(value)) {
return '';
} else if (raw) {
Expand All @@ -210,28 +263,28 @@ export default class CookiesService extends Service {
}
}

_decodeValue(value, raw) {
_decodeValue(value: string | undefined, raw?: boolean) {
if (isNone(value) || raw) {
return value;
} else {
return decodeURIComponent(value);
}
}

_filterDocumentCookies(unfilteredCookies) {
_filterDocumentCookies(unfilteredCookies: string[]): CookiePair[] {
return unfilteredCookies
.map(c => {
.map<CookiePair>(c => {
let separatorIndex = c.indexOf('=');
return [c.substring(0, separatorIndex), c.substring(separatorIndex + 1)];
})
.filter(c => c.length === 2 && isPresent(c[0]));
}

_serializeCookie(name, value, options = {}) {
_serializeCookie(name: string, value: unknown, options: WriteOptions = {}) {
return serializeCookie(name, value, options);
}

_isCookieSizeAcceptable(value) {
_isCookieSizeAcceptable(value: string) {
// Counting bytes varies Pre-ES6 and in ES6
// This snippet counts the bytes in the value
// about to be stored as the cookie:
Expand All @@ -240,9 +293,7 @@ export default class CookiesService extends Service {
let i = 0;
let c;
while ((c = value.charCodeAt(i++))) {
/* eslint-disable no-bitwise */
_byteCount += c >> 11 ? 3 : c >> 7 ? 2 : 1;
/* eslint-enable no-bitwise */
}

return _byteCount < MAX_COOKIE_BYTE_LENGTH;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { assert } from '@ember/debug';
import { isEmpty } from '@ember/utils';
import { serializeCookie } from '../utils/serialize-cookie.ts';
import type { WriteOptions } from '../services/cookies.ts';

export default function clearAllCookies(options = {}) {
export default function clearAllCookies(options: WriteOptions = {}) {
assert('Cookies cannot be set to be HTTP-only from a browser!', !options.httpOnly);
assert(
'Expires, Max-Age, and raw options cannot be set when clearing cookies',
Expand All @@ -16,6 +17,9 @@ export default function clearAllCookies(options = {}) {

cookies.forEach(cookie => {
let cookieName = cookie.split('=')[0];
document.cookie = serializeCookie(cookieName, '', options);

if (typeof cookieName === 'string') {
document.cookie = serializeCookie(cookieName, '', options);
}
});
}
1 change: 0 additions & 1 deletion packages/ember-cookies/src/test-support/index.js

This file was deleted.

1 change: 1 addition & 0 deletions packages/ember-cookies/src/test-support/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as clearAllCookies } from './clear-all-cookies.ts';
2 changes: 1 addition & 1 deletion packages/ember-cookies/src/utils/serialize-cookie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface Options {
partitioned?: boolean;
}

export const serializeCookie = (name: string, value: string, options: Options = {}) => {
export const serializeCookie = (name: string, value: string | unknown, options: Options = {}) => {
let cookie = `${name}=${value}`;

if (!isEmpty(options.domain)) {
Expand Down

This file was deleted.

Loading
Loading