@instantdb/core 0.22.167 → 0.22.168

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@instantdb/core",
3
- "version": "0.22.167",
3
+ "version": "0.22.168",
4
4
  "description": "Instant's core local abstraction",
5
5
  "homepage": "https://github.com/instantdb/instant/tree/main/client/packages/core",
6
6
  "repository": {
@@ -56,13 +56,14 @@
56
56
  "dependencies": {
57
57
  "mutative": "^1.0.10",
58
58
  "uuid": "^11.1.0",
59
- "@instantdb/version": "0.22.167"
59
+ "@instantdb/version": "0.22.168"
60
60
  },
61
61
  "scripts": {
62
62
  "test": "vitest",
63
63
  "bench": "vitest bench",
64
64
  "test:types": "tsc -p tsconfig.test.json --noEmit",
65
65
  "test:ci": "vitest run && pnpm run test:types",
66
+ "test:e2e": "vitest run --project e2e",
66
67
  "bench:ci": "vitest bench --run",
67
68
  "check": "tsc --noEmit",
68
69
  "check-exports": "attw --pack .",
package/src/Reactor.js CHANGED
@@ -63,6 +63,9 @@ const defaultConfig = {
63
63
 
64
64
  // Param that the backend adds if this is an oauth redirect
65
65
  const OAUTH_REDIRECT_PARAM = '_instant_oauth_redirect';
66
+ const OAUTH_EXTRA_FIELDS_ID_PARAM = '_instant_extra_fields_id';
67
+
68
+ const oauthExtraFieldsKey = 'oauthExtraFields';
66
69
 
67
70
  const currentUserKey = `currentUser`;
68
71
 
@@ -1877,6 +1880,7 @@ export default class Reactor {
1877
1880
  if (url.searchParams.get(OAUTH_REDIRECT_PARAM)) {
1878
1881
  const startUrl = url.toString();
1879
1882
  url.searchParams.delete(OAUTH_REDIRECT_PARAM);
1883
+ url.searchParams.delete(OAUTH_EXTRA_FIELDS_ID_PARAM);
1880
1884
  url.searchParams.delete('code');
1881
1885
  url.searchParams.delete('error');
1882
1886
  const newPath =
@@ -1949,8 +1953,20 @@ export default class Reactor {
1949
1953
  if (!code) {
1950
1954
  return null;
1951
1955
  }
1956
+ const extraFieldsId = params.get(OAUTH_EXTRA_FIELDS_ID_PARAM);
1952
1957
  this._replaceUrlAfterOAuth();
1953
1958
  try {
1959
+ let extraFields;
1960
+ const stored = await this.kv.waitForKeyToLoad(oauthExtraFieldsKey);
1961
+ if (extraFieldsId && stored) {
1962
+ extraFields = stored[extraFieldsId];
1963
+ }
1964
+ // Clean up all stored extraFields after login
1965
+ if (stored) {
1966
+ this.kv.updateInPlace((prev) => {
1967
+ delete prev[oauthExtraFieldsKey];
1968
+ });
1969
+ }
1954
1970
  const currentUser = await this._getCurrentUser();
1955
1971
  const isGuest = currentUser?.type === 'guest';
1956
1972
  const { user } = await authAPI.exchangeCodeForToken({
@@ -1958,6 +1974,7 @@ export default class Reactor {
1958
1974
  appId: this.config.appId,
1959
1975
  code,
1960
1976
  refreshToken: isGuest ? currentUser.refresh_token : undefined,
1977
+ extraFields,
1961
1978
  });
1962
1979
  this.setCurrentUser(user);
1963
1980
  return null;
@@ -2199,15 +2216,16 @@ export default class Reactor {
2199
2216
  });
2200
2217
  }
2201
2218
 
2202
- async signInWithMagicCode({ email, code }) {
2219
+ async signInWithMagicCode(params) {
2203
2220
  const currentUser = await this.getCurrentUser();
2204
2221
  const isGuest = currentUser?.user?.type === 'guest';
2205
- const res = await authAPI.verifyMagicCode({
2222
+ const res = await authAPI.checkMagicCode({
2206
2223
  apiURI: this.config.apiURI,
2207
2224
  appId: this.config.appId,
2208
- email,
2209
- code,
2225
+ email: params.email,
2226
+ code: params.code,
2210
2227
  refreshToken: isGuest ? currentUser?.user?.refresh_token : undefined,
2228
+ extraFields: params.extraFields,
2211
2229
  });
2212
2230
  await this.changeCurrentUser(res.user);
2213
2231
  return res;
@@ -2266,19 +2284,36 @@ export default class Reactor {
2266
2284
  * @param {Object} params - The parameters to create the authorization URL.
2267
2285
  * @param {string} params.clientName - The name of the client requesting authorization.
2268
2286
  * @param {string} params.redirectURL - The URL to redirect users to after authorization.
2287
+ * @param {Record<string, any>} [params.extraFields] - Extra fields to write to $users on creation
2269
2288
  * @returns {string} The created authorization URL.
2270
2289
  */
2271
- createAuthorizationURL({ clientName, redirectURL }) {
2290
+ createAuthorizationURL({ clientName, redirectURL, extraFields }) {
2272
2291
  const { apiURI, appId } = this.config;
2273
- return `${apiURI}/runtime/oauth/start?app_id=${appId}&client_name=${clientName}&redirect_uri=${redirectURL}`;
2292
+ let finalRedirectURL = redirectURL;
2293
+ if (extraFields) {
2294
+ // Store extraFields under a unique ID so multiple
2295
+ // createAuthorizationURL calls don't overwrite each other.
2296
+ // The ID is passed through the redirect URL and used
2297
+ // by _oauthLoginInit to retrieve the right extraFields.
2298
+ // All entries are cleaned up after login.
2299
+ const extraFieldsId = `${Math.random().toString(36).slice(2)}`;
2300
+ this.kv.updateInPlace((prev) => {
2301
+ const stored = prev[oauthExtraFieldsKey] || {};
2302
+ stored[extraFieldsId] = extraFields;
2303
+ prev[oauthExtraFieldsKey] = stored;
2304
+ });
2305
+ finalRedirectURL = `${redirectURL}${redirectURL.includes('?') ? '&' : '?'}${OAUTH_EXTRA_FIELDS_ID_PARAM}=${extraFieldsId}`;
2306
+ }
2307
+ return `${apiURI}/runtime/oauth/start?app_id=${appId}&client_name=${clientName}&redirect_uri=${encodeURIComponent(finalRedirectURL)}`;
2274
2308
  }
2275
2309
 
2276
2310
  /**
2277
2311
  * @param {Object} params
2278
2312
  * @param {string} params.code - The code received from the OAuth service.
2279
2313
  * @param {string} [params.codeVerifier] - The code verifier used to generate the code challenge.
2314
+ * @param {Record<string, any>} [params.extraFields] - Extra fields to write to $users on creation
2280
2315
  */
2281
- async exchangeCodeForToken({ code, codeVerifier }) {
2316
+ async exchangeCodeForToken({ code, codeVerifier, extraFields }) {
2282
2317
  const currentUser = await this.getCurrentUser();
2283
2318
  const isGuest = currentUser?.user?.type === 'guest';
2284
2319
  const res = await authAPI.exchangeCodeForToken({
@@ -2287,6 +2322,7 @@ export default class Reactor {
2287
2322
  code: code,
2288
2323
  codeVerifier,
2289
2324
  refreshToken: isGuest ? currentUser?.user?.refresh_token : undefined,
2325
+ extraFields,
2290
2326
  });
2291
2327
  await this.changeCurrentUser(res.user);
2292
2328
  return res;
@@ -2302,18 +2338,20 @@ export default class Reactor {
2302
2338
  * @param {string} params.clientName - The name of the client requesting authorization.
2303
2339
  * @param {string} params.idToken - The id_token from the external service
2304
2340
  * @param {string | null | undefined} [params.nonce] - The nonce used when requesting the id_token from the external service
2341
+ * @param {Record<string, any>} [params.extraFields] - Extra fields to write to $users on creation
2305
2342
  */
2306
- async signInWithIdToken({ idToken, clientName, nonce }) {
2343
+ async signInWithIdToken(params) {
2307
2344
  const currentUser = await this.getCurrentUser();
2308
2345
  const refreshToken = currentUser?.user?.refresh_token;
2309
2346
 
2310
2347
  const res = await authAPI.signInWithIdToken({
2311
2348
  apiURI: this.config.apiURI,
2312
2349
  appId: this.config.appId,
2313
- idToken,
2314
- clientName,
2315
- nonce,
2350
+ idToken: params.idToken,
2351
+ clientName: params.clientName,
2352
+ nonce: params.nonce,
2316
2353
  refreshToken,
2354
+ extraFields: params.extraFields,
2317
2355
  });
2318
2356
  await this.changeCurrentUser(res.user);
2319
2357
  return res;
package/src/authAPI.ts CHANGED
@@ -31,6 +31,11 @@ export type VerifyMagicCodeParams = {
31
31
  export type VerifyResponse = {
32
32
  user: User;
33
33
  };
34
+
35
+ /**
36
+ * @deprecated Use {@link checkMagicCode} instead to get the `created` field
37
+ * and support `extraFields`.
38
+ */
34
39
  export async function verifyMagicCode({
35
40
  apiURI,
36
41
  appId,
@@ -51,6 +56,38 @@ export async function verifyMagicCode({
51
56
  return res;
52
57
  }
53
58
 
59
+ export type CheckMagicCodeParams = {
60
+ email: string;
61
+ code: string;
62
+ refreshToken?: string | undefined;
63
+ extraFields?: Record<string, any> | undefined;
64
+ };
65
+ export type CheckMagicCodeResponse = {
66
+ user: User;
67
+ created: boolean;
68
+ };
69
+ export async function checkMagicCode({
70
+ apiURI,
71
+ appId,
72
+ email,
73
+ code,
74
+ refreshToken,
75
+ extraFields,
76
+ }: SharedInput & CheckMagicCodeParams): Promise<CheckMagicCodeResponse> {
77
+ const res = await jsonFetch(`${apiURI}/runtime/auth/verify_magic_code`, {
78
+ method: 'POST',
79
+ headers: { 'content-type': 'application/json' },
80
+ body: JSON.stringify({
81
+ 'app-id': appId,
82
+ email,
83
+ code,
84
+ ...(refreshToken ? { 'refresh-token': refreshToken } : {}),
85
+ ...(extraFields ? { 'extra-fields': extraFields } : {}),
86
+ }),
87
+ });
88
+ return res;
89
+ }
90
+
54
91
  export type VerifyRefreshTokenParams = { refreshToken: string };
55
92
  export async function verifyRefreshToken({
56
93
  apiURI,
@@ -86,6 +123,7 @@ export type ExchangeCodeForTokenParams = {
86
123
  code: string;
87
124
  codeVerifier?: string;
88
125
  refreshToken?: string | undefined;
126
+ extraFields?: Record<string, any> | undefined;
89
127
  };
90
128
 
91
129
  export async function exchangeCodeForToken({
@@ -94,7 +132,8 @@ export async function exchangeCodeForToken({
94
132
  code,
95
133
  codeVerifier,
96
134
  refreshToken,
97
- }: SharedInput & ExchangeCodeForTokenParams): Promise<VerifyResponse> {
135
+ extraFields,
136
+ }: SharedInput & ExchangeCodeForTokenParams): Promise<CheckMagicCodeResponse> {
98
137
  const res = await jsonFetch(`${apiURI}/runtime/oauth/token`, {
99
138
  method: 'POST',
100
139
  headers: { 'content-type': 'application/json' },
@@ -103,6 +142,7 @@ export async function exchangeCodeForToken({
103
142
  code: code,
104
143
  code_verifier: codeVerifier,
105
144
  refresh_token: refreshToken,
145
+ ...(extraFields ? { extra_fields: extraFields } : {}),
106
146
  }),
107
147
  });
108
148
  return res;
@@ -113,6 +153,7 @@ export type SignInWithIdTokenParams = {
113
153
  idToken: string;
114
154
  clientName: string;
115
155
  refreshToken?: string;
156
+ extraFields?: Record<string, any> | undefined;
116
157
  };
117
158
 
118
159
  export async function signInWithIdToken({
@@ -122,7 +163,8 @@ export async function signInWithIdToken({
122
163
  idToken,
123
164
  clientName,
124
165
  refreshToken,
125
- }: SharedInput & SignInWithIdTokenParams): Promise<VerifyResponse> {
166
+ extraFields,
167
+ }: SharedInput & SignInWithIdTokenParams): Promise<CheckMagicCodeResponse> {
126
168
  const res = await jsonFetch(`${apiURI}/runtime/oauth/id_token`, {
127
169
  method: 'POST',
128
170
  headers: { 'content-type': 'application/json' },
@@ -132,6 +174,7 @@ export async function signInWithIdToken({
132
174
  id_token: idToken,
133
175
  client_name: clientName,
134
176
  refresh_token: refreshToken,
177
+ ...(extraFields ? { extra_fields: extraFields } : {}),
135
178
  }),
136
179
  });
137
180
  return res;
package/src/index.ts CHANGED
@@ -114,6 +114,8 @@ import type { UploadFileResponse, DeleteFileResponse } from './StorageAPI.ts';
114
114
  import { FrameworkClient, type FrameworkConfig } from './framework.ts';
115
115
 
116
116
  import type {
117
+ CheckMagicCodeParams,
118
+ CheckMagicCodeResponse,
117
119
  ExchangeCodeForTokenParams,
118
120
  SendMagicCodeParams,
119
121
  SendMagicCodeResponse,
@@ -347,8 +349,8 @@ class Auth {
347
349
  * .catch((err) => console.error(err.body?.message))
348
350
  */
349
351
  signInWithMagicCode = (
350
- params: VerifyMagicCodeParams,
351
- ): Promise<VerifyResponse> => {
352
+ params: CheckMagicCodeParams,
353
+ ): Promise<CheckMagicCodeResponse> => {
352
354
  return this.db.signInWithMagicCode(params);
353
355
  };
354
356
 
@@ -397,6 +399,7 @@ class Auth {
397
399
  createAuthorizationURL = (params: {
398
400
  clientName: string;
399
401
  redirectURL: string;
402
+ extraFields?: Record<string, any>;
400
403
  }): string => {
401
404
  return this.db.createAuthorizationURL(params);
402
405
  };
@@ -421,7 +424,7 @@ class Auth {
421
424
  */
422
425
  signInWithIdToken = (
423
426
  params: SignInWithIdTokenParams,
424
- ): Promise<VerifyResponse> => {
427
+ ): Promise<CheckMagicCodeResponse> => {
425
428
  return this.db.signInWithIdToken(params);
426
429
  };
427
430
 
@@ -441,7 +444,9 @@ class Auth {
441
444
  * .catch((err) => console.error(err.body?.message));
442
445
  *
443
446
  */
444
- exchangeOAuthCode = (params: ExchangeCodeForTokenParams) => {
447
+ exchangeOAuthCode = (
448
+ params: ExchangeCodeForTokenParams,
449
+ ): Promise<CheckMagicCodeResponse> => {
445
450
  return this.db.exchangeCodeForToken(params);
446
451
  };
447
452
 
@@ -1155,6 +1160,8 @@ export {
1155
1160
  type InstantDBInferredType,
1156
1161
 
1157
1162
  // auth types
1163
+ type CheckMagicCodeParams,
1164
+ type CheckMagicCodeResponse,
1158
1165
  type ExchangeCodeForTokenParams,
1159
1166
  type SendMagicCodeParams,
1160
1167
  type SendMagicCodeResponse,
package/vitest.config.ts CHANGED
@@ -1,11 +1,17 @@
1
1
  import { playwright } from '@vitest/browser-playwright';
2
2
  import { defineConfig } from 'vitest/config';
3
3
 
4
+ const devSlot = Number(process.env.DEV_SLOT ?? 0);
5
+ const localPort = process.env.CI ? 0 : 8888 + devSlot * 1000;
6
+
4
7
  export default defineConfig({
5
8
  test: {
6
9
  projects: [
7
10
  {
8
11
  extends: true,
12
+ define: {
13
+ __DEV_LOCAL_PORT__: localPort,
14
+ },
9
15
  test: {
10
16
  name: 'e2e',
11
17
  include: ['**/**.e2e.test.ts'],