@0xkey-io/react-native-wallet-kit 0.1.0
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/LICENSE +201 -0
- package/README.md +93 -0
- package/dist/hooks/use-0xkey.d.ts +3 -0
- package/dist/hooks/use-0xkey.d.ts.map +1 -0
- package/dist/hooks/use-0xkey.js +15 -0
- package/dist/hooks/use-0xkey.js.map +1 -0
- package/dist/hooks/use-0xkey.mjs +13 -0
- package/dist/hooks/use-0xkey.mjs.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +6 -0
- package/dist/index.mjs.map +1 -0
- package/dist/providers/Types.d.ts +275 -0
- package/dist/providers/Types.d.ts.map +1 -0
- package/dist/providers/Types.js +9 -0
- package/dist/providers/Types.js.map +1 -0
- package/dist/providers/Types.mjs +7 -0
- package/dist/providers/Types.mjs.map +1 -0
- package/dist/providers/ZeroXKeyProvider.d.ts +39 -0
- package/dist/providers/ZeroXKeyProvider.d.ts.map +1 -0
- package/dist/providers/ZeroXKeyProvider.js +1846 -0
- package/dist/providers/ZeroXKeyProvider.js.map +1 -0
- package/dist/providers/ZeroXKeyProvider.mjs +1844 -0
- package/dist/providers/ZeroXKeyProvider.mjs.map +1 -0
- package/dist/providers/index.d.ts +4 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/tests/oauth-test.d.ts +2 -0
- package/dist/tests/oauth-test.d.ts.map +1 -0
- package/dist/typedoc-entry/index.d.ts +5 -0
- package/dist/typedoc-entry/index.d.ts.map +1 -0
- package/dist/types/base.d.ts +137 -0
- package/dist/types/base.d.ts.map +1 -0
- package/dist/types/base.js +48 -0
- package/dist/types/base.js.map +1 -0
- package/dist/types/base.mjs +48 -0
- package/dist/types/base.mjs.map +1 -0
- package/dist/types/method-types.d.ts +102 -0
- package/dist/types/method-types.d.ts.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/oauth.d.ts +240 -0
- package/dist/utils/oauth.d.ts.map +1 -0
- package/dist/utils/oauth.js +340 -0
- package/dist/utils/oauth.js.map +1 -0
- package/dist/utils/oauth.mjs +319 -0
- package/dist/utils/oauth.mjs.map +1 -0
- package/dist/utils/timers.d.ts +73 -0
- package/dist/utils/timers.d.ts.map +1 -0
- package/dist/utils/timers.js +132 -0
- package/dist/utils/timers.js.map +1 -0
- package/dist/utils/timers.mjs +122 -0
- package/dist/utils/timers.mjs.map +1 -0
- package/dist/utils/utils.d.ts +27 -0
- package/dist/utils/utils.d.ts.map +1 -0
- package/dist/utils/utils.js +37 -0
- package/dist/utils/utils.js.map +1 -0
- package/dist/utils/utils.mjs +33 -0
- package/dist/utils/utils.mjs.map +1 -0
- package/package.json +80 -0
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
2
|
+
import { stringToBase64urlString } from '@0xkey-io/encoding';
|
|
3
|
+
import { OAuthProviders } from '@0xkey-io/sdk-types';
|
|
4
|
+
import AsyncStorageModule from '@react-native-async-storage/async-storage';
|
|
5
|
+
|
|
6
|
+
// some bundlers wrap the module as { default: AsyncStorage } instead of
|
|
7
|
+
// returning AsyncStorage directly. This handles both cases
|
|
8
|
+
const AsyncStorage = AsyncStorageModule.default ?? AsyncStorageModule;
|
|
9
|
+
const DISCORD_AUTH_URL = "https://discord.com/oauth2/authorize";
|
|
10
|
+
const X_AUTH_URL = "https://x.com/i/oauth2/authorize";
|
|
11
|
+
const GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
12
|
+
const APPLE_AUTH_URL = "https://account.apple.com/auth/authorize";
|
|
13
|
+
const FACEBOOK_AUTH_URL = "https://www.facebook.com/v23.0/dialog/oauth";
|
|
14
|
+
const FACEBOOK_GRAPH_URL = "https://graph.facebook.com/v23.0/oauth/access_token";
|
|
15
|
+
const ZEROXKEY_OAUTH_ORIGIN_URL = "https://oauth-origin.0xkey.com";
|
|
16
|
+
const ZEROXKEY_OAUTH_REDIRECT_URL = "https://oauth-redirect.0xkey.com";
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// OAuth State Building
|
|
19
|
+
// ============================================================================
|
|
20
|
+
/**
|
|
21
|
+
* Builds the OAuth state parameter string
|
|
22
|
+
*/
|
|
23
|
+
function buildOAuthState(params) {
|
|
24
|
+
const { provider, flow, publicKey, nonce, additionalState } = params;
|
|
25
|
+
let state = `provider=${provider}&flow=${flow}&publicKey=${encodeURIComponent(publicKey)}`;
|
|
26
|
+
if (nonce) {
|
|
27
|
+
state += `&nonce=${nonce}`;
|
|
28
|
+
}
|
|
29
|
+
if (additionalState) {
|
|
30
|
+
const extra = Object.entries(additionalState)
|
|
31
|
+
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
|
32
|
+
.join("&");
|
|
33
|
+
if (extra) {
|
|
34
|
+
state += `&${extra}`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return state;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Pre-configured OAuth provider settings
|
|
41
|
+
*/
|
|
42
|
+
const OAUTH_PROVIDER_CONFIGS = {
|
|
43
|
+
[OAuthProviders.GOOGLE]: {
|
|
44
|
+
provider: OAuthProviders.GOOGLE,
|
|
45
|
+
authUrl: GOOGLE_AUTH_URL,
|
|
46
|
+
scopes: "openid email profile",
|
|
47
|
+
usesPKCE: false,
|
|
48
|
+
responseType: "id_token",
|
|
49
|
+
nonceInParams: true,
|
|
50
|
+
},
|
|
51
|
+
[OAuthProviders.APPLE]: {
|
|
52
|
+
provider: OAuthProviders.APPLE,
|
|
53
|
+
authUrl: APPLE_AUTH_URL,
|
|
54
|
+
scopes: "",
|
|
55
|
+
usesPKCE: false,
|
|
56
|
+
responseType: "code id_token",
|
|
57
|
+
responseMode: "fragment",
|
|
58
|
+
nonceInParams: true,
|
|
59
|
+
},
|
|
60
|
+
[OAuthProviders.FACEBOOK]: {
|
|
61
|
+
provider: OAuthProviders.FACEBOOK,
|
|
62
|
+
authUrl: FACEBOOK_AUTH_URL,
|
|
63
|
+
scopes: "openid",
|
|
64
|
+
usesPKCE: true,
|
|
65
|
+
responseType: "code",
|
|
66
|
+
nonceInParams: true,
|
|
67
|
+
},
|
|
68
|
+
[OAuthProviders.DISCORD]: {
|
|
69
|
+
provider: OAuthProviders.DISCORD,
|
|
70
|
+
authUrl: DISCORD_AUTH_URL,
|
|
71
|
+
scopes: "identify email",
|
|
72
|
+
usesPKCE: true,
|
|
73
|
+
responseType: "code",
|
|
74
|
+
nonceInParams: false,
|
|
75
|
+
},
|
|
76
|
+
[OAuthProviders.X]: {
|
|
77
|
+
provider: OAuthProviders.X,
|
|
78
|
+
authUrl: X_AUTH_URL,
|
|
79
|
+
scopes: "tweet.read users.read",
|
|
80
|
+
usesPKCE: true,
|
|
81
|
+
responseType: "code",
|
|
82
|
+
nonceInParams: false,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// OAuth State Parsing
|
|
87
|
+
// ============================================================================
|
|
88
|
+
/**
|
|
89
|
+
* Parses the OAuth state parameter string into an object
|
|
90
|
+
*/
|
|
91
|
+
function parseStateParam(stateParam) {
|
|
92
|
+
if (!stateParam)
|
|
93
|
+
return {};
|
|
94
|
+
const result = {};
|
|
95
|
+
const pairs = stateParam.split("&");
|
|
96
|
+
for (const pair of pairs) {
|
|
97
|
+
const [key, value] = pair.split("=");
|
|
98
|
+
if (key && value !== undefined) {
|
|
99
|
+
result[key] = decodeURIComponent(value);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
// Function to generate PKCE challenge pair for Facebook OAuth
|
|
105
|
+
async function generateChallengePair() {
|
|
106
|
+
// Generate verifier from 32 random bytes
|
|
107
|
+
const randomBytes = new Uint8Array(32);
|
|
108
|
+
// eslint-disable-next-line no-undef
|
|
109
|
+
crypto.getRandomValues(randomBytes);
|
|
110
|
+
const randomAsBinary = String.fromCharCode(...randomBytes);
|
|
111
|
+
const verifier = stringToBase64urlString(randomAsBinary);
|
|
112
|
+
// Compute SHA-256 over ASCII(verifier), then base64url encode
|
|
113
|
+
const ascii = new TextEncoder().encode(verifier);
|
|
114
|
+
const digest = sha256(ascii);
|
|
115
|
+
const digestAsBinary = String.fromCharCode(...digest);
|
|
116
|
+
const codeChallenge = stringToBase64urlString(digestAsBinary);
|
|
117
|
+
return { verifier, codeChallenge };
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Gets the AsyncStorage key name for a PKCE provider's verifier
|
|
121
|
+
*/
|
|
122
|
+
function getPKCEVerifierKey(provider) {
|
|
123
|
+
return `${provider}_verifier`;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Stores the PKCE verifier in AsyncStorage
|
|
127
|
+
* @param provider - The OAuth provider
|
|
128
|
+
* @param verifier - The verifier string to store
|
|
129
|
+
*/
|
|
130
|
+
async function storePKCEVerifier(provider, verifier) {
|
|
131
|
+
const key = getPKCEVerifierKey(provider);
|
|
132
|
+
await AsyncStorage.setItem(key, verifier);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Retrieves and removes the PKCE verifier from AsyncStorage
|
|
136
|
+
* @param provider - The OAuth provider
|
|
137
|
+
* @returns The verifier string
|
|
138
|
+
* @throws Error if verifier is not found
|
|
139
|
+
*/
|
|
140
|
+
async function consumePKCEVerifier(provider) {
|
|
141
|
+
const key = getPKCEVerifierKey(provider);
|
|
142
|
+
const verifier = await AsyncStorage.getItem(key);
|
|
143
|
+
if (!verifier) {
|
|
144
|
+
throw new Error(`Missing PKCE verifier for ${provider}. OAuth flow may have been interrupted.`);
|
|
145
|
+
}
|
|
146
|
+
await AsyncStorage.removeItem(key);
|
|
147
|
+
return verifier;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Builds the complete OAuth authorization URL for a provider
|
|
151
|
+
* For react-native, can use either:
|
|
152
|
+
* - Direct provider URLs (Discord, X)
|
|
153
|
+
* - ZeroXKey OAuth proxy (Google, Apple, Facebook)
|
|
154
|
+
*/
|
|
155
|
+
function buildOAuthUrl(params) {
|
|
156
|
+
const { provider, clientId, redirectUri, publicKey, nonce, codeChallenge, additionalState, useOauthProxyOrigin = false, } = params;
|
|
157
|
+
const config = OAUTH_PROVIDER_CONFIGS[provider];
|
|
158
|
+
// Build state parameter
|
|
159
|
+
const stateParams = {
|
|
160
|
+
provider,
|
|
161
|
+
flow: "redirect",
|
|
162
|
+
publicKey,
|
|
163
|
+
};
|
|
164
|
+
if (!config.nonceInParams && nonce) {
|
|
165
|
+
stateParams.nonce = nonce;
|
|
166
|
+
}
|
|
167
|
+
if (additionalState) {
|
|
168
|
+
stateParams.additionalState = additionalState;
|
|
169
|
+
}
|
|
170
|
+
const state = buildOAuthState(stateParams);
|
|
171
|
+
// If using ZeroXKey Oauth proxy (for Google, Apple, Facebook)
|
|
172
|
+
if (useOauthProxyOrigin) {
|
|
173
|
+
const url = new URL(ZEROXKEY_OAUTH_ORIGIN_URL);
|
|
174
|
+
url.searchParams.set("provider", provider);
|
|
175
|
+
url.searchParams.set("clientId", clientId);
|
|
176
|
+
url.searchParams.set("redirectUri", redirectUri);
|
|
177
|
+
url.searchParams.set("nonce", nonce);
|
|
178
|
+
if (codeChallenge) {
|
|
179
|
+
url.searchParams.set("codeChallenge", codeChallenge);
|
|
180
|
+
}
|
|
181
|
+
url.searchParams.set("state", state);
|
|
182
|
+
return url.toString();
|
|
183
|
+
}
|
|
184
|
+
// Direct provider URLs (for Discord, X)
|
|
185
|
+
const authUrl = new URL(config.authUrl);
|
|
186
|
+
authUrl.searchParams.set("client_id", clientId);
|
|
187
|
+
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
188
|
+
authUrl.searchParams.set("response_type", config.responseType);
|
|
189
|
+
if (config.scopes) {
|
|
190
|
+
authUrl.searchParams.set("scope", config.scopes);
|
|
191
|
+
}
|
|
192
|
+
if (config.usesPKCE && codeChallenge) {
|
|
193
|
+
authUrl.searchParams.set("code_challenge", codeChallenge);
|
|
194
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
195
|
+
}
|
|
196
|
+
authUrl.searchParams.set("state", state);
|
|
197
|
+
return authUrl.toString();
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Parses the deep link URL returned from InAppBrowser after OAuth redirect
|
|
201
|
+
* @param deepLinkUrl - The URL from InAppBrowser result (e.g., "myapp://?id_token=...&state=...")
|
|
202
|
+
* @returns Parsed OAuth response data
|
|
203
|
+
*/
|
|
204
|
+
function parseInAppBrowserResult(deepLinkUrl) {
|
|
205
|
+
const qsIndex = deepLinkUrl.indexOf("?");
|
|
206
|
+
const queryString = qsIndex >= 0 ? deepLinkUrl.substring(qsIndex + 1) : "";
|
|
207
|
+
const urlParams = new URLSearchParams(queryString);
|
|
208
|
+
const idToken = urlParams.get("id_token");
|
|
209
|
+
const authCode = urlParams.get("code");
|
|
210
|
+
const stateParam = urlParams.get("state");
|
|
211
|
+
// Parse state parameter
|
|
212
|
+
const stateData = parseStateParam(stateParam);
|
|
213
|
+
return {
|
|
214
|
+
idToken: idToken ?? null,
|
|
215
|
+
authCode: authCode ?? null,
|
|
216
|
+
sessionKey: stateData.sessionKey,
|
|
217
|
+
provider: stateData.provider ?? null,
|
|
218
|
+
publicKey: stateData.publicKey ?? null,
|
|
219
|
+
nonce: stateData.nonce ?? null,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Unified OAuth completion handler.
|
|
224
|
+
* Routes to the appropriate completion handler based on params:
|
|
225
|
+
* Priority: callbacks.onOauthSuccess > callbacks.onOauthRedirect > completeOauth
|
|
226
|
+
*
|
|
227
|
+
* This is the core completion logic used by InAppBrowser flows.
|
|
228
|
+
*/
|
|
229
|
+
async function completeOAuthFlow(params) {
|
|
230
|
+
const { provider, publicKey, oidcToken, sessionKey, callbacks, completeOauth, } = params;
|
|
231
|
+
const completionParams = {
|
|
232
|
+
oidcToken,
|
|
233
|
+
providerName: provider,
|
|
234
|
+
publicKey,
|
|
235
|
+
...(sessionKey && { sessionKey }),
|
|
236
|
+
};
|
|
237
|
+
if (callbacks?.onOauthSuccess) {
|
|
238
|
+
callbacks.onOauthSuccess(completionParams);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (callbacks?.onOauthRedirect) {
|
|
242
|
+
callbacks.onOauthRedirect({
|
|
243
|
+
idToken: oidcToken,
|
|
244
|
+
publicKey,
|
|
245
|
+
...(sessionKey && { sessionKey }),
|
|
246
|
+
});
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
await completeOauth(completionParams);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Unified PKCE flow handler for all PKCE-based OAuth providers.
|
|
253
|
+
* Handles the complete PKCE flow: verifier retrieval, token exchange, and completion routing.
|
|
254
|
+
*
|
|
255
|
+
* @param params - The PKCE flow parameters
|
|
256
|
+
* @returns A promise that resolves when the flow is complete
|
|
257
|
+
*/
|
|
258
|
+
async function handlePKCEFlow(params) {
|
|
259
|
+
const { provider, publicKey, authCode, sessionKey, callbacks, completeOauth, exchangeCodeForToken, } = params;
|
|
260
|
+
if (!authCode) {
|
|
261
|
+
throw new Error(`Missing authorization code from ${provider} OAuth`);
|
|
262
|
+
}
|
|
263
|
+
// Get and consume the stored verifier
|
|
264
|
+
const verifier = await consumePKCEVerifier(provider);
|
|
265
|
+
try {
|
|
266
|
+
// Exchange code for token
|
|
267
|
+
const oidcToken = await exchangeCodeForToken(verifier);
|
|
268
|
+
if (!oidcToken) {
|
|
269
|
+
throw new Error(`Missing oidcToken from ${provider} OAuth exchange`);
|
|
270
|
+
}
|
|
271
|
+
// Complete the OAuth flow
|
|
272
|
+
const completionParams = {
|
|
273
|
+
provider,
|
|
274
|
+
publicKey,
|
|
275
|
+
oidcToken,
|
|
276
|
+
completeOauth,
|
|
277
|
+
};
|
|
278
|
+
if (sessionKey) {
|
|
279
|
+
completionParams.sessionKey = sessionKey;
|
|
280
|
+
}
|
|
281
|
+
if (callbacks) {
|
|
282
|
+
completionParams.callbacks = callbacks;
|
|
283
|
+
}
|
|
284
|
+
await completeOAuthFlow(completionParams);
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
// Ensure cleanup even on error
|
|
288
|
+
const key = getPKCEVerifierKey(provider);
|
|
289
|
+
await AsyncStorage.removeItem(key).catch(() => {
|
|
290
|
+
/* ignore cleanup errors */
|
|
291
|
+
});
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// Function to exchange Facebook authorization code for token
|
|
296
|
+
async function exchangeCodeForToken(clientId, redirectUri, code, codeVerifier) {
|
|
297
|
+
const params = new URLSearchParams({
|
|
298
|
+
client_id: clientId,
|
|
299
|
+
redirect_uri: redirectUri,
|
|
300
|
+
code_verifier: codeVerifier,
|
|
301
|
+
code: code,
|
|
302
|
+
grant_type: "authorization_code",
|
|
303
|
+
});
|
|
304
|
+
const response = await fetch(FACEBOOK_GRAPH_URL, {
|
|
305
|
+
method: "POST",
|
|
306
|
+
headers: {
|
|
307
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
308
|
+
},
|
|
309
|
+
body: params.toString(),
|
|
310
|
+
});
|
|
311
|
+
if (!response.ok) {
|
|
312
|
+
const errorData = await response.json();
|
|
313
|
+
throw new Error(`Facebook token exchange failed: ${JSON.stringify(errorData)}`);
|
|
314
|
+
}
|
|
315
|
+
return await response.json();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export { APPLE_AUTH_URL, DISCORD_AUTH_URL, FACEBOOK_AUTH_URL, FACEBOOK_GRAPH_URL, GOOGLE_AUTH_URL, OAUTH_PROVIDER_CONFIGS, X_AUTH_URL, ZEROXKEY_OAUTH_ORIGIN_URL, ZEROXKEY_OAUTH_REDIRECT_URL, buildOAuthState, buildOAuthUrl, completeOAuthFlow, consumePKCEVerifier, exchangeCodeForToken, generateChallengePair, getPKCEVerifierKey, handlePKCEFlow, parseInAppBrowserResult, parseStateParam, storePKCEVerifier };
|
|
319
|
+
//# sourceMappingURL=oauth.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.mjs","sources":["../../src/utils/oauth.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export type TimerController = {
|
|
2
|
+
clear: () => void;
|
|
3
|
+
};
|
|
4
|
+
export type TimerMap = Record<string, TimerController>;
|
|
5
|
+
export declare const MAX_DELAY_MS = 2147483647;
|
|
6
|
+
/**
|
|
7
|
+
* @internal A drop-in replacement for `setTimeout` that supports arbitrarily long delays.
|
|
8
|
+
*
|
|
9
|
+
* ### Why?
|
|
10
|
+
* Browsers clamp `setTimeout` delays to a 32-bit signed integer,
|
|
11
|
+
* i.e. a maximum of ~24.8 days (`2_147_483_647ms`). Any larger value
|
|
12
|
+
* will fire almost immediately. This helper safely schedules timeouts
|
|
13
|
+
* that can be months or years into the future.
|
|
14
|
+
*
|
|
15
|
+
* ### How it works
|
|
16
|
+
* - On call, we record a fixed **target timestamp** (`now + delayMs`).
|
|
17
|
+
* - We schedule a single "leg" of at most ~24.8 days into the future,
|
|
18
|
+
* with an internal `tick` function as the callback.
|
|
19
|
+
* - When a leg expires, `tick` checks how much time is left:
|
|
20
|
+
* - If `remaining > 0`, it schedules the next leg (`min(MAX_DELAY_MS, remaining)`).
|
|
21
|
+
* - If `remaining <= 0`, the full delay has passed and we finally call your `cb()`.
|
|
22
|
+
* - At any time, only **one** leg is active. Calling `.clear()` cancels
|
|
23
|
+
* the current leg and prevents further hops.
|
|
24
|
+
*
|
|
25
|
+
* ### Benefits
|
|
26
|
+
* - Works seamlessly for both short and very long delays.
|
|
27
|
+
* - Survives tab sleep / system suspend: when the tab wakes,
|
|
28
|
+
* `tick` re-checks the target timestamp and fires immediately if overdue.
|
|
29
|
+
* - No drift accumulation: each hop recalculates based on the fixed target,
|
|
30
|
+
* so you always land as close as possible to the intended deadline.
|
|
31
|
+
*
|
|
32
|
+
* ### Example
|
|
33
|
+
* ```ts
|
|
34
|
+
* const controller = setCappedTimeout(() => {
|
|
35
|
+
* console.log("A whole year later!");
|
|
36
|
+
* }, 1000 * 60 * 60 * 24 * 365);
|
|
37
|
+
*
|
|
38
|
+
* // Cancel if needed
|
|
39
|
+
* controller.clear();
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @param cb - Function to run once the full delay has elapsed.
|
|
43
|
+
* @param delayMs - Delay in milliseconds (can exceed 2_147_483_647).
|
|
44
|
+
* @returns A controller with a `.clear()` method to cancel the timeout.
|
|
45
|
+
*/
|
|
46
|
+
export declare function setCappedTimeout(cb: () => void, delayMs: number): TimerController;
|
|
47
|
+
/** @internal Simple one-shot timeout that still returns a controller for uniformity. */
|
|
48
|
+
export declare function setTimeoutController(cb: () => void, delayMs: number): TimerController;
|
|
49
|
+
/** @internal Replace any existing timer for `key` with `controller`. */
|
|
50
|
+
export declare function putTimer(map: TimerMap, key: string, controller: TimerController): void;
|
|
51
|
+
/** @internal Clear a specific key (noop if missing). */
|
|
52
|
+
export declare function clearKey(map: TimerMap, key: string): void;
|
|
53
|
+
/** @internal Clear several keys at once (noop on missing). */
|
|
54
|
+
export declare function clearKeys(map: TimerMap, keys: string[]): void;
|
|
55
|
+
/** @internal Clear all timers in the map. */
|
|
56
|
+
export declare function clearAll(map: TimerMap): void;
|
|
57
|
+
/**
|
|
58
|
+
* @internal Convenience: set a capped timeout directly into the map for `key`.
|
|
59
|
+
* @param map The map to store the timer in (pass in the ref to the timer map)
|
|
60
|
+
* @param key The key to associate with the timer
|
|
61
|
+
* @param cb The callback to invoke when the timer expires
|
|
62
|
+
* @param delayMs The delay in milliseconds before the timer expires
|
|
63
|
+
* */
|
|
64
|
+
export declare function setCappedTimeoutInMap(map: TimerMap, key: string, cb: () => void, delayMs: number): void;
|
|
65
|
+
/**
|
|
66
|
+
* @internal Convenience: set a regular (short) timeout into the map for `key`.
|
|
67
|
+
* @param map The map to store the timer in (pass in the ref to the timer map)
|
|
68
|
+
* @param key The key to associate with the timer
|
|
69
|
+
* @param cb The callback to invoke when the timer expires
|
|
70
|
+
* @param delayMs The delay in milliseconds before the timer expires
|
|
71
|
+
*/
|
|
72
|
+
export declare function setTimeoutInMap(map: TimerMap, key: string, cb: () => void, delayMs: number): void;
|
|
73
|
+
//# sourceMappingURL=timers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timers.d.ts","sourceRoot":"","sources":["../../src/utils/timers.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,eAAe,GAAG;IAAE,KAAK,EAAE,MAAM,IAAI,CAAA;CAAE,CAAC;AACpD,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAEvD,eAAO,MAAM,YAAY,aAAgB,CAAC;AAO1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,IAAI,EACd,OAAO,EAAE,MAAM,GACd,eAAe,CAuBjB;AAED,wFAAwF;AACxF,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,MAAM,IAAI,EACd,OAAO,EAAE,MAAM,GACd,eAAe,CAIjB;AAED,wEAAwE;AACxE,wBAAgB,QAAQ,CACtB,GAAG,EAAE,QAAQ,EACb,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,eAAe,QAI5B;AAED,wDAAwD;AACxD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,QAGlD;AAED,8DAA8D;AAC9D,wBAAgB,SAAS,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,QAEtD;AAED,6CAA6C;AAC7C,wBAAgB,QAAQ,CAAC,GAAG,EAAE,QAAQ,QAKrC;AAED;;;;;;KAMK;AACL,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,QAAQ,EACb,GAAG,EAAE,MAAM,EACX,EAAE,EAAE,MAAM,IAAI,EACd,OAAO,EAAE,MAAM,QAGhB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,QAAQ,EACb,GAAG,EAAE,MAAM,EACX,EAAE,EAAE,MAAM,IAAI,EACd,OAAO,EAAE,MAAM,QAGhB"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// This file contains utility functions for managing timers and timeouts for session management.
|
|
4
|
+
// Unfortunately, the delay param in Node Timeouts is a 32-bit signed integer, meaning it can only represent delays up to ~24.8 days.
|
|
5
|
+
// This file provides functions to handle long delays by breaking them into smaller chunks.
|
|
6
|
+
// These functions are separate from the normal utils function to reduce clutter and improve visibility.
|
|
7
|
+
// Use `setTimeoutInMap` for short, frequent nudges (10s), and `setCappedTimeoutInMap` for anything that could exceed ~24.8 days.
|
|
8
|
+
const MAX_DELAY_MS = 2_147_483_647; // ~24.8 days
|
|
9
|
+
/** @internal */
|
|
10
|
+
function toIntMs(x) {
|
|
11
|
+
return Math.max(0, Math.floor(Number.isFinite(x) ? x : 0));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* @internal A drop-in replacement for `setTimeout` that supports arbitrarily long delays.
|
|
15
|
+
*
|
|
16
|
+
* ### Why?
|
|
17
|
+
* Browsers clamp `setTimeout` delays to a 32-bit signed integer,
|
|
18
|
+
* i.e. a maximum of ~24.8 days (`2_147_483_647ms`). Any larger value
|
|
19
|
+
* will fire almost immediately. This helper safely schedules timeouts
|
|
20
|
+
* that can be months or years into the future.
|
|
21
|
+
*
|
|
22
|
+
* ### How it works
|
|
23
|
+
* - On call, we record a fixed **target timestamp** (`now + delayMs`).
|
|
24
|
+
* - We schedule a single "leg" of at most ~24.8 days into the future,
|
|
25
|
+
* with an internal `tick` function as the callback.
|
|
26
|
+
* - When a leg expires, `tick` checks how much time is left:
|
|
27
|
+
* - If `remaining > 0`, it schedules the next leg (`min(MAX_DELAY_MS, remaining)`).
|
|
28
|
+
* - If `remaining <= 0`, the full delay has passed and we finally call your `cb()`.
|
|
29
|
+
* - At any time, only **one** leg is active. Calling `.clear()` cancels
|
|
30
|
+
* the current leg and prevents further hops.
|
|
31
|
+
*
|
|
32
|
+
* ### Benefits
|
|
33
|
+
* - Works seamlessly for both short and very long delays.
|
|
34
|
+
* - Survives tab sleep / system suspend: when the tab wakes,
|
|
35
|
+
* `tick` re-checks the target timestamp and fires immediately if overdue.
|
|
36
|
+
* - No drift accumulation: each hop recalculates based on the fixed target,
|
|
37
|
+
* so you always land as close as possible to the intended deadline.
|
|
38
|
+
*
|
|
39
|
+
* ### Example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const controller = setCappedTimeout(() => {
|
|
42
|
+
* console.log("A whole year later!");
|
|
43
|
+
* }, 1000 * 60 * 60 * 24 * 365);
|
|
44
|
+
*
|
|
45
|
+
* // Cancel if needed
|
|
46
|
+
* controller.clear();
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @param cb - Function to run once the full delay has elapsed.
|
|
50
|
+
* @param delayMs - Delay in milliseconds (can exceed 2_147_483_647).
|
|
51
|
+
* @returns A controller with a `.clear()` method to cancel the timeout.
|
|
52
|
+
*/
|
|
53
|
+
function setCappedTimeout(cb, delayMs) {
|
|
54
|
+
const target = Date.now() + toIntMs(delayMs);
|
|
55
|
+
let handle;
|
|
56
|
+
const tick = () => {
|
|
57
|
+
const remaining = target - Date.now();
|
|
58
|
+
if (remaining <= 0) {
|
|
59
|
+
cb();
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
handle = setTimeout(tick, Math.min(MAX_DELAY_MS, remaining));
|
|
63
|
+
};
|
|
64
|
+
tick();
|
|
65
|
+
return {
|
|
66
|
+
clear() {
|
|
67
|
+
if (handle !== undefined) {
|
|
68
|
+
clearTimeout(handle);
|
|
69
|
+
handle = undefined;
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/** @internal Simple one-shot timeout that still returns a controller for uniformity. */
|
|
75
|
+
function setTimeoutController(cb, delayMs) {
|
|
76
|
+
const ms = toIntMs(delayMs);
|
|
77
|
+
const id = setTimeout(cb, ms);
|
|
78
|
+
return { clear: () => clearTimeout(id) };
|
|
79
|
+
}
|
|
80
|
+
/** @internal Replace any existing timer for `key` with `controller`. */
|
|
81
|
+
function putTimer(map, key, controller) {
|
|
82
|
+
map[key]?.clear?.();
|
|
83
|
+
map[key] = controller;
|
|
84
|
+
}
|
|
85
|
+
/** @internal Clear a specific key (noop if missing). */
|
|
86
|
+
function clearKey(map, key) {
|
|
87
|
+
map[key]?.clear?.();
|
|
88
|
+
delete map[key];
|
|
89
|
+
}
|
|
90
|
+
/** @internal Clear several keys at once (noop on missing). */
|
|
91
|
+
function clearKeys(map, keys) {
|
|
92
|
+
for (const k of keys)
|
|
93
|
+
clearKey(map, k);
|
|
94
|
+
}
|
|
95
|
+
/** @internal Clear all timers in the map. */
|
|
96
|
+
function clearAll(map) {
|
|
97
|
+
for (const k of Object.keys(map)) {
|
|
98
|
+
map[k]?.clear?.();
|
|
99
|
+
delete map[k];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* @internal Convenience: set a capped timeout directly into the map for `key`.
|
|
104
|
+
* @param map The map to store the timer in (pass in the ref to the timer map)
|
|
105
|
+
* @param key The key to associate with the timer
|
|
106
|
+
* @param cb The callback to invoke when the timer expires
|
|
107
|
+
* @param delayMs The delay in milliseconds before the timer expires
|
|
108
|
+
* */
|
|
109
|
+
function setCappedTimeoutInMap(map, key, cb, delayMs) {
|
|
110
|
+
putTimer(map, key, setCappedTimeout(cb, delayMs));
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* @internal Convenience: set a regular (short) timeout into the map for `key`.
|
|
114
|
+
* @param map The map to store the timer in (pass in the ref to the timer map)
|
|
115
|
+
* @param key The key to associate with the timer
|
|
116
|
+
* @param cb The callback to invoke when the timer expires
|
|
117
|
+
* @param delayMs The delay in milliseconds before the timer expires
|
|
118
|
+
*/
|
|
119
|
+
function setTimeoutInMap(map, key, cb, delayMs) {
|
|
120
|
+
putTimer(map, key, setTimeoutController(cb, delayMs));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
exports.MAX_DELAY_MS = MAX_DELAY_MS;
|
|
124
|
+
exports.clearAll = clearAll;
|
|
125
|
+
exports.clearKey = clearKey;
|
|
126
|
+
exports.clearKeys = clearKeys;
|
|
127
|
+
exports.putTimer = putTimer;
|
|
128
|
+
exports.setCappedTimeout = setCappedTimeout;
|
|
129
|
+
exports.setCappedTimeoutInMap = setCappedTimeoutInMap;
|
|
130
|
+
exports.setTimeoutController = setTimeoutController;
|
|
131
|
+
exports.setTimeoutInMap = setTimeoutInMap;
|
|
132
|
+
//# sourceMappingURL=timers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timers.js","sources":["../../src/utils/timers.ts"],"sourcesContent":[null],"names":[],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AAMO,MAAM,YAAY,GAAG,cAAc;AAE1C;AACA,SAAS,OAAO,CAAC,CAAS,EAAA;IACxB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AAC5D;AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCG;AACG,SAAU,gBAAgB,CAC9B,EAAc,EACd,OAAe,EAAA;IAEf,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;AAC5C,IAAA,IAAI,MAAiD;IAErD,MAAM,IAAI,GAAG,MAAK;QAChB,MAAM,SAAS,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE;AACrC,QAAA,IAAI,SAAS,IAAI,CAAC,EAAE;AAClB,YAAA,EAAE,EAAE;YACJ;QACF;AACA,QAAA,MAAM,GAAG,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;AAC9D,IAAA,CAAC;AAED,IAAA,IAAI,EAAE;IAEN,OAAO;QACL,KAAK,GAAA;AACH,YAAA,IAAI,MAAM,KAAK,SAAS,EAAE;gBACxB,YAAY,CAAC,MAAM,CAAC;gBACpB,MAAM,GAAG,SAAS;YACpB;QACF,CAAC;KACF;AACH;AAEA;AACM,SAAU,oBAAoB,CAClC,EAAc,EACd,OAAe,EAAA;AAEf,IAAA,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAC3B,MAAM,EAAE,GAAG,UAAU,CAAC,EAAE,EAAE,EAAE,CAAC;IAC7B,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC,EAAE,CAAC,EAAE;AAC1C;AAEA;SACgB,QAAQ,CACtB,GAAa,EACb,GAAW,EACX,UAA2B,EAAA;AAE3B,IAAA,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,IAAI;AACnB,IAAA,GAAG,CAAC,GAAG,CAAC,GAAG,UAAU;AACvB;AAEA;AACM,SAAU,QAAQ,CAAC,GAAa,EAAE,GAAW,EAAA;AACjD,IAAA,GAAG,CAAC,GAAG,CAAC,EAAE,KAAK,IAAI;AACnB,IAAA,OAAO,GAAG,CAAC,GAAG,CAAC;AACjB;AAEA;AACM,SAAU,SAAS,CAAC,GAAa,EAAE,IAAc,EAAA;IACrD,KAAK,MAAM,CAAC,IAAI,IAAI;AAAE,QAAA,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC;AACxC;AAEA;AACM,SAAU,QAAQ,CAAC,GAAa,EAAA;IACpC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;AAChC,QAAA,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI;AACjB,QAAA,OAAO,GAAG,CAAC,CAAC,CAAC;IACf;AACF;AAEA;;;;;;AAMK;AACC,SAAU,qBAAqB,CACnC,GAAa,EACb,GAAW,EACX,EAAc,EACd,OAAe,EAAA;AAEf,IAAA,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,gBAAgB,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;AACnD;AAEA;;;;;;AAMG;AACG,SAAU,eAAe,CAC7B,GAAa,EACb,GAAW,EACX,EAAc,EACd,OAAe,EAAA;AAEf,IAAA,QAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,oBAAoB,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;AACvD;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// This file contains utility functions for managing timers and timeouts for session management.
|
|
2
|
+
// Unfortunately, the delay param in Node Timeouts is a 32-bit signed integer, meaning it can only represent delays up to ~24.8 days.
|
|
3
|
+
// This file provides functions to handle long delays by breaking them into smaller chunks.
|
|
4
|
+
// These functions are separate from the normal utils function to reduce clutter and improve visibility.
|
|
5
|
+
// Use `setTimeoutInMap` for short, frequent nudges (10s), and `setCappedTimeoutInMap` for anything that could exceed ~24.8 days.
|
|
6
|
+
const MAX_DELAY_MS = 2_147_483_647; // ~24.8 days
|
|
7
|
+
/** @internal */
|
|
8
|
+
function toIntMs(x) {
|
|
9
|
+
return Math.max(0, Math.floor(Number.isFinite(x) ? x : 0));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* @internal A drop-in replacement for `setTimeout` that supports arbitrarily long delays.
|
|
13
|
+
*
|
|
14
|
+
* ### Why?
|
|
15
|
+
* Browsers clamp `setTimeout` delays to a 32-bit signed integer,
|
|
16
|
+
* i.e. a maximum of ~24.8 days (`2_147_483_647ms`). Any larger value
|
|
17
|
+
* will fire almost immediately. This helper safely schedules timeouts
|
|
18
|
+
* that can be months or years into the future.
|
|
19
|
+
*
|
|
20
|
+
* ### How it works
|
|
21
|
+
* - On call, we record a fixed **target timestamp** (`now + delayMs`).
|
|
22
|
+
* - We schedule a single "leg" of at most ~24.8 days into the future,
|
|
23
|
+
* with an internal `tick` function as the callback.
|
|
24
|
+
* - When a leg expires, `tick` checks how much time is left:
|
|
25
|
+
* - If `remaining > 0`, it schedules the next leg (`min(MAX_DELAY_MS, remaining)`).
|
|
26
|
+
* - If `remaining <= 0`, the full delay has passed and we finally call your `cb()`.
|
|
27
|
+
* - At any time, only **one** leg is active. Calling `.clear()` cancels
|
|
28
|
+
* the current leg and prevents further hops.
|
|
29
|
+
*
|
|
30
|
+
* ### Benefits
|
|
31
|
+
* - Works seamlessly for both short and very long delays.
|
|
32
|
+
* - Survives tab sleep / system suspend: when the tab wakes,
|
|
33
|
+
* `tick` re-checks the target timestamp and fires immediately if overdue.
|
|
34
|
+
* - No drift accumulation: each hop recalculates based on the fixed target,
|
|
35
|
+
* so you always land as close as possible to the intended deadline.
|
|
36
|
+
*
|
|
37
|
+
* ### Example
|
|
38
|
+
* ```ts
|
|
39
|
+
* const controller = setCappedTimeout(() => {
|
|
40
|
+
* console.log("A whole year later!");
|
|
41
|
+
* }, 1000 * 60 * 60 * 24 * 365);
|
|
42
|
+
*
|
|
43
|
+
* // Cancel if needed
|
|
44
|
+
* controller.clear();
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @param cb - Function to run once the full delay has elapsed.
|
|
48
|
+
* @param delayMs - Delay in milliseconds (can exceed 2_147_483_647).
|
|
49
|
+
* @returns A controller with a `.clear()` method to cancel the timeout.
|
|
50
|
+
*/
|
|
51
|
+
function setCappedTimeout(cb, delayMs) {
|
|
52
|
+
const target = Date.now() + toIntMs(delayMs);
|
|
53
|
+
let handle;
|
|
54
|
+
const tick = () => {
|
|
55
|
+
const remaining = target - Date.now();
|
|
56
|
+
if (remaining <= 0) {
|
|
57
|
+
cb();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
handle = setTimeout(tick, Math.min(MAX_DELAY_MS, remaining));
|
|
61
|
+
};
|
|
62
|
+
tick();
|
|
63
|
+
return {
|
|
64
|
+
clear() {
|
|
65
|
+
if (handle !== undefined) {
|
|
66
|
+
clearTimeout(handle);
|
|
67
|
+
handle = undefined;
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** @internal Simple one-shot timeout that still returns a controller for uniformity. */
|
|
73
|
+
function setTimeoutController(cb, delayMs) {
|
|
74
|
+
const ms = toIntMs(delayMs);
|
|
75
|
+
const id = setTimeout(cb, ms);
|
|
76
|
+
return { clear: () => clearTimeout(id) };
|
|
77
|
+
}
|
|
78
|
+
/** @internal Replace any existing timer for `key` with `controller`. */
|
|
79
|
+
function putTimer(map, key, controller) {
|
|
80
|
+
map[key]?.clear?.();
|
|
81
|
+
map[key] = controller;
|
|
82
|
+
}
|
|
83
|
+
/** @internal Clear a specific key (noop if missing). */
|
|
84
|
+
function clearKey(map, key) {
|
|
85
|
+
map[key]?.clear?.();
|
|
86
|
+
delete map[key];
|
|
87
|
+
}
|
|
88
|
+
/** @internal Clear several keys at once (noop on missing). */
|
|
89
|
+
function clearKeys(map, keys) {
|
|
90
|
+
for (const k of keys)
|
|
91
|
+
clearKey(map, k);
|
|
92
|
+
}
|
|
93
|
+
/** @internal Clear all timers in the map. */
|
|
94
|
+
function clearAll(map) {
|
|
95
|
+
for (const k of Object.keys(map)) {
|
|
96
|
+
map[k]?.clear?.();
|
|
97
|
+
delete map[k];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* @internal Convenience: set a capped timeout directly into the map for `key`.
|
|
102
|
+
* @param map The map to store the timer in (pass in the ref to the timer map)
|
|
103
|
+
* @param key The key to associate with the timer
|
|
104
|
+
* @param cb The callback to invoke when the timer expires
|
|
105
|
+
* @param delayMs The delay in milliseconds before the timer expires
|
|
106
|
+
* */
|
|
107
|
+
function setCappedTimeoutInMap(map, key, cb, delayMs) {
|
|
108
|
+
putTimer(map, key, setCappedTimeout(cb, delayMs));
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* @internal Convenience: set a regular (short) timeout into the map for `key`.
|
|
112
|
+
* @param map The map to store the timer in (pass in the ref to the timer map)
|
|
113
|
+
* @param key The key to associate with the timer
|
|
114
|
+
* @param cb The callback to invoke when the timer expires
|
|
115
|
+
* @param delayMs The delay in milliseconds before the timer expires
|
|
116
|
+
*/
|
|
117
|
+
function setTimeoutInMap(map, key, cb, delayMs) {
|
|
118
|
+
putTimer(map, key, setTimeoutController(cb, delayMs));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export { MAX_DELAY_MS, clearAll, clearKey, clearKeys, putTimer, setCappedTimeout, setCappedTimeoutInMap, setTimeoutController, setTimeoutInMap };
|
|
122
|
+
//# sourceMappingURL=timers.mjs.map
|