@teleporthq/teleport-shared 0.43.75 → 0.44.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/__tests__/page-arrival/page-export.test.ts +109 -0
- package/__tests__/page-arrival/runtime.test.ts +209 -0
- package/__tests__/utils/table-access-trusted-reader-roles.ts +144 -0
- package/dist/cjs/index.d.ts +2 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/page-arrival/constants.d.ts +14 -0
- package/dist/cjs/page-arrival/constants.d.ts.map +1 -0
- package/dist/cjs/page-arrival/constants.js +17 -0
- package/dist/cjs/page-arrival/constants.js.map +1 -0
- package/dist/cjs/page-arrival/index.d.ts +4 -0
- package/dist/cjs/page-arrival/index.d.ts.map +1 -0
- package/dist/cjs/page-arrival/index.js +20 -0
- package/dist/cjs/page-arrival/index.js.map +1 -0
- package/dist/cjs/page-arrival/page-export.d.ts +14 -0
- package/dist/cjs/page-arrival/page-export.d.ts.map +1 -0
- package/dist/cjs/page-arrival/page-export.js +88 -0
- package/dist/cjs/page-arrival/page-export.js.map +1 -0
- package/dist/cjs/page-arrival/runtime.d.ts +21 -0
- package/dist/cjs/page-arrival/runtime.d.ts.map +1 -0
- package/dist/cjs/page-arrival/runtime.js +26 -0
- package/dist/cjs/page-arrival/runtime.js.map +1 -0
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/cjs/utils/table-access.d.ts +17 -4
- package/dist/cjs/utils/table-access.d.ts.map +1 -1
- package/dist/cjs/utils/table-access.js +55 -14
- package/dist/cjs/utils/table-access.js.map +1 -1
- package/dist/esm/index.d.ts +2 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +2 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/page-arrival/constants.d.ts +14 -0
- package/dist/esm/page-arrival/constants.d.ts.map +1 -0
- package/dist/esm/page-arrival/constants.js +14 -0
- package/dist/esm/page-arrival/constants.js.map +1 -0
- package/dist/esm/page-arrival/index.d.ts +4 -0
- package/dist/esm/page-arrival/index.d.ts.map +1 -0
- package/dist/esm/page-arrival/index.js +4 -0
- package/dist/esm/page-arrival/index.js.map +1 -0
- package/dist/esm/page-arrival/page-export.d.ts +14 -0
- package/dist/esm/page-arrival/page-export.d.ts.map +1 -0
- package/dist/esm/page-arrival/page-export.js +61 -0
- package/dist/esm/page-arrival/page-export.js.map +1 -0
- package/dist/esm/page-arrival/runtime.d.ts +21 -0
- package/dist/esm/page-arrival/runtime.d.ts.map +1 -0
- package/dist/esm/page-arrival/runtime.js +22 -0
- package/dist/esm/page-arrival/runtime.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/utils/table-access.d.ts +17 -4
- package/dist/esm/utils/table-access.d.ts.map +1 -1
- package/dist/esm/utils/table-access.js +53 -13
- package/dist/esm/utils/table-access.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +2 -0
- package/src/page-arrival/constants.ts +15 -0
- package/src/page-arrival/index.ts +3 -0
- package/src/page-arrival/page-export.ts +58 -0
- package/src/page-arrival/runtime.ts +75 -0
- package/src/utils/table-access.ts +50 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teleporthq/teleport-shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.0",
|
|
4
4
|
"description": "A utility belt for the entire teleportHQ ecosystem",
|
|
5
5
|
"author": "teleportHQ",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@babel/types": "^7.5.5",
|
|
28
|
-
"@teleporthq/teleport-types": "^0.
|
|
28
|
+
"@teleporthq/teleport-types": "^0.44.0",
|
|
29
29
|
"jss": "^10.0.0",
|
|
30
30
|
"jss-preset-default": "^10.0.0"
|
|
31
31
|
},
|
|
32
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "3f8ab44ace8163b154799b7ea68ece45b3cf9022"
|
|
33
33
|
}
|
package/src/index.ts
CHANGED
|
@@ -31,6 +31,7 @@ import * as RichTextContent from './utils/rich-text-content'
|
|
|
31
31
|
import * as RichTextContentCodegen from './utils/rich-text-content-codegen'
|
|
32
32
|
import * as DataCache from './cache'
|
|
33
33
|
import * as PageRevalidate from './page-revalidate'
|
|
34
|
+
import * as PageArrival from './page-arrival'
|
|
34
35
|
import * as ScrollSceneRuntime from './scroll-scene'
|
|
35
36
|
import * as MotionRuntime from './motion-runtime'
|
|
36
37
|
import * as PageTransition from './page-transition'
|
|
@@ -72,6 +73,7 @@ export {
|
|
|
72
73
|
RichTextContentCodegen,
|
|
73
74
|
DataCache,
|
|
74
75
|
PageRevalidate,
|
|
76
|
+
PageArrival,
|
|
75
77
|
ScrollSceneRuntime,
|
|
76
78
|
MotionRuntime,
|
|
77
79
|
PageTransition,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the generated page-arrival wrapper lives and what pages import it as.
|
|
3
|
+
*
|
|
4
|
+
* Lives in `teleport-shared` because two page plugins in two packages ask for
|
|
5
|
+
* it — the workflows plugin (page-load and element-visible triggers) and the
|
|
6
|
+
* state data-source plugin (state seeded from a data source) — and they must
|
|
7
|
+
* emit the same file under the same name.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Folder of the emitted runtime, relative to the generated project root. */
|
|
11
|
+
export const PAGE_ARRIVAL_DIR = ['utils']
|
|
12
|
+
export const PAGE_ARRIVAL_FILE_NAME = 'page-arrival'
|
|
13
|
+
|
|
14
|
+
/** The default export of the runtime, as the page module imports it. */
|
|
15
|
+
export const PAGE_ARRIVAL_WRAPPER = 'remountOnPageArrival'
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as types from '@babel/types'
|
|
2
|
+
import { ComponentStructure, FileType } from '@teleporthq/teleport-types'
|
|
3
|
+
import { generatePageToRootPrefix } from '../utils/generic'
|
|
4
|
+
import { PAGE_ARRIVAL_DIR, PAGE_ARRIVAL_FILE_NAME, PAGE_ARRIVAL_WRAPPER } from './constants'
|
|
5
|
+
import { generatePageArrivalRuntime } from './runtime'
|
|
6
|
+
|
|
7
|
+
/** The chunk the React base-component plugin emits `export default <Page>` into. */
|
|
8
|
+
const EXPORT_CHUNK_NAME = 'export'
|
|
9
|
+
|
|
10
|
+
const isPageArrivalWrapper = (expression: types.Node): boolean =>
|
|
11
|
+
expression.type === 'CallExpression' &&
|
|
12
|
+
expression.callee.type === 'Identifier' &&
|
|
13
|
+
expression.callee.name === PAGE_ARRIVAL_WRAPPER
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Makes the page start fresh on every arrival — see `generatePageArrivalRuntime`
|
|
17
|
+
* for why Next.js does not do it on its own.
|
|
18
|
+
*
|
|
19
|
+
* Rewrites `export default Page` into `export default remountOnPageArrival(Page)`,
|
|
20
|
+
* imports the wrapper and emits it once per project. Several plugins may ask for
|
|
21
|
+
* the same page, so a page that is already wrapped is left alone. Nothing is
|
|
22
|
+
* changed when the page has no plain default export to wrap, or when the
|
|
23
|
+
* generation has nowhere to emit the runtime — an import of a file that is never
|
|
24
|
+
* written would break the build.
|
|
25
|
+
*/
|
|
26
|
+
export const remountPageOnArrival = (structure: ComponentStructure): void => {
|
|
27
|
+
const { chunks, dependencies, options, uidl } = structure
|
|
28
|
+
const exportChunk = chunks.find((chunk) => chunk.name === EXPORT_CHUNK_NAME)
|
|
29
|
+
const exportDeclaration = exportChunk?.content as types.Node | undefined
|
|
30
|
+
|
|
31
|
+
if (
|
|
32
|
+
!options.extractedResources ||
|
|
33
|
+
!exportDeclaration ||
|
|
34
|
+
exportDeclaration.type !== 'ExportDefaultDeclaration' ||
|
|
35
|
+
isPageArrivalWrapper(exportDeclaration.declaration) ||
|
|
36
|
+
exportDeclaration.declaration.type !== 'Identifier'
|
|
37
|
+
) {
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
exportDeclaration.declaration = types.callExpression(types.identifier(PAGE_ARRIVAL_WRAPPER), [
|
|
42
|
+
exportDeclaration.declaration,
|
|
43
|
+
])
|
|
44
|
+
|
|
45
|
+
const runtimePath = [...PAGE_ARRIVAL_DIR, PAGE_ARRIVAL_FILE_NAME].join('/')
|
|
46
|
+
const rootPrefix = generatePageToRootPrefix({
|
|
47
|
+
folderPath: uidl.outputOptions?.folderPath,
|
|
48
|
+
pagesPath: options.pagesPath,
|
|
49
|
+
})
|
|
50
|
+
dependencies[PAGE_ARRIVAL_WRAPPER] = { type: 'local', path: `${rootPrefix}${runtimePath}` }
|
|
51
|
+
|
|
52
|
+
options.extractedResources[runtimePath] = {
|
|
53
|
+
fileName: PAGE_ARRIVAL_FILE_NAME,
|
|
54
|
+
fileType: FileType.JS,
|
|
55
|
+
path: [...PAGE_ARRIVAL_DIR],
|
|
56
|
+
content: generatePageArrivalRuntime(),
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { PAGE_ARRIVAL_WRAPPER } from './constants'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `utils/page-arrival.js` — the wrapper a page's default export goes through
|
|
5
|
+
* when the page computes something on arrival.
|
|
6
|
+
*
|
|
7
|
+
* Next.js keeps a page mounted when a navigation lands on the page that is
|
|
8
|
+
* already on screen: a link to the current URL, another record of the same
|
|
9
|
+
* dynamic route, another query string, another language. The page receives
|
|
10
|
+
* the new props but keeps every `useState` seeded on its first arrival, and no
|
|
11
|
+
* mount effect runs again — so page-load and element-visible workflows stay
|
|
12
|
+
* silent and data-source states keep their old rows until a full reload.
|
|
13
|
+
*
|
|
14
|
+
* The wrapper gives the page a new key for each such arrival, which starts it
|
|
15
|
+
* exactly like a full load does. Only a navigation that STARTED while the page
|
|
16
|
+
* was mounted and ENDED on its own route counts: one that ends elsewhere
|
|
17
|
+
* unmounts the page anyway, and React 17 runs that unmount's effect cleanup
|
|
18
|
+
* after the router has already announced the completed navigation. Shallow
|
|
19
|
+
* navigations (state mirrored into the URL) and hash changes keep the page as
|
|
20
|
+
* it is — they are not arrivals.
|
|
21
|
+
*/
|
|
22
|
+
export const generatePageArrivalRuntime =
|
|
23
|
+
(): string => `import React, { useEffect, useState } from 'react'
|
|
24
|
+
import Router from 'next/router'
|
|
25
|
+
|
|
26
|
+
function isShallow(options) {
|
|
27
|
+
return !!(options && options.shallow)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function usePageArrivals() {
|
|
31
|
+
const [arrivals, setArrivals] = useState(0)
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const route = Router.pathname
|
|
35
|
+
let arriving = false
|
|
36
|
+
|
|
37
|
+
function handleStart(url, options) {
|
|
38
|
+
arriving = !isShallow(options)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function handleComplete(url, options) {
|
|
42
|
+
const arrived = arriving && !isShallow(options) && Router.pathname === route
|
|
43
|
+
arriving = false
|
|
44
|
+
if (arrived) {
|
|
45
|
+
setArrivals(function (count) {
|
|
46
|
+
return count + 1
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function handleError() {
|
|
52
|
+
arriving = false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
Router.events.on('routeChangeStart', handleStart)
|
|
56
|
+
Router.events.on('routeChangeComplete', handleComplete)
|
|
57
|
+
Router.events.on('routeChangeError', handleError)
|
|
58
|
+
return function () {
|
|
59
|
+
Router.events.off('routeChangeStart', handleStart)
|
|
60
|
+
Router.events.off('routeChangeComplete', handleComplete)
|
|
61
|
+
Router.events.off('routeChangeError', handleError)
|
|
62
|
+
}
|
|
63
|
+
}, [])
|
|
64
|
+
|
|
65
|
+
return arrivals
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export default function ${PAGE_ARRIVAL_WRAPPER}(Page) {
|
|
69
|
+
function PageArrival(props) {
|
|
70
|
+
return <Page key={usePageArrivals()} {...props} />
|
|
71
|
+
}
|
|
72
|
+
PageArrival.displayName = 'PageArrival(' + (Page.displayName || Page.name || 'Page') + ')'
|
|
73
|
+
return PageArrival
|
|
74
|
+
}
|
|
75
|
+
`
|
|
@@ -91,6 +91,19 @@ export const CUSTOMER_RECORD_TABLES: ReadonlyArray<string> = [
|
|
|
91
91
|
'teleport_ai_chat_documents',
|
|
92
92
|
'teleport_ai_chat_conversations',
|
|
93
93
|
'teleport_ai_chat_messages',
|
|
94
|
+
// A booking carries the customer's name, email and phone and the key of
|
|
95
|
+
// their booking link; the capacity counters decide who gets the last place;
|
|
96
|
+
// the business's settings row carries the address its notices go to. The
|
|
97
|
+
// booking pages read them only in server segments (public columns only),
|
|
98
|
+
// the admin with its session.
|
|
99
|
+
'teleport_bookings',
|
|
100
|
+
'teleport_booking_units',
|
|
101
|
+
'teleport_booking_settings',
|
|
102
|
+
// A calendar link holds the other platform's private calendar address and
|
|
103
|
+
// the key of this site's own feed; its blocks are that platform's bookings.
|
|
104
|
+
// Only the sync's server segments and the feed route read them.
|
|
105
|
+
'teleport_booking_calendars',
|
|
106
|
+
'teleport_booking_calendar_blocks',
|
|
94
107
|
]
|
|
95
108
|
|
|
96
109
|
export const PROTECTED_TABLES: ReadonlyArray<string> = [...MONEY_TABLES, ...CUSTOMER_RECORD_TABLES]
|
|
@@ -183,30 +196,55 @@ const ADMIN_FOLDER_NAME = 'admin-panel'
|
|
|
183
196
|
const ADMIN_ROUTE_PREFIX = '/admin'
|
|
184
197
|
|
|
185
198
|
/**
|
|
186
|
-
* The roles
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
199
|
+
* The roles that may open the generated admin panel as a whole: the roles its
|
|
200
|
+
* FOLDER admits, which the middleware and the workflow-route guard union into
|
|
201
|
+
* every page under it. A page's own roles never count — an admin page that also
|
|
202
|
+
* admits another role (a staff screen) must not hand that role the rest of the
|
|
203
|
+
* panel.
|
|
204
|
+
*
|
|
205
|
+
* A folder that records no roles (an older project, or one an owner edited)
|
|
206
|
+
* falls back to the roles EVERY protected admin page admits. Sorted; empty
|
|
207
|
+
* without an admin panel.
|
|
190
208
|
*/
|
|
191
|
-
export const
|
|
192
|
-
const roles = new Set<string>()
|
|
209
|
+
export const resolveAdminPanelRoles = (auth?: UIDLAuthentication | null): string[] => {
|
|
193
210
|
if (!auth) {
|
|
194
211
|
return []
|
|
195
212
|
}
|
|
213
|
+
const folderRoles = new Set<string>()
|
|
196
214
|
Object.values(auth.folderProtection || {}).forEach((folder) => {
|
|
197
215
|
if (folder && folder.folderName === ADMIN_FOLDER_NAME) {
|
|
198
|
-
;(folder.allowedRoles || []).forEach((role) =>
|
|
216
|
+
;(folder.allowedRoles || []).forEach((role) => folderRoles.add(role))
|
|
199
217
|
}
|
|
200
218
|
})
|
|
201
|
-
|
|
219
|
+
if (folderRoles.size > 0) {
|
|
220
|
+
return Array.from(folderRoles).sort()
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let everyPageAdmits: string[] | null = null
|
|
224
|
+
for (const page of Object.values(auth.pageProtection || {})) {
|
|
202
225
|
const route = page && typeof page.route === 'string' ? page.route : ''
|
|
203
|
-
if (route
|
|
204
|
-
|
|
226
|
+
if (route !== ADMIN_ROUTE_PREFIX && !route.startsWith(`${ADMIN_ROUTE_PREFIX}/`)) {
|
|
227
|
+
continue
|
|
205
228
|
}
|
|
206
|
-
|
|
207
|
-
|
|
229
|
+
const pageRoles = page.allowedRoles || []
|
|
230
|
+
everyPageAdmits =
|
|
231
|
+
everyPageAdmits === null
|
|
232
|
+
? Array.from(new Set(pageRoles))
|
|
233
|
+
: everyPageAdmits.filter((role) => pageRoles.indexOf(role) !== -1)
|
|
234
|
+
}
|
|
235
|
+
return (everyPageAdmits || []).sort()
|
|
208
236
|
}
|
|
209
237
|
|
|
238
|
+
/**
|
|
239
|
+
* The roles allowed to read a protected table from a browser: the admin
|
|
240
|
+
* panel's (`resolveAdminPanelRoles`) — so a role that opens only some admin
|
|
241
|
+
* pages reads no account, email or order. A project without an admin panel
|
|
242
|
+
* has no page that binds a money table, so the empty list (server-side
|
|
243
|
+
* callers only) is the right answer.
|
|
244
|
+
*/
|
|
245
|
+
export const resolveTrustedReaderRoles = (auth?: UIDLAuthentication | null): string[] =>
|
|
246
|
+
resolveAdminPanelRoles(auth)
|
|
247
|
+
|
|
210
248
|
/**
|
|
211
249
|
* ES5 source of the guard, inlined into a generated route. Declares:
|
|
212
250
|
* - `__taIsInternalRequest(req)` — the caller presented the app secret, or
|