@openstax/ts-utils 1.1.3 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ declare type HashCompoundValue = Array<HashValue> | {
|
|
|
10
10
|
};
|
|
11
11
|
export declare const hashValue: (value: HashValue) => string;
|
|
12
12
|
export declare const once: <F extends (...args: any[]) => any>(fn: F) => F;
|
|
13
|
+
export declare const partitionSequence: <T, P>(getPartition: (thing: T, previous?: P | undefined) => {
|
|
14
|
+
matches?: boolean | undefined;
|
|
15
|
+
value: P;
|
|
16
|
+
}, sequence: T[]) => [P, T[]][];
|
|
13
17
|
export declare const memoize: <F extends (...args: any[]) => any>(fn: F) => F;
|
|
14
18
|
export declare const roundToPrecision: (num: number, places: number) => number;
|
|
15
19
|
export declare const getCommonProperties: <T1 extends {}, T2 extends {}>(thing1: T1, thing2: T2) => (keyof T1 & keyof T2)[];
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.tuple = exports.merge = exports.getCommonProperties = exports.roundToPrecision = exports.memoize = exports.once = exports.hashValue = exports.mapFind = exports.fnIf = exports.putKeyValue = exports.getKeyValueOr = exports.getKeyValue = void 0;
|
|
6
|
+
exports.tuple = exports.merge = exports.getCommonProperties = exports.roundToPrecision = exports.memoize = exports.partitionSequence = exports.once = exports.hashValue = exports.mapFind = exports.fnIf = exports.putKeyValue = exports.getKeyValueOr = exports.getKeyValue = void 0;
|
|
4
7
|
const crypto_1 = require("crypto");
|
|
8
|
+
const deep_equal_1 = __importDefault(require("deep-equal"));
|
|
5
9
|
const guards_1 = require("./guards");
|
|
6
10
|
/*
|
|
7
11
|
* there was a reason i made these instead of using lodash/fp but i forget what it was. i think maybe
|
|
@@ -72,6 +76,54 @@ const once = (fn) => {
|
|
|
72
76
|
return ((...args) => result || (result = fn(...args)));
|
|
73
77
|
};
|
|
74
78
|
exports.once = once;
|
|
79
|
+
/*
|
|
80
|
+
* partitions a sequence based on a partition function returning {value: any; matches?: boolean}
|
|
81
|
+
* - if the function returns `matches` explicitly then adjacent matching elements will
|
|
82
|
+
* be grouped and the predicate value in the result will be from the last item in the group
|
|
83
|
+
* - if the function returns only a value then matching will be evaluated based on the deep
|
|
84
|
+
* equality of the value with its neighbors
|
|
85
|
+
*
|
|
86
|
+
* this is different from lodash/partition and lodash/groupBy because:
|
|
87
|
+
* - it preserves the order of the items, items will only be grouped if they are already adjacent
|
|
88
|
+
* - there can be any number of groups
|
|
89
|
+
* - it tells you the partition value
|
|
90
|
+
* - the partition value can be reduced, if you care (so you can like, partition on sequential values)
|
|
91
|
+
*
|
|
92
|
+
* simple predicate:
|
|
93
|
+
* returns: [[0, [1,2]], [1, [3,4,5]]]
|
|
94
|
+
* partitionSequence((n: number) => ({value: Math.floor(n / 3)}), [1,2,3,4,5])
|
|
95
|
+
*
|
|
96
|
+
* mutating partition:
|
|
97
|
+
* returns: [
|
|
98
|
+
* [{min: 1,max: 3}, [1,2,3]],
|
|
99
|
+
* [{min: 5,max: 6}, [5,6]],
|
|
100
|
+
* [{min: 8,max: 8}, [8]],
|
|
101
|
+
* ]
|
|
102
|
+
* partitionSequence(
|
|
103
|
+
* (n: number, p?: {min: number; max: number}) =>
|
|
104
|
+
* p && p.max + 1 === n
|
|
105
|
+
* ? {value: {...p, max: n}, matches: true}
|
|
106
|
+
* : {value: {min: n, max: n}, matches: false}
|
|
107
|
+
* , [1,2,3,5,6,8]
|
|
108
|
+
* )
|
|
109
|
+
*/
|
|
110
|
+
const partitionSequence = (getPartition, sequence) => {
|
|
111
|
+
const appendItem = (result, item) => {
|
|
112
|
+
const current = result[result.length - 1];
|
|
113
|
+
const itemPartition = getPartition(item, current === null || current === void 0 ? void 0 : current[0]);
|
|
114
|
+
if (current && ((itemPartition.matches === undefined && (0, deep_equal_1.default)(current[0], itemPartition.value))
|
|
115
|
+
|| itemPartition.matches)) {
|
|
116
|
+
current[0] = itemPartition.value;
|
|
117
|
+
current[1].push(item);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
result.push([itemPartition.value, [item]]);
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
};
|
|
124
|
+
return sequence.reduce(appendItem, []);
|
|
125
|
+
};
|
|
126
|
+
exports.partitionSequence = partitionSequence;
|
|
75
127
|
/*
|
|
76
128
|
* memoizes a function with any number of arguments
|
|
77
129
|
*/
|
|
@@ -6,12 +6,62 @@ declare type Config = {
|
|
|
6
6
|
};
|
|
7
7
|
interface Initializer<C> {
|
|
8
8
|
configSpace?: C;
|
|
9
|
+
window: Window;
|
|
10
|
+
}
|
|
11
|
+
export declare type EventHandler = (e: {
|
|
12
|
+
data: any;
|
|
13
|
+
origin: string;
|
|
14
|
+
source: Pick<Window, 'postMessage'>;
|
|
15
|
+
}) => void;
|
|
16
|
+
export interface Window {
|
|
9
17
|
fetch: GenericFetch;
|
|
18
|
+
top: {} | null;
|
|
19
|
+
parent: Pick<Window, 'postMessage'> | null;
|
|
20
|
+
location: {
|
|
21
|
+
search: string;
|
|
22
|
+
};
|
|
23
|
+
document: {
|
|
24
|
+
referrer: string;
|
|
25
|
+
};
|
|
26
|
+
postMessage: (data: any, origin: string) => void;
|
|
27
|
+
addEventListener: (event: 'message', callback: EventHandler) => void;
|
|
28
|
+
removeEventListener: (event: 'message', callback: EventHandler) => void;
|
|
10
29
|
}
|
|
11
|
-
export declare const browserAuthProvider: <C extends string = "auth">(
|
|
30
|
+
export declare const browserAuthProvider: <C extends string = "auth">({ window, configSpace }: Initializer<C>) => (configProvider: { [key in C]: {
|
|
12
31
|
accountsUrl: import("../../config").ConfigValueProvider<string>;
|
|
13
|
-
}; }) =>
|
|
32
|
+
}; }) => {
|
|
33
|
+
/**
|
|
34
|
+
* adds auth parameters to the url. this is only safe to use when using javascript to navigate
|
|
35
|
+
* within the current window, eg `window.location = 'https://my.otherservice.com';` anchors
|
|
36
|
+
* should use getAuthorizedLinkUrl for their href.
|
|
37
|
+
*
|
|
38
|
+
* result unreliable unless `getUser` is resolved first.
|
|
39
|
+
*/
|
|
40
|
+
getAuthorizedUrl: (urlString: string) => string;
|
|
41
|
+
/**
|
|
42
|
+
* all link href-s must be rendered with auth tokens so that they work when opened in a new tab
|
|
43
|
+
*
|
|
44
|
+
* result unreliable unless `getUser` is resolved first.
|
|
45
|
+
*/
|
|
46
|
+
getAuthorizedLinkUrl: (urlString: string) => string;
|
|
47
|
+
/**
|
|
48
|
+
* gets an authorized url for an iframe src. sets params on the url and saves its
|
|
49
|
+
* origin to trust releasing user identity to it
|
|
50
|
+
*
|
|
51
|
+
* result unreliable unless `getUser` is resolved first.
|
|
52
|
+
*/
|
|
53
|
+
getAuthorizedEmbedUrl: (urlString: string) => string;
|
|
54
|
+
/**
|
|
55
|
+
* gets second argument for `fetch` that has authentication token or cookie
|
|
56
|
+
*
|
|
57
|
+
* result unreliable unless `getUser` is resolved first.
|
|
58
|
+
*/
|
|
14
59
|
getAuthorizedFetchConfig: () => FetchConfig;
|
|
60
|
+
/**
|
|
61
|
+
* loads current user identity. does not reflect changes in identity after being called the first time.
|
|
62
|
+
*
|
|
63
|
+
* result unreliable unless `getUser` is resolved first.
|
|
64
|
+
*/
|
|
15
65
|
getUser: () => Promise<User | undefined>;
|
|
16
66
|
};
|
|
17
67
|
export {};
|
|
@@ -4,27 +4,129 @@ exports.browserAuthProvider = void 0;
|
|
|
4
4
|
const __1 = require("../..");
|
|
5
5
|
const config_1 = require("../../config");
|
|
6
6
|
const guards_1 = require("../../guards");
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
var PostMessageTypes;
|
|
8
|
+
(function (PostMessageTypes) {
|
|
9
|
+
PostMessageTypes["ReceiveUser"] = "receive-user";
|
|
10
|
+
PostMessageTypes["RequestUser"] = "request-user";
|
|
11
|
+
})(PostMessageTypes || (PostMessageTypes = {}));
|
|
12
|
+
const browserAuthProvider = ({ window, configSpace }) => (configProvider) => {
|
|
13
|
+
const config = configProvider[(0, guards_1.ifDefined)(configSpace, 'auth')];
|
|
9
14
|
const accountsUrl = (0, config_1.resolveConfigValue)(config.accountsUrl);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
const queryString = window.location.search;
|
|
16
|
+
const queryKey = 'auth';
|
|
17
|
+
const embeddedQueryValue = 'embedded';
|
|
18
|
+
const authQuery = new URLSearchParams(queryString).get(queryKey);
|
|
19
|
+
const referrer = window.document.referrer ? new URL(window.document.referrer) : undefined;
|
|
20
|
+
const isEmbedded = window.top && window.top !== window;
|
|
21
|
+
const trustedParent = isEmbedded && referrer && referrer.hostname.match(/^(openstax\.org|((.*)(\.openstax\.org|local|localhost)))$/) ? referrer : undefined;
|
|
22
|
+
const trustedEmbeds = new Set();
|
|
23
|
+
let userData = {
|
|
24
|
+
token: [null, embeddedQueryValue].includes(authQuery) ? null : authQuery
|
|
25
|
+
};
|
|
26
|
+
window.addEventListener('message', event => {
|
|
27
|
+
if (event.data.type === PostMessageTypes.RequestUser && trustedEmbeds.has(event.origin)) {
|
|
28
|
+
getUser().then(() => {
|
|
29
|
+
event.source.postMessage({ type: PostMessageTypes.ReceiveUser, userData }, event.origin);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
const getAuthorizedEmbedUrl = (urlString) => {
|
|
34
|
+
const url = new URL(urlString);
|
|
35
|
+
trustedEmbeds.add(url.origin);
|
|
36
|
+
url.searchParams.set(queryKey, embeddedQueryValue);
|
|
37
|
+
return url.href;
|
|
38
|
+
};
|
|
39
|
+
const getAuthorizedLinkUrl = (urlString) => {
|
|
40
|
+
const url = new URL(urlString);
|
|
41
|
+
if (userData.token) {
|
|
42
|
+
url.searchParams.set(queryKey, userData.token);
|
|
43
|
+
}
|
|
44
|
+
return url.href;
|
|
45
|
+
};
|
|
46
|
+
const getAuthorizedUrl = (urlString) => {
|
|
47
|
+
const url = new URL(urlString);
|
|
48
|
+
if (authQuery) {
|
|
49
|
+
url.searchParams.set(queryKey, authQuery);
|
|
50
|
+
}
|
|
51
|
+
return url.href;
|
|
52
|
+
};
|
|
53
|
+
// *note* that this does not actually prevent cookies from being sent on same-origin
|
|
54
|
+
// requests, i'm not sure if its possible to stop browsers from sending cookies in
|
|
55
|
+
// that case
|
|
56
|
+
const getAuthorizedFetchConfig = () => userData.token ? {
|
|
57
|
+
headers: { Authorization: `Bearer ${userData.token}` },
|
|
58
|
+
} : {
|
|
59
|
+
credentials: 'include',
|
|
60
|
+
};
|
|
61
|
+
/*
|
|
62
|
+
* requests user identity from parent window via postMessage
|
|
63
|
+
*/
|
|
64
|
+
const getParentWindowUser = () => new Promise((resolve, reject) => {
|
|
65
|
+
if (!window.parent || !trustedParent) {
|
|
66
|
+
return reject(new Error('parent window is undefined or not trusted'));
|
|
67
|
+
}
|
|
68
|
+
const handler = (event) => {
|
|
69
|
+
if (event.data.type === PostMessageTypes.ReceiveUser && event.origin === trustedParent.origin) {
|
|
70
|
+
clearTimeout(timeout);
|
|
71
|
+
window.removeEventListener('message', handler);
|
|
72
|
+
resolve(event.data.userData);
|
|
73
|
+
}
|
|
27
74
|
};
|
|
75
|
+
window.addEventListener('message', handler);
|
|
76
|
+
window.parent.postMessage({ type: PostMessageTypes.RequestUser }, trustedParent.origin);
|
|
77
|
+
const timeout = setTimeout(() => {
|
|
78
|
+
window.removeEventListener('message', handler);
|
|
79
|
+
reject(new Error('loading user identity timed out'));
|
|
80
|
+
}, 100);
|
|
81
|
+
});
|
|
82
|
+
/*
|
|
83
|
+
* requests user identity from accounts api using given token or cookie
|
|
84
|
+
*/
|
|
85
|
+
const getFetchUser = async () => {
|
|
86
|
+
return await window.fetch((await accountsUrl).replace(/\/+$/, '') + '/accounts/api/user', getAuthorizedFetchConfig())
|
|
87
|
+
.then(response => response.status === 200 ? response.json() : undefined)
|
|
88
|
+
.then(user => ({ ...userData, user }));
|
|
89
|
+
};
|
|
90
|
+
const getUser = (0, __1.once)(async () => {
|
|
91
|
+
userData = authQuery === embeddedQueryValue
|
|
92
|
+
? await getParentWindowUser()
|
|
93
|
+
: await getFetchUser();
|
|
94
|
+
return userData.user;
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
/**
|
|
98
|
+
* adds auth parameters to the url. this is only safe to use when using javascript to navigate
|
|
99
|
+
* within the current window, eg `window.location = 'https://my.otherservice.com';` anchors
|
|
100
|
+
* should use getAuthorizedLinkUrl for their href.
|
|
101
|
+
*
|
|
102
|
+
* result unreliable unless `getUser` is resolved first.
|
|
103
|
+
*/
|
|
104
|
+
getAuthorizedUrl,
|
|
105
|
+
/**
|
|
106
|
+
* all link href-s must be rendered with auth tokens so that they work when opened in a new tab
|
|
107
|
+
*
|
|
108
|
+
* result unreliable unless `getUser` is resolved first.
|
|
109
|
+
*/
|
|
110
|
+
getAuthorizedLinkUrl,
|
|
111
|
+
/**
|
|
112
|
+
* gets an authorized url for an iframe src. sets params on the url and saves its
|
|
113
|
+
* origin to trust releasing user identity to it
|
|
114
|
+
*
|
|
115
|
+
* result unreliable unless `getUser` is resolved first.
|
|
116
|
+
*/
|
|
117
|
+
getAuthorizedEmbedUrl,
|
|
118
|
+
/**
|
|
119
|
+
* gets second argument for `fetch` that has authentication token or cookie
|
|
120
|
+
*
|
|
121
|
+
* result unreliable unless `getUser` is resolved first.
|
|
122
|
+
*/
|
|
123
|
+
getAuthorizedFetchConfig,
|
|
124
|
+
/**
|
|
125
|
+
* loads current user identity. does not reflect changes in identity after being called the first time.
|
|
126
|
+
*
|
|
127
|
+
* result unreliable unless `getUser` is resolved first.
|
|
128
|
+
*/
|
|
129
|
+
getUser
|
|
28
130
|
};
|
|
29
131
|
};
|
|
30
132
|
exports.browserAuthProvider = browserAuthProvider;
|