nitro-web 0.0.207 → 0.1.2

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/client/app.tsx CHANGED
@@ -15,7 +15,7 @@ type LayoutProps = {
15
15
 
16
16
  type Settings = {
17
17
  afterApp?: () => void
18
- beforeApp: (config: Config) => Promise<object>
18
+ beforeApp: (config: Config) => Promise<unknown>
19
19
  // beforeStoreUpdate: (prevStore: Store | null, newData: Store) => Store
20
20
  isStatic?: boolean
21
21
  layouts: React.FC<LayoutProps>[]
@@ -6,12 +6,14 @@ import passportLocal from 'passport-local'
6
6
  import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt'
7
7
  import db, { isId } from 'monastery'
8
8
  import jsonwebtoken from 'jsonwebtoken'
9
+ import { getDomain } from 'tldts'
9
10
  import { sendEmail } from 'nitro-web/server'
10
11
  import { isArray, pick, ucFirst, fullNameSplit } from 'nitro-web/util'
11
12
  // Todo: detect if the user is already invited to the company, instead of token error
12
13
 
13
14
  const JWT_SECRET = process.env.JWT_SECRET || 'replace_this_with_secure_env_secret'
14
15
  let authConfig = null
16
+
15
17
  export const auth = {
16
18
  userFindFromProvider, userSigninGetStore, getStore,
17
19
  userCreate, passwordValidate, tokenCreate, tokenParse, tokenSend,
@@ -37,7 +39,7 @@ function setup(middleware, _config, helpers = {}) {
37
39
  // Tip: you can pass in your own helpers to override the default helpers, internally all functions are called
38
40
  // with `auth` as the context, so `this` context contains all helpers.
39
41
  // E.g. setup: (middleware, config, helpers) => authRoutes.setup(middleware, config, { getStore, ... })
40
- const configKeys = ['baseUrl', 'emailFrom', 'env', 'name', 'mailgunDomain', 'mailgunKey', 'masterPassword', 'isNotMultiTenant',
42
+ const configKeys = ['baseUrl', 'emailFrom', 'env', 'name', 'mailgunDomain', 'mailgunKey', 'masterPassword', 'isNotMultiTenant',
41
43
  'confirmInvites']
42
44
  authConfig = pick(_config, configKeys)
43
45
  for (const key of Object.keys(helpers)) {
@@ -104,13 +106,13 @@ function setup(middleware, _config, helpers = {}) {
104
106
  }
105
107
 
106
108
  async function store(req, res) {
107
- res.json(await auth.getStore(req.user))
109
+ res.json(await auth.getStore(req.user, req))
108
110
  }
109
111
 
110
112
  async function signup(req, res) {
111
113
  try {
112
114
  const desktop = req.query.desktop
113
- const user = await auth.userCreate(req.body)
115
+ const user = await auth.userCreate(req.body, getBaseUrl(req))
114
116
  res.send(await auth.userSigninGetStore(user, desktop))
115
117
  } catch (err) {
116
118
  res.error(err)
@@ -125,6 +127,9 @@ function signin(req, res) {
125
127
  passport.authenticate('local', { session: false }, async (err, user, info) => {
126
128
  if (err) return res.error(err)
127
129
  if (!user && info) return res.error('email', info.message)
130
+ if (req.whitelabel && user.company?._id?.toString() !== req.whitelabel._id?.toString()) {
131
+ return res.error('email', `This sign in page is for ${req.whitelabel.name || req.whitelabel.subdomain} only.`)
132
+ }
128
133
  try {
129
134
  const response = await auth.userSigninGetStore(user, desktop)
130
135
  res.send(response)
@@ -169,12 +174,12 @@ export async function resetInstructions(req, res) {
169
174
  // const desktop = req.query.hasOwnProperty('desktop') ? '?desktop' : '' // see sendToken for future usage
170
175
  let email = (req.body.email || '').trim().toLowerCase()
171
176
  if (!email) throw { title: 'email', detail: 'The email you entered is incorrect.' }
172
-
177
+
173
178
  let user = await db.user.findOne({ query: { email }, _privateData: true })
174
179
  if (!user) throw { title: 'email', detail: 'The email you entered is incorrect.' }
175
180
 
176
181
  // Send reset password email
177
- await auth.tokenSend({ _id: user._id, email: user.email, firstName: user.firstName })
182
+ await auth.tokenSend({ _id: user._id, email: user.email, firstName: user.firstName, baseUrl: getBaseUrl(req) })
178
183
  res.json({})
179
184
  } catch (err) {
180
185
  res.error(err)
@@ -193,7 +198,7 @@ export async function inviteInstructions(req, res) {
193
198
  if (authConfig.isNotMultiTenant) {
194
199
  const user = await db.user.findOne({ query: { _id: req.body._id }, _privateData: true })
195
200
  if (!user) throw new Error('Please pre-create the user first, no user id found.')
196
- await auth.tokenSend({ type: 'invite', _id: user._id, email: user.email, firstName: user.firstName })
201
+ await auth.tokenSend({ type: 'invite', _id: user._id, email: user.email, firstName: user.firstName, baseUrl: getBaseUrl(req) })
197
202
  res.json({})
198
203
 
199
204
  } else {
@@ -212,6 +217,7 @@ export async function inviteInstructions(req, res) {
212
217
  type: 'companyInvite', _id: companyId,
213
218
  email: req.body.email,
214
219
  firstName: existingUser?.firstName || req.body.firstName,
220
+ baseUrl: getBaseUrl(req),
215
221
  })
216
222
  }
217
223
  res.json({})
@@ -310,8 +316,8 @@ export async function userSigninGetStore(user, isDesktop) {
310
316
  return { ...store, jwt }
311
317
  }
312
318
 
313
- export async function getStore(user) {
314
- // Initial store
319
+ export async function getStore(user, _req) {
320
+ // Initial store (req only passed from the /store route, not after signin)
315
321
  return {
316
322
  user: user || undefined,
317
323
  }
@@ -325,10 +331,11 @@ export async function getStore(user) {
325
331
  * @param {string} [userData.password] - optional
326
332
  * @param {string} [userData.password2] - optional, to confirm the password
327
333
  * @param {string} [userData.company] - if multi tenant and `user.company` is not an id, create a new company
334
+ * @param {string} [baseUrl] - baseUrl to use for the email
328
335
  * @param {boolean} [skipSendEmail=false] - whether to skip sending the welcome email
329
336
  * @returns {Promise<object>} - the created user
330
337
  */
331
- export async function userCreate({ password, password2, company, ...userDataProp }, skipSendEmail) {
338
+ export async function userCreate({ password, password2, company, ...userDataProp }, baseUrl, skipSendEmail) {
332
339
  try {
333
340
  const userId = db.id()
334
341
  const options = { blacklist: ['-_id'] }
@@ -371,7 +378,7 @@ export async function userCreate({ password, password2, company, ...userDataProp
371
378
  // Send welcome email
372
379
  if (!skipSendEmail) {
373
380
  sendEmail({
374
- config: authConfig,
381
+ config: { ...authConfig, baseUrl: baseUrl || authConfig.baseUrl },
375
382
  template: 'welcome',
376
383
  to: `${ucFirst(userData.firstName)}<${userData.email}>`,
377
384
  }).catch(console.error)
@@ -499,9 +506,10 @@ export async function tokenConfirmForMultiTenant(req) {
499
506
  * @param {string} options.firstName - recipient first name
500
507
  * @param {function} [options.beforeUpdate] - runs before updating the model with the token, return null to skip update
501
508
  * @param {function} [options.beforeSendEmail] - runs before sending the email, receives (options, token)
509
+ * @param {string} [options.baseUrl] - baseUrl to use for the email
502
510
  * @returns {Promise<{token: string, mailgunPromise: Promise<unknown>}>}
503
511
  */
504
- export async function tokenSend({ type = 'reset', _id, email, firstName, beforeUpdate, beforeSendEmail }) {
512
+ export async function tokenSend({ type = 'reset', _id, email, firstName, beforeUpdate, beforeSendEmail, baseUrl }) {
505
513
  if (!_id) throw new Error(`${type === 'companyInvite' ? 'company' : 'user'} id is required`)
506
514
  if (!email) throw new Error('email is required')
507
515
  if (!firstName) throw new Error('firstName is required')
@@ -527,7 +535,7 @@ export async function tokenSend({ type = 'reset', _id, email, firstName, beforeU
527
535
 
528
536
  // Send email
529
537
  const options = {
530
- config: authConfig,
538
+ config: { ...authConfig, baseUrl: baseUrl || authConfig.baseUrl },
531
539
  template: type === 'reset' ? 'reset-instructions' : 'invite-instructions',
532
540
  to: `${ucFirst(firstName)}<${email}>`,
533
541
  data: { token: token },
@@ -538,4 +546,29 @@ export async function tokenSend({ type = 'reset', _id, email, firstName, beforeU
538
546
 
539
547
  // Return the token and mailgun promise
540
548
  return { token, mailgunPromise }
549
+ }
550
+
551
+ function getBaseUrl(req) {
552
+ return resolveBaseUrl(req?.baseUrl, authConfig.baseUrl)
553
+ }
554
+
555
+ export function resolveBaseUrl(reqUrl, cfgUrl) {
556
+ // Use reqUrl if its apex domain matches cfgUrl's apex (e.g. wildcard.example.com matches cfg: app.example.com)
557
+ if (!reqUrl) return cfgUrl
558
+ try {
559
+ const reqHost = new URL(reqUrl).hostname
560
+ const cfgHost = new URL(cfgUrl).hostname
561
+ const reqApex = getDomain(reqHost)
562
+ const cfgApex = getDomain(cfgHost)
563
+
564
+ if (reqApex && cfgApex) {
565
+ return reqApex === cfgApex ? reqUrl : cfgUrl
566
+ } else if (reqHost === cfgHost || reqHost.endsWith('.' + cfgHost)) {
567
+ return reqUrl
568
+ } else {
569
+ return cfgUrl
570
+ }
571
+ } catch (_) {
572
+ return cfgUrl
573
+ }
541
574
  }
@@ -0,0 +1,40 @@
1
+ import { test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { resolveBaseUrl } from './auth.api.js'
4
+
5
+ const cases = [
6
+ // [cfgUrl, reqUrl, expected, description]
7
+
8
+ // cfg: match
9
+
10
+ ['https://app.example.com', 'https://app.example.com', 'https://app.example.com', 'match: exact'],
11
+ ['https://app.example.com', 'https://wildcard.example.com', 'https://wildcard.example.com', 'match: sibling'],
12
+ ['https://app.example.com', 'https://foo.bar.example.com', 'https://foo.bar.example.com', 'match: nested'],
13
+ ['https://app.example.com', 'https://example.com', 'https://example.com', 'match: apex'],
14
+ ['https://app.example.com', 'http://app.example.com:3000', 'http://app.example.com:3000', 'match: port (ignored)'],
15
+ ['https://example.co.uk', 'https://app.example.co.uk', 'https://app.example.co.uk', 'match: co.uk nested'],
16
+
17
+ ['http://localhost:3000', 'http://localhost:3001', 'http://localhost:3001', 'match: localhost: port (ignored)'],
18
+ ['http://localhost:3000', 'http://app.localhost:3001', 'http://app.localhost:3001', 'match: localhost: nested'],
19
+ ['http://127.0.0.1:3000', 'http://127.0.0.1:3000', 'http://127.0.0.1:3000', 'match: IP exact'],
20
+
21
+ // cfg: mismatch
22
+
23
+ ['https://app.example.com', 'https://example2.com', 'https://app.example.com', 'mismatch: apex'],
24
+ ['https://app.example.com', 'https://evilapp.example.com.bad', 'https://app.example.com', 'mismatch: nested'],
25
+ ['https://example.co.uk', 'https://other.co.uk', 'https://example.co.uk', 'mismatch: co.uk apex'],
26
+
27
+ ['http://otherhost', 'http://app.localhost', 'http://otherhost', 'mismatch: localhost: nested'],
28
+ ['http://127.0.0.1', 'http://192.168.1.1', 'http://127.0.0.1', 'mismatch: IP different'],
29
+ ['https://app.example.com', undefined, 'https://app.example.com', 'mismatch: undefined'],
30
+ ['https://app.example.com', '', 'https://app.example.com', 'mismatch: empty'],
31
+ ['https://app.example.com', 'not a url', 'https://app.example.com', 'mismatch: bad string'],
32
+ ]
33
+
34
+ test('resolveBaseUrl', async (t) => {
35
+ for (const [cfgUrl, reqUrl, expected, desc] of cases) {
36
+ await t.test(desc, () => {
37
+ assert.equal(resolveBaseUrl(reqUrl, cfgUrl), expected)
38
+ })
39
+ }
40
+ })
@@ -4,7 +4,7 @@ import { Fragment, useEffect } from 'react'
4
4
 
5
5
  type InviteConfirmProps = {
6
6
  className?: string,
7
- elements?: { Button?: typeof Button },
7
+ elements?: { Button?: typeof Button, Header?: React.ReactNode },
8
8
  redirectTo?: string,
9
9
  }
10
10
 
@@ -25,6 +25,7 @@ export function InviteConfirm({ className, elements, redirectTo }: InviteConfirm
25
25
 
26
26
  const Elements = {
27
27
  Button: elements?.Button || Button,
28
+ Header: elements?.Header || null,
28
29
  }
29
30
 
30
31
  // Auto-confirm on mount for already signed-in users
@@ -72,6 +73,7 @@ export function InviteConfirm({ className, elements, redirectTo }: InviteConfirm
72
73
 
73
74
  return (
74
75
  <div className={className}>
76
+ {!!Elements.Header && Elements.Header}
75
77
  <Topbar title={<Fragment>Accept Your Invite</Fragment>} />
76
78
 
77
79
  <form onSubmit={(e) => submit(state, e)} class="mb-0">
@@ -4,7 +4,7 @@ import { Fragment } from 'react'
4
4
 
5
5
  type resetInstructionsProps = {
6
6
  className?: string,
7
- elements?: { Button?: typeof Button },
7
+ elements?: { Button?: typeof Button, Header?: React.ReactNode },
8
8
  redirectTo?: string,
9
9
  }
10
10
 
@@ -16,6 +16,7 @@ export function ResetInstructions({ className, elements, redirectTo }: resetInst
16
16
 
17
17
  const Elements = {
18
18
  Button: elements?.Button || Button,
19
+ Header: elements?.Header || null,
19
20
  }
20
21
 
21
22
  async function onSubmit (event: React.FormEvent<HTMLFormElement>) {
@@ -31,6 +32,7 @@ export function ResetInstructions({ className, elements, redirectTo }: resetInst
31
32
 
32
33
  return (
33
34
  <div className={className}>
35
+ {!!Elements.Header && Elements.Header}
34
36
  <Topbar title={<Fragment>Reset your Password</Fragment>} />
35
37
 
36
38
  <form onSubmit={onSubmit} class="mb-0">
@@ -64,6 +66,7 @@ export function ResetPassword({ className, elements, redirectTo }: resetInstruct
64
66
 
65
67
  const Elements = {
66
68
  Button: elements?.Button || Button,
69
+ Header: elements?.Header || null,
67
70
  }
68
71
 
69
72
  async function onSubmit (event: React.FormEvent<HTMLFormElement>) {
@@ -79,6 +82,7 @@ export function ResetPassword({ className, elements, redirectTo }: resetInstruct
79
82
 
80
83
  return (
81
84
  <div className={className}>
85
+ {!!Elements.Header && Elements.Header}
82
86
  <Topbar title={<Fragment>Reset your Password</Fragment>} />
83
87
 
84
88
  <form onSubmit={onSubmit} class="mb-0">
@@ -4,7 +4,7 @@ import { Fragment } from 'react'
4
4
 
5
5
  type signinProps = {
6
6
  className?: string,
7
- elements?: { Button?: typeof Button },
7
+ elements?: { Button?: typeof Button, Header?: React.ReactNode },
8
8
  redirectTo?: string,
9
9
  }
10
10
 
@@ -22,6 +22,7 @@ export function Signin({ className, elements, redirectTo }: signinProps) {
22
22
 
23
23
  const Elements = {
24
24
  Button: elements?.Button || Button,
25
+ Header: elements?.Header || null,
25
26
  }
26
27
 
27
28
  useEffect(() => {
@@ -60,6 +61,7 @@ export function Signin({ className, elements, redirectTo }: signinProps) {
60
61
 
61
62
  return (
62
63
  <div className={className}>
64
+ {!!Elements.Header && Elements.Header}
63
65
  <Topbar title={<Fragment>Sign in to your Account</Fragment>} />
64
66
 
65
67
  <form onSubmit={onSubmit} class="mb-0">
@@ -4,7 +4,7 @@ import { Fragment } from 'react'
4
4
 
5
5
  type signupProps = {
6
6
  className?: string,
7
- elements?: { Button?: typeof Button },
7
+ elements?: { Button?: typeof Button, Header?: React.ReactNode },
8
8
  redirectTo?: string,
9
9
  }
10
10
 
@@ -22,6 +22,7 @@ export function Signup({ className, elements, redirectTo }: signupProps) {
22
22
 
23
23
  const Elements = {
24
24
  Button: elements?.Button || Button,
25
+ Header: elements?.Header || null,
25
26
  }
26
27
 
27
28
  async function onSubmit (e: React.FormEvent<HTMLFormElement>) {
@@ -37,6 +38,7 @@ export function Signup({ className, elements, redirectTo }: signupProps) {
37
38
 
38
39
  return (
39
40
  <div className={className}>
41
+ {!!Elements.Header && Elements.Header}
40
42
  <Topbar title={<Fragment>Start your 21 day Free Trial</Fragment>} />
41
43
 
42
44
  <form onSubmit={onSubmit} class="mb-0">
@@ -2,6 +2,7 @@
2
2
  import Stripe from 'stripe'
3
3
  import db from 'monastery'
4
4
  import * as util from 'nitro-web/util'
5
+ import { resolveBaseUrl } from '../auth/auth.api.js'
5
6
 
6
7
  let stripe = undefined
7
8
  let stripeProducts = []
@@ -81,7 +82,7 @@ async function billingPortalSessionCreate(req, res) {
81
82
  }
82
83
  const session = await stripe.billingPortal.sessions.create({
83
84
  customer: req.user.stripeCustomer.id,
84
- return_url: config.baseUrl + '/subscriptions',
85
+ return_url: resolveBaseUrl(req.baseUrl, config.baseUrl) + '/subscriptions',
85
86
  })
86
87
  res.json(session.url)
87
88
  } catch (err) {
@@ -1,6 +1,8 @@
1
- export function NotFound() {
1
+ import { twMerge } from 'nitro-web/util'
2
+
3
+ export function NotFound({ className }: { className?: string }) {
2
4
  return (
3
- <div style={{'minHeight': '300px'}}>
5
+ <div className={twMerge('min-h-[300px]', className)}>
4
6
  <span class="h1">Page Not Found</span><br />
5
7
  <br />
6
8
  The page you&apos;re looking for doesn&apos;t exist or has moved.<br />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitro-web",
3
- "version": "0.0.207",
3
+ "version": "0.1.2",
4
4
  "repository": "github:boycce/nitro-web",
5
5
  "homepage": "https://boycce.github.io/nitro-web/",
6
6
  "description": "Nitro is a battle-tested, modular base project to turbocharge your projects, styled using Tailwind 🚀",
@@ -31,6 +31,7 @@
31
31
  "minor": "npm run types && standard-version -a --release-as minor && npm publish && cd ../webpack && npm publish",
32
32
  "patch": "npm run types && standard-version -a --release-as patch && npm publish && cd ../webpack && npm publish",
33
33
  "republish": "npm publish && cd ../webpack && npm publish",
34
+ "test": "node --test components/**/*.test.js",
34
35
  "types": "tsc util.js ./server/index.js --declaration --declarationMap --allowJs --emitDeclarationOnly --jsx react-jsx --esModuleInterop --skipLibCheck --outDir types && cd ../webpack && npm run types -w . && cd ../core"
35
36
  },
36
37
  "dependencies": {
@@ -55,7 +56,8 @@
55
56
  "passport-local": "^1.0.0",
56
57
  "sort-route-addresses-nodeps": "0.0.4",
57
58
  "standard-version": "github:boycce/standard-version",
58
- "tailwind-merge": "^2.6.0"
59
+ "tailwind-merge": "^2.6.0",
60
+ "tldts": "^7.0.30"
59
61
  },
60
62
  "devDependencies": {
61
63
  "@typescript-eslint/eslint-plugin": "^8.18.1"
package/server/router.js CHANGED
@@ -34,6 +34,7 @@ import * as util from 'nitro-web/util'
34
34
  let configLocal
35
35
  const _dirname = dirname(fileURLToPath(import.meta.url)) + '/'
36
36
 
37
+ /** @returns {Promise<{ server: import('http').Server, expressApp: import('express').Application }>} */
37
38
  export async function setupRouter (config) {
38
39
  configLocal = config
39
40
  const { env, middleware: configMiddleware, version } = config
@@ -96,6 +97,7 @@ export async function setupRouter (config) {
96
97
  expressApp.use('/assets', compression()) // gzip
97
98
  expressApp.use('/assets', express.static(distDir + 'assets/', { maxage: '365d' }))
98
99
  } else if (!allMiddleware[name]) {
100
+ console.error(`SetupRouter: Server middleware '${name}' not found`)
99
101
  continue
100
102
  } else {
101
103
  expressApp.use(allMiddleware[name])
@@ -158,7 +160,7 @@ export async function setupRouter (config) {
158
160
  else { res.status(404); res.notFound() }
159
161
  })
160
162
 
161
- return server
163
+ return { server, expressApp }
162
164
  }
163
165
 
164
166
  function setupErrorResponses (expressApp) {
@@ -335,8 +337,13 @@ export const middleware = {
335
337
 
336
338
  modifyRequest: (req, res, next) => {
337
339
  // Handy boolean denoting that the request wants JSON returned
338
- // global.start = new Date().getTime()
339
340
  req.json = req.xhr || req.accepts(['html', 'json']) == 'json'
341
+ // Dynamic baseUrl from the request headers, may require app.set('trust proxy', 1) in the server setup
342
+ req.baseUrl = req.protocol + '://' + req.headers.host
343
+ // log the headers if the config.logHeaders is true
344
+ if (configLocal.logHeaders) {
345
+ console.info('Headers:', req.headers)
346
+ }
340
347
  next()
341
348
  },
342
349
 
@@ -7,7 +7,7 @@ export function userSigninGetStore(user: any, isDesktop: any): Promise<{
7
7
  jwt: string;
8
8
  user: any;
9
9
  }>;
10
- export function getStore(user: any): Promise<{
10
+ export function getStore(user: any, _req: any): Promise<{
11
11
  user: any;
12
12
  }>;
13
13
  /**
@@ -16,6 +16,7 @@ export function getStore(user: any): Promise<{
16
16
  * @param {string} [userData.password] - optional
17
17
  * @param {string} [userData.password2] - optional, to confirm the password
18
18
  * @param {string} [userData.company] - if multi tenant and `user.company` is not an id, create a new company
19
+ * @param {string} [baseUrl] - baseUrl to use for the email
19
20
  * @param {boolean} [skipSendEmail=false] - whether to skip sending the welcome email
20
21
  * @returns {Promise<object>} - the created user
21
22
  */
@@ -23,7 +24,7 @@ export function userCreate({ password, password2, company, ...userDataProp }: {
23
24
  password?: string;
24
25
  password2?: string;
25
26
  company?: string;
26
- }, skipSendEmail?: boolean): Promise<object>;
27
+ }, baseUrl?: string, skipSendEmail?: boolean): Promise<object>;
27
28
  export function passwordValidate(password: string, password2: any): Promise<void>;
28
29
  export function tokenCreate(modelName: any, id: any): Promise<any>;
29
30
  export function tokenParse(token: any, modelName: any, maxAgeMs?: number): any;
@@ -48,19 +49,22 @@ export function tokenConfirmForMultiTenant(req: any): Promise<{
48
49
  * @param {string} options.firstName - recipient first name
49
50
  * @param {function} [options.beforeUpdate] - runs before updating the model with the token, return null to skip update
50
51
  * @param {function} [options.beforeSendEmail] - runs before sending the email, receives (options, token)
52
+ * @param {string} [options.baseUrl] - baseUrl to use for the email
51
53
  * @returns {Promise<{token: string, mailgunPromise: Promise<unknown>}>}
52
54
  */
53
- export function tokenSend({ type, _id, email, firstName, beforeUpdate, beforeSendEmail }: {
55
+ export function tokenSend({ type, _id, email, firstName, beforeUpdate, beforeSendEmail, baseUrl }: {
54
56
  type: "reset" | "invite" | "companyInvite";
55
57
  _id: string;
56
58
  email: string;
57
59
  firstName: string;
58
60
  beforeUpdate?: Function;
59
61
  beforeSendEmail?: Function;
62
+ baseUrl?: string;
60
63
  }): Promise<{
61
64
  token: string;
62
65
  mailgunPromise: Promise<unknown>;
63
66
  }>;
67
+ export function resolveBaseUrl(reqUrl: any, cfgUrl: any): any;
64
68
  export namespace auth {
65
69
  export { userFindFromProvider };
66
70
  export { userSigninGetStore };
@@ -1 +1 @@
1
- {"version":3,"file":"auth.api.d.ts","sourceRoot":"","sources":["../../../components/auth/auth.api.js"],"names":[],"mappings":"AAsKA,qEAeC;AAED,sEAsCC;AAED,gEAMC;AAED,iEAgBC;AAID,qGAkDC;AAED;;;GAOC;AAED;;GAKC;AAID;;;;;;;;GAQG;AACH,8EANG;IAA0B,QAAQ,GAA1B,MAAM;IACY,SAAS,GAA3B,MAAM;IACY,OAAO,GAAzB,MAAM;CACd,kBAAQ,OAAO,GACL,OAAO,CAAC,MAAM,CAAC,CA0D3B;AAED,kFAiBC;AAED,mEAOC;AAED,+EAWC;AAED;;;GAEC;AAED;;;GAuBC;AAED;;;GAgCC;AAED;;;;;;;;;;GAUG;AACH,0FARG;IAAsD,IAAI,EAAlD,OAAO,GAAG,QAAQ,GAAG,eAAe;IACpB,GAAG,EAAnB,MAAM;IACU,KAAK,EAArB,MAAM;IACU,SAAS,EAAzB,MAAM;IACa,YAAY;IACZ,eAAe;CAC1C,GAAU,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAC,CAAC,CAuCtE;;;;;;;;;;;;;;AAxgBD;;;;;;;;;;;EAaC;AAwED,0DAEC;AA6BD,mDAEC;AAnBD,iDAeC;AAzBD,2DAQC;AAuBD,2DAwBC;AAjID,0EAoEC"}
1
+ {"version":3,"file":"auth.api.d.ts","sourceRoot":"","sources":["../../../components/auth/auth.api.js"],"names":[],"mappings":"AA2KA,qEAeC;AAED,sEAuCC;AAED,gEAMC;AAED,iEAgBC;AAID,qGAkDC;AAED;;;GAOC;AAED;;GAKC;AAID;;;;;;;;;GASG;AACH,8EAPG;IAA0B,QAAQ,GAA1B,MAAM;IACY,SAAS,GAA3B,MAAM;IACY,OAAO,GAAzB,MAAM;CACd,YAAQ,MAAM,kBACN,OAAO,GACL,OAAO,CAAC,MAAM,CAAC,CA0D3B;AAED,kFAiBC;AAED,mEAOC;AAED,+EAWC;AAED;;;GAEC;AAED;;;GAuBC;AAED;;;GAgCC;AAED;;;;;;;;;;;GAWG;AACH,mGATG;IAAsD,IAAI,EAAlD,OAAO,GAAG,QAAQ,GAAG,eAAe;IACpB,GAAG,EAAnB,MAAM;IACU,KAAK,EAArB,MAAM;IACU,SAAS,EAAzB,MAAM;IACa,YAAY;IACZ,eAAe;IACjB,OAAO,GAAxB,MAAM;CACd,GAAU,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;CAAC,CAAC,CAuCtE;AAMD,8DAmBC;;;;;;;;;;;;;;AAviBD;;;;;;;;;;;EAaC;AAwED,0DAEC;AAgCD,mDAEC;AAtBD,iDAkBC;AA5BD,2DAQC;AA0BD,2DAwBC;AApID,0EAoEC"}
@@ -1 +1 @@
1
- {"version":3,"file":"stripe.api.d.ts","sourceRoot":"","sources":["../../../components/billing/stripe.api.js"],"names":[],"mappings":"AAiHA,4CA8BC;AAtID;;;;;EAQC;AAkBD,iEAuCC;AAED,+EAaC;AAED,wEAWC;AAnFD,4DAcC"}
1
+ {"version":3,"file":"stripe.api.d.ts","sourceRoot":"","sources":["../../../components/billing/stripe.api.js"],"names":[],"mappings":"AAkHA,4CA8BC;AAtID;;;;;EAQC;AAkBD,iEAuCC;AAED,+EAaC;AAED,wEAWC;AAnFD,4DAcC"}
@@ -1,4 +1,8 @@
1
- export function setupRouter(config: any): Promise<http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>>;
1
+ /** @returns {Promise<{ server: import('http').Server, expressApp: import('express').Application }>} */
2
+ export function setupRouter(config: any): Promise<{
3
+ server: import("http").Server;
4
+ expressApp: import("express").Application;
5
+ }>;
2
6
  export function isAdminUser(req: any): boolean;
3
7
  export function isValidUserOrRespond(req: any, res: any): boolean;
4
8
  /** @type {MiddlewareConfig} */
@@ -18,6 +22,5 @@ export type MiddlewareConfig = {
18
22
  order: string[];
19
23
  [key: string]: ((req: Request, res: Response, next: Function) => void) | string[];
20
24
  };
21
- import http from 'http';
22
25
  import express from 'express';
23
26
  //# sourceMappingURL=router.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../server/router.js"],"names":[],"mappings":"AAoCA,wHA6HC;AAoPD,+CAEC;AAED,kEAYC;AApGD,+BAA+B;AAC/B,yBADW,gBAAgB,CAkF1B;sBAnYY,OAAO,CAAC,OAAO,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,GAAoB,CAAC;CAC7B;uBACS,OAAO,CAAC,QAAQ,GAAG;IAC3B,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACjE,YAAY,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,CAAC;IACvD,SAAS,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,CAAC;IACpD,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,CAAC;IACnD,WAAW,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,CAAC;CACvD;+BACS;IACR,KAAK,EAAE,MAAM,EAAE,CAAC;IACpB,CAAK,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,UAAU,KAAK,IAAI,CAAC,GAAG,MAAM,EAAE,CAAC;CACnF;iBA1Ba,MAAM;oBAIH,SAAS"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../server/router.js"],"names":[],"mappings":"AAoCA,uGAAuG;AACvG,0CADc,OAAO,CAAC;IAAE,MAAM,EAAE,OAAO,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,SAAS,EAAE,WAAW,CAAA;CAAE,CAAC,CA+HlG;AAyPD,+CAEC;AAED,kEAYC;AAzGD,+BAA+B;AAC/B,yBADW,gBAAgB,CAuF1B;sBA1YY,OAAO,CAAC,OAAO,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,GAAoB,CAAC;CAC7B;uBACS,OAAO,CAAC,QAAQ,GAAG;IAC3B,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACjE,YAAY,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,CAAC;IACvD,SAAS,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,CAAC;IACpD,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,CAAC;IACnD,WAAW,EAAE,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,EAAE,KAAK,IAAI,CAAC;CACvD;+BACS;IACR,KAAK,EAAE,MAAM,EAAE,CAAC;IACpB,CAAK,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,UAAU,KAAK,IAAI,CAAC,GAAG,MAAM,EAAE,CAAC;CACnF;oBAtBgB,SAAS"}
package/types.ts CHANGED
@@ -17,7 +17,7 @@ type InjectedConfig = {
17
17
 
18
18
  export type Config = InjectedConfig & {
19
19
  // Non-injectable config on the client
20
- beforeApp?: () => Promise<object>
20
+ beforeApp?: (config: Config) => Promise<unknown>
21
21
  beforeStoreUpdate?: (prevStore: Store | null, newData: Store) => Store
22
22
  middleware?: {[key: string]: (route: any, store: any) => undefined | { redirect: string }}
23
23
  }
package/util.js CHANGED
@@ -756,9 +756,9 @@ export function formData (obj, cfg, existingFormData, keyPrefix) {
756
756
  cfg.allowEmptyArrays = cfg.allowEmptyArrays === undefined ? false : cfg.allowEmptyArrays
757
757
  existingFormData = existingFormData || new FormData()
758
758
 
759
- const isBlob = typeof obj === 'object' &&
760
- 'size' in obj && typeof obj.size === 'number' &&
761
- 'type' in obj && typeof obj.type === 'string' &&
759
+ const isBlob = obj != null && typeof obj === 'object' &&
760
+ 'size' in obj && typeof obj.size === 'number' &&
761
+ 'type' in obj && typeof obj.type === 'string' &&
762
762
  'slice' in obj && typeof obj.slice === 'function'
763
763
 
764
764
  const isFile = isBlob &&