-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathuserfront.ts
170 lines (159 loc) · 5.59 KB
/
userfront.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// Interface for userfront-core,
// for uniformity for Logout and PasswordReset forms that don't
// need a state machine to describe their behavior.
import Userfront from "@userfront/core";
import { get } from "lodash";
interface Store {
tenantId?: string;
}
declare module "@userfront/core" {
const store: Store;
}
let singleton = Userfront;
let isSingletonOverridden = false;
/**
* Override the Userfront singleton imported from @userfront/core with an object of your choice.
*
* @param newSingleton object to use in place of the Userfront singleton
*/
export const overrideUserfrontSingleton = (newSingleton: any) => {
singleton = newSingleton as typeof Userfront;
isSingletonOverridden = true;
};
// A type with the keys of all functions in Type
type Functions<Type> = {
[Key in keyof Type]-?: Type[Key] extends Function ? Key : never;
}[keyof Type];
export interface CallUserfront {
method: Functions<typeof singleton>;
args: Array<Object>;
}
/**
* Get a property by key from the Userfront singleton, wrapped in a Promise
* so that this can be invoked as an XState service.
* The key may be a path ("path.to.value" -> Userfront.path.to.value)
* The target singleton can be changed with overrideUserfrontSingleton.
*
* @param key key of the property to get from Userfront
* @returns value of Userfront[key]
*/
export const getUserfrontProperty = async (key: string) => {
if (!key) {
console.warn(
"Tried to read a key from the Userfront object, but no key was provided."
);
return Promise.reject();
}
if (get(singleton, key)) {
return get(singleton, key);
}
console.warn(
`Tried to read key ${key} from the Userfront object, but no such key was found in the object or object.store.`
);
return undefined;
};
/**
* Get a property by key from the Userfront singleton without wrapping in a Promise.
* The key may be a path ("path.to.value" -> Userfront.path.to.value)
* The target singleton can be changed with overrideUserfrontSingleton.
*
* @param key key to retrieve
* @returns value of Userfront[key]
*/
export const getUserfrontPropertySync = (key: string) => {
if (!key) {
console.warn(
"Tried to read a key from the Userfront object, but no key was provided."
);
throw new Error();
}
if (get(singleton, key)) {
return get(singleton, key);
}
console.warn(
`Tried to read key ${key} from the Userfront object, but no such key was found in the object or object.store.`
);
return undefined;
};
/**
* Call a method on the Userfront singleton, ensuring that a Promise is returned so that this can be
* invoked as a service in XState models.
* The target singleton can be changed with overrideUserfrontSingleton.
*
* @param {object} config
* @param {string} config.method method name to call; may be a path ("path.to.method" -> Userfront.path.to.method())
* @param {Array} config.args arguments to pass to the method
* @returns {Promise} result of the call, wrapped in a Promise even if the method is sync
*/
export const callUserfront = async ({ method, args = [] }: CallUserfront) => {
// Allow Userfront.init() when singleton is present but not initialized
if (method === "init" && !!singleton) {
return (<any>singleton.init)(...args);
}
if (!singleton || !singleton.store?.tenantId) {
console.warn(
"Tried to call a Userfront method before the Userfront service was initialized."
);
return Promise.reject();
}
const _method = get(singleton, method);
if (!_method || !(typeof _method === "function")) {
console.warn(`Method ${method} not found on Userfront object.`);
return Promise.reject();
}
try {
const res = await (<any>_method)(...args);
// Workaround to avoid flickering when CoreJS redirects:
// wait for the current iteration of the event loop to finish,
// and for the async task queue to finish,
// and for the NEXT event loop cycle to finish,
// then return.
// TODO DEV-658 fix this in a nicer and more reliable way
if (isSingletonOverridden) {
// Don't wait if we've overridden the CoreJS singleton,
// i.e. mocked it out for tests.
return res;
}
return new Promise((resolve) => {
setTimeout(() => resolve(res), 1);
});
} catch (err: any) {
console.warn(
`Method ${method} on Userfront object threw. Error: ${err.message}`
);
return Promise.reject(err);
}
};
/**
* Call a method on the Userfront singleton, immediately returning the result (which may be a Promise).
* The target singleton can be changed with overrideUserfrontSingleton.
* @param {object} config
* @param {string} config.method method name to call; may be a path ("path.to.method" -> Userfront.path.to.method())
* @param {Array} config.args arguments to pass to the method
* @returns result of the call, which may be a Promise if the method is async
*/
export const callUserfrontSync = ({ method, args = [] }: CallUserfront) => {
// Allow Userfront.init() when singleton is present but not initialized
if (method === "init" && !!singleton) {
return (<any>singleton.init)(...args);
}
if (!singleton || !singleton.store?.tenantId) {
console.warn(
"Tried to call a Userfront method before the Userfront service was initialized."
);
throw new Error();
}
const _method = get(singleton, method);
if (!_method || !(typeof _method === "function")) {
console.warn(`Method ${method} not found on Userfront object.`);
throw new Error();
}
try {
return (<any>_method)(...args);
} catch (err: any) {
console.warn(
`Method ${method} on Userfront object threw. Error: ${err.message}`
);
throw err;
}
};