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

feat: allows UPS to receive cookie config options #261

Merged
merged 10 commits into from
Jun 7, 2024
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
# [1.17.0-ups-configurable-cookie.2](https://github.com/mParticle/aquarium/compare/v1.17.0-ups-configurable-cookie.1...v1.17.0-ups-configurable-cookie.2) (2024-06-07)


### Bug Fixes

* hide minimap after user clicks on a button ([#264](https://github.com/mParticle/aquarium/issues/264)) ([21a399c](https://github.com/mParticle/aquarium/commit/21a399c3afb60bde0737d472db095fba22c41d63))


### Features

* minimap active state ([#263](https://github.com/mParticle/aquarium/issues/263)) ([ae9a2b0](https://github.com/mParticle/aquarium/commit/ae9a2b0387b7474d13950078cd5b2ee663848ba7))

# [1.17.0-ups-configurable-cookie.1](https://github.com/mParticle/aquarium/compare/v1.16.1...v1.17.0-ups-configurable-cookie.1) (2024-06-03)


### Features

* allows UPS to receive cookie config options instead of only the key ([517d030](https://github.com/mParticle/aquarium/commit/517d03066bb1d55968d650bb7a2a6162a1567ad7))

## [1.16.1](https://github.com/mParticle/aquarium/compare/v1.16.0...v1.16.1) (2024-06-03)


Expand Down
102 changes: 44 additions & 58 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mparticle/aquarium",
"version": "1.16.1",
"version": "1.17.0-ups-configurable-cookie.2",
"description": "mParticle Component Library",
"license": "Apache-2.0",
"keywords": [
Expand Down Expand Up @@ -65,6 +65,7 @@
"stylelint-config-recommended": "14.0.0",
"stylelint-config-standard": "36.0.0",
"stylelint-no-indistinguishable-colors": "2.1.0",
"type-fest": "^4.20.0",
"typescript": "5.3.3",
"vite": "5.0.12",
"vite-plugin-dts": "3.7.2",
Expand Down
15 changes: 5 additions & 10 deletions src/services/user-preferences/user-preferences-service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,8 @@ describe('When testing the User Preferences Service', () => {
userPreferencesService = new UserPreferencesService<TestUserPreferenceId>(
definitions,
compositeUserPreferencesService,
cookieKey,
lowLevelScope,
() => new Date(),
{ key: cookieKey },
)
await userPreferencesService.init()

Expand Down Expand Up @@ -82,9 +81,8 @@ describe('When testing the User Preferences Service', () => {
userPreferencesService = new UserPreferencesService<TestUserPreferenceId>(
definitions,
compositeUserPreferencesService,
cookieKey,
currentScope,
() => new Date(),
{ key: cookieKey },
)
await userPreferencesService.init()

Expand All @@ -109,9 +107,8 @@ describe('When testing the User Preferences Service', () => {
userPreferencesService = new UserPreferencesService<TestUserPreferenceId>(
definitions,
compositeUserPreferencesService,
cookieKey,
someScope,
() => new Date(),
{ key: cookieKey },
)
await userPreferencesService.init()

Expand Down Expand Up @@ -148,9 +145,8 @@ describe('When testing the User Preferences Service', () => {
userPreferencesService = new UserPreferencesService<TestUserPreferenceId>(
definitions,
compositeUserPreferencesService,
cookieKey,
lowLevelScope,
() => new Date(),
{ key: cookieKey },
)
await userPreferencesService.init()

Expand Down Expand Up @@ -182,9 +178,8 @@ describe('When testing the User Preferences Service', () => {
userPreferencesService = new UserPreferencesService<TestUserPreferenceId>(
definitions,
compositeUserPreferencesService,
cookieKey,
lowLevelScope,
() => new Date(),
{ key: cookieKey },
)
await userPreferencesService.init()

Expand Down
15 changes: 6 additions & 9 deletions src/services/user-preferences/user-preferences.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
/* eslint-disable @typescript-eslint/no-extraneous-class,no-unused-vars,@typescript-eslint/no-unused-vars */
import * as Cookies from '../../utils/Cookies'
import { type UserPreferences } from 'src/services/user-preferences/models/storage-models/user-preferences'
import { type CompositeUserPreferences } from 'src/services/user-preferences/models/user-preferences/composite-user-preferences'
import { type UserPreferenceScope } from 'src/services/user-preferences/models/storage-models/user-preference-scope'
import { type UserPreferenceDefinitions } from 'src/services/user-preferences/models/definitions/user-preference-definitions'
import { type CompositeUserPreferencesService } from 'src/services/user-preferences/composite-user-preferences-service'
import * as Cookies from 'src/utils/Cookies'
import { type CookieOptions } from 'src/utils/Cookies'

export class UserPreferencesService<TUserPreferenceId extends PropertyKey> {
public preferences!: CompositeUserPreferences<TUserPreferenceId>

constructor(
private readonly definitions: UserPreferenceDefinitions<TUserPreferenceId>,
private readonly compositeUserPreferencesService: CompositeUserPreferencesService<TUserPreferenceId>,
private readonly cookieKey: string,
private readonly currentScope: UserPreferenceScope,
public dateFormatter: () => Date,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for dateFormatter anymore cause that was used for the expires option which is now configurable from the client.

private readonly cookieOptions: CookieOptions & { key: string },
private readonly onUpdate?: (resolvedPreferences: CompositeUserPreferences<TUserPreferenceId>) => void,
) {}

Expand Down Expand Up @@ -43,7 +43,7 @@ export class UserPreferencesService<TUserPreferenceId extends PropertyKey> {
// @ts-expect-error
const { allowedScope } = this.definitions[userPreferenceId]

const currentStoredPreferences = Cookies.getObject(this.cookieKey)
const currentStoredPreferences = Cookies.getObject(this.cookieOptions.key)

const storedPreferences = this.compositeUserPreferencesService.getUpdatedUserPreferenceStorageObject(
userPreferenceId,
Expand All @@ -69,14 +69,11 @@ export class UserPreferencesService<TUserPreferenceId extends PropertyKey> {
}

private async getStoredPreferences(): Promise<UserPreferences<TUserPreferenceId>> {
return await Promise.resolve(Cookies.getObject(this.cookieKey) ?? {})
return await Promise.resolve(Cookies.getObject(this.cookieOptions.key) ?? {})
}

private async setStoredPreferences(storedPreferences: UserPreferences<TUserPreferenceId>): Promise<void> {
Cookies.putObject(this.cookieKey, storedPreferences, {
expires: this.dateFormatter(),
path: '/',
})
Cookies.putObject(this.cookieOptions.key, storedPreferences, this.cookieOptions)

await Promise.resolve()
}
Expand Down
51 changes: 44 additions & 7 deletions src/utils/Cookies.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { RequireOneOrNone } from 'type-fest'

export function get(key: string): string | null {
const cookies = getAll()
return cookies?.[key] ? cookies[key] : null
Expand All @@ -12,23 +14,58 @@ export function getObject(key: string): string | null {
return value ? JSON.parse(value) : value
}

export function put(key: string, value: string | null, options: any /* TODO fix any */ = {}): void {
let expires = options.expires
if (value == null) expires = 'Thu, 01 Jan 1970 00:00:01 GMT'
if (typeof expires === 'string') expires = new Date(expires)
export type CookieOptions = RequireOneOrNone<
{
path?: string
domain?: string
secure?: boolean
expiresISOString: string
permanent: boolean
},
'expiresISOString' | 'permanent'
>
Comment on lines +17 to +26
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we use permanent, expires is forced as undefined
Screenshot 2024-06-07 122402

If we use expires, permanent is forced as undefined
Screenshot 2024-06-07 122434

Not allowed to use both
Screenshot 2024-06-07 122525

Even in JS land, precedence would be given to permanent

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥 nice


export function put(key: string, value: string | null, options: CookieOptions = {}): void {
let str = `${_encode(key)}=${value != null ? _encode(value) : ''}`
if (options.path) str += `; path=${options.path}`
if (options.domain) str += `; domain=${options.domain}`
if (options.expires) str += `; expires=${expires.toUTCString()}`
if (options.permanent || options.expiresISOString)
str += `; expires=${calculateExpires(value, options.permanent, options.expiresISOString)}`
if (options.secure) str += '; secure'
document.cookie = str
}

export function putObject(key: string, value: Record<string, unknown>, options = {}): void {
/**
* This code came from the aurelia-cookie plugin initially and the way they remove a cookie
* is by calling the put method with a null value and the same options as the cookie that needs to be removed.
*
* This null value makes the cookie expire immediately by setting the expires attribute to a date in the past.
* I'm keeping the same logic, but we should consider using a more robust library for cookie management.
*
* If we don't set the expires option, the cookie Expires property in the browser becomes "Session", which
* doesn't seem to have a predictable behaviour across different browsers.
*
* @see https://stackoverflow.com/questions/4132095/when-does-a-cookie-with-expiration-time-at-end-of-session-expire
*/
function calculateExpires(value: string | null, permanent?: boolean, expires?: string): string {
const defaultExpires = 'Thu, 01 Jan 1970 00:00:01 GMT'

if (value === null) {
return defaultExpires
}

if (permanent) {
return 'Sat, 31 Dec 2044 23:59:59 GMT'
}

return expires ?? defaultExpires
}

export function putObject(key: string, value: Record<string, unknown>, options: CookieOptions = {}): void {
put(key, JSON.stringify(value), options)
}

export function remove(key: string, options = {}): void {
export function remove(key: string, options: CookieOptions = {}): void {
put(key, null, options)
}

Expand Down
Loading