@budibase/backend-core 2.11.43 → 2.11.44

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.
@@ -1,37 +1,50 @@
1
1
  import env from "../../environment"
2
2
  import * as objectStore from "../objectStore"
3
3
  import * as cloudfront from "../cloudfront"
4
+ import qs from "querystring"
5
+ import { DEFAULT_TENANT_ID, getTenantId } from "../../context"
6
+
7
+ export function clientLibraryPath(appId: string) {
8
+ return `${objectStore.sanitizeKey(appId)}/budibase-client.js`
9
+ }
4
10
 
5
11
  /**
6
- * In production the client library is stored in the object store, however in development
7
- * we use the symlinked version produced by lerna, located in node modules. We link to this
8
- * via a specific endpoint (under /api/assets/client).
9
- * @param appId In production we need the appId to look up the correct bucket, as the
10
- * version of the client lib may differ between apps.
11
- * @param version The version to retrieve.
12
- * @return The URL to be inserted into appPackage response or server rendered
13
- * app index file.
12
+ * Previously we used to serve the client library directly from Cloudfront, however
13
+ * due to issues with the domain we were unable to continue doing this - keeping
14
+ * incase we are able to switch back to CDN path again in future.
14
15
  */
15
- export const clientLibraryUrl = (appId: string, version: string) => {
16
- if (env.isProd()) {
17
- let file = `${objectStore.sanitizeKey(appId)}/budibase-client.js`
18
- if (env.CLOUDFRONT_CDN) {
19
- // append app version to bust the cache
20
- if (version) {
21
- file += `?v=${version}`
22
- }
23
- // don't need to use presigned for client with cloudfront
24
- // file is public
25
- return cloudfront.getUrl(file)
26
- } else {
27
- return objectStore.getPresignedUrl(env.APPS_BUCKET_NAME, file)
16
+ export function clientLibraryCDNUrl(appId: string, version: string) {
17
+ let file = clientLibraryPath(appId)
18
+ if (env.CLOUDFRONT_CDN) {
19
+ // append app version to bust the cache
20
+ if (version) {
21
+ file += `?v=${version}`
28
22
  }
23
+ // don't need to use presigned for client with cloudfront
24
+ // file is public
25
+ return cloudfront.getUrl(file)
29
26
  } else {
30
- return `/api/assets/client`
27
+ return objectStore.getPresignedUrl(env.APPS_BUCKET_NAME, file)
28
+ }
29
+ }
30
+
31
+ export function clientLibraryUrl(appId: string, version: string) {
32
+ let tenantId, qsParams: { appId: string; version: string; tenantId?: string }
33
+ try {
34
+ tenantId = getTenantId()
35
+ } finally {
36
+ qsParams = {
37
+ appId,
38
+ version,
39
+ }
40
+ }
41
+ if (tenantId && tenantId !== DEFAULT_TENANT_ID) {
42
+ qsParams.tenantId = tenantId
31
43
  }
44
+ return `/api/assets/client?${qs.encode(qsParams)}`
32
45
  }
33
46
 
34
- export const getAppFileUrl = (s3Key: string) => {
47
+ export function getAppFileUrl(s3Key: string) {
35
48
  if (env.CLOUDFRONT_CDN) {
36
49
  return cloudfront.getPresignedUrl(s3Key)
37
50
  } else {
@@ -6,7 +6,7 @@ import { Plugin } from "@budibase/types"
6
6
 
7
7
  // URLS
8
8
 
9
- export const enrichPluginURLs = (plugins: Plugin[]) => {
9
+ export function enrichPluginURLs(plugins: Plugin[]) {
10
10
  if (!plugins || !plugins.length) {
11
11
  return []
12
12
  }
@@ -17,12 +17,12 @@ export const enrichPluginURLs = (plugins: Plugin[]) => {
17
17
  })
18
18
  }
19
19
 
20
- const getPluginJSUrl = (plugin: Plugin) => {
20
+ function getPluginJSUrl(plugin: Plugin) {
21
21
  const s3Key = getPluginJSKey(plugin)
22
22
  return getPluginUrl(s3Key)
23
23
  }
24
24
 
25
- const getPluginIconUrl = (plugin: Plugin): string | undefined => {
25
+ function getPluginIconUrl(plugin: Plugin): string | undefined {
26
26
  const s3Key = getPluginIconKey(plugin)
27
27
  if (!s3Key) {
28
28
  return
@@ -30,7 +30,7 @@ const getPluginIconUrl = (plugin: Plugin): string | undefined => {
30
30
  return getPluginUrl(s3Key)
31
31
  }
32
32
 
33
- const getPluginUrl = (s3Key: string) => {
33
+ function getPluginUrl(s3Key: string) {
34
34
  if (env.CLOUDFRONT_CDN) {
35
35
  return cloudfront.getPresignedUrl(s3Key)
36
36
  } else {
@@ -40,11 +40,11 @@ const getPluginUrl = (s3Key: string) => {
40
40
 
41
41
  // S3 KEYS
42
42
 
43
- export const getPluginJSKey = (plugin: Plugin) => {
43
+ export function getPluginJSKey(plugin: Plugin) {
44
44
  return getPluginS3Key(plugin, "plugin.min.js")
45
45
  }
46
46
 
47
- export const getPluginIconKey = (plugin: Plugin) => {
47
+ export function getPluginIconKey(plugin: Plugin) {
48
48
  // stored iconUrl is deprecated - hardcode to icon.svg in this case
49
49
  const iconFileName = plugin.iconUrl ? "icon.svg" : plugin.iconFileName
50
50
  if (!iconFileName) {
@@ -53,12 +53,12 @@ export const getPluginIconKey = (plugin: Plugin) => {
53
53
  return getPluginS3Key(plugin, iconFileName)
54
54
  }
55
55
 
56
- const getPluginS3Key = (plugin: Plugin, fileName: string) => {
56
+ function getPluginS3Key(plugin: Plugin, fileName: string) {
57
57
  const s3Key = getPluginS3Dir(plugin.name)
58
58
  return `${s3Key}/${fileName}`
59
59
  }
60
60
 
61
- export const getPluginS3Dir = (pluginName: string) => {
61
+ export function getPluginS3Dir(pluginName: string) {
62
62
  let s3Key = `${pluginName}`
63
63
  if (env.MULTI_TENANCY) {
64
64
  const tenantId = context.getTenantId()
@@ -1,5 +1,4 @@
1
1
  import * as app from "../app"
2
- import { getAppFileUrl } from "../app"
3
2
  import { testEnv } from "../../../../tests/extra"
4
3
 
5
4
  describe("app", () => {
@@ -7,6 +6,15 @@ describe("app", () => {
7
6
  testEnv.nodeJest()
8
7
  })
9
8
 
9
+ function baseCheck(url: string, tenantId?: string) {
10
+ expect(url).toContain("/api/assets/client")
11
+ if (tenantId) {
12
+ expect(url).toContain(`tenantId=${tenantId}`)
13
+ }
14
+ expect(url).toContain("appId=app_123")
15
+ expect(url).toContain("version=2.0.0")
16
+ }
17
+
10
18
  describe("clientLibraryUrl", () => {
11
19
  function getClientUrl() {
12
20
  return app.clientLibraryUrl("app_123/budibase-client.js", "2.0.0")
@@ -20,31 +28,19 @@ describe("app", () => {
20
28
  it("gets url in dev", () => {
21
29
  testEnv.nodeDev()
22
30
  const url = getClientUrl()
23
- expect(url).toBe("/api/assets/client")
24
- })
25
-
26
- it("gets url with embedded minio", () => {
27
- testEnv.withMinio()
28
- const url = getClientUrl()
29
- expect(url).toBe(
30
- "/files/signed/prod-budi-app-assets/app_123/budibase-client.js/budibase-client.js"
31
- )
31
+ baseCheck(url)
32
32
  })
33
33
 
34
34
  it("gets url with custom S3", () => {
35
35
  testEnv.withS3()
36
36
  const url = getClientUrl()
37
- expect(url).toBe(
38
- "http://s3.example.com/prod-budi-app-assets/app_123/budibase-client.js/budibase-client.js"
39
- )
37
+ baseCheck(url)
40
38
  })
41
39
 
42
40
  it("gets url with cloudfront + s3", () => {
43
41
  testEnv.withCloudfront()
44
42
  const url = getClientUrl()
45
- expect(url).toBe(
46
- "http://cf.example.com/app_123/budibase-client.js/budibase-client.js?v=2.0.0"
47
- )
43
+ baseCheck(url)
48
44
  })
49
45
  })
50
46
 
@@ -57,7 +53,7 @@ describe("app", () => {
57
53
  testEnv.nodeDev()
58
54
  await testEnv.withTenant(tenantId => {
59
55
  const url = getClientUrl()
60
- expect(url).toBe("/api/assets/client")
56
+ baseCheck(url, tenantId)
61
57
  })
62
58
  })
63
59
 
@@ -65,9 +61,7 @@ describe("app", () => {
65
61
  await testEnv.withTenant(tenantId => {
66
62
  testEnv.withMinio()
67
63
  const url = getClientUrl()
68
- expect(url).toBe(
69
- "/files/signed/prod-budi-app-assets/app_123/budibase-client.js/budibase-client.js"
70
- )
64
+ baseCheck(url, tenantId)
71
65
  })
72
66
  })
73
67
 
@@ -75,9 +69,7 @@ describe("app", () => {
75
69
  await testEnv.withTenant(tenantId => {
76
70
  testEnv.withS3()
77
71
  const url = getClientUrl()
78
- expect(url).toBe(
79
- "http://s3.example.com/prod-budi-app-assets/app_123/budibase-client.js/budibase-client.js"
80
- )
72
+ baseCheck(url, tenantId)
81
73
  })
82
74
  })
83
75
 
@@ -85,9 +77,7 @@ describe("app", () => {
85
77
  await testEnv.withTenant(tenantId => {
86
78
  testEnv.withCloudfront()
87
79
  const url = getClientUrl()
88
- expect(url).toBe(
89
- "http://cf.example.com/app_123/budibase-client.js/budibase-client.js?v=2.0.0"
90
- )
80
+ baseCheck(url, tenantId)
91
81
  })
92
82
  })
93
83
  })
@@ -1,6 +1,6 @@
1
1
  const sanitize = require("sanitize-s3-objectkey")
2
2
  import AWS from "aws-sdk"
3
- import stream from "stream"
3
+ import stream, { Readable } from "stream"
4
4
  import fetch from "node-fetch"
5
5
  import tar from "tar-fs"
6
6
  import zlib from "zlib"
@@ -66,10 +66,10 @@ export function sanitizeBucket(input: string) {
66
66
  * @return an S3 object store object, check S3 Nodejs SDK for usage.
67
67
  * @constructor
68
68
  */
69
- export const ObjectStore = (
69
+ export function ObjectStore(
70
70
  bucket: string,
71
71
  opts: { presigning: boolean } = { presigning: false }
72
- ) => {
72
+ ) {
73
73
  const config: any = {
74
74
  s3ForcePathStyle: true,
75
75
  signatureVersion: "v4",
@@ -104,7 +104,7 @@ export const ObjectStore = (
104
104
  * Given an object store and a bucket name this will make sure the bucket exists,
105
105
  * if it does not exist then it will create it.
106
106
  */
107
- export const makeSureBucketExists = async (client: any, bucketName: string) => {
107
+ export async function makeSureBucketExists(client: any, bucketName: string) {
108
108
  bucketName = sanitizeBucket(bucketName)
109
109
  try {
110
110
  await client
@@ -139,13 +139,13 @@ export const makeSureBucketExists = async (client: any, bucketName: string) => {
139
139
  * Uploads the contents of a file given the required parameters, useful when
140
140
  * temp files in use (for example file uploaded as an attachment).
141
141
  */
142
- export const upload = async ({
142
+ export async function upload({
143
143
  bucket: bucketName,
144
144
  filename,
145
145
  path,
146
146
  type,
147
147
  metadata,
148
- }: UploadParams) => {
148
+ }: UploadParams) {
149
149
  const extension = filename.split(".").pop()
150
150
  const fileBytes = fs.readFileSync(path)
151
151
 
@@ -180,12 +180,12 @@ export const upload = async ({
180
180
  * Similar to the upload function but can be used to send a file stream
181
181
  * through to the object store.
182
182
  */
183
- export const streamUpload = async (
183
+ export async function streamUpload(
184
184
  bucketName: string,
185
185
  filename: string,
186
186
  stream: any,
187
187
  extra = {}
188
- ) => {
188
+ ) {
189
189
  const objectStore = ObjectStore(bucketName)
190
190
  await makeSureBucketExists(objectStore, bucketName)
191
191
 
@@ -215,7 +215,7 @@ export const streamUpload = async (
215
215
  * retrieves the contents of a file from the object store, if it is a known content type it
216
216
  * will be converted, otherwise it will be returned as a buffer stream.
217
217
  */
218
- export const retrieve = async (bucketName: string, filepath: string) => {
218
+ export async function retrieve(bucketName: string, filepath: string) {
219
219
  const objectStore = ObjectStore(bucketName)
220
220
  const params = {
221
221
  Bucket: sanitizeBucket(bucketName),
@@ -230,7 +230,7 @@ export const retrieve = async (bucketName: string, filepath: string) => {
230
230
  }
231
231
  }
232
232
 
233
- export const listAllObjects = async (bucketName: string, path: string) => {
233
+ export async function listAllObjects(bucketName: string, path: string) {
234
234
  const objectStore = ObjectStore(bucketName)
235
235
  const list = (params: ListParams = {}) => {
236
236
  return objectStore
@@ -261,11 +261,11 @@ export const listAllObjects = async (bucketName: string, path: string) => {
261
261
  /**
262
262
  * Generate a presigned url with a default TTL of 1 hour
263
263
  */
264
- export const getPresignedUrl = (
264
+ export function getPresignedUrl(
265
265
  bucketName: string,
266
266
  key: string,
267
267
  durationSeconds: number = 3600
268
- ) => {
268
+ ) {
269
269
  const objectStore = ObjectStore(bucketName, { presigning: true })
270
270
  const params = {
271
271
  Bucket: sanitizeBucket(bucketName),
@@ -291,7 +291,7 @@ export const getPresignedUrl = (
291
291
  /**
292
292
  * Same as retrieval function but puts to a temporary file.
293
293
  */
294
- export const retrieveToTmp = async (bucketName: string, filepath: string) => {
294
+ export async function retrieveToTmp(bucketName: string, filepath: string) {
295
295
  bucketName = sanitizeBucket(bucketName)
296
296
  filepath = sanitizeKey(filepath)
297
297
  const data = await retrieve(bucketName, filepath)
@@ -300,7 +300,7 @@ export const retrieveToTmp = async (bucketName: string, filepath: string) => {
300
300
  return outputPath
301
301
  }
302
302
 
303
- export const retrieveDirectory = async (bucketName: string, path: string) => {
303
+ export async function retrieveDirectory(bucketName: string, path: string) {
304
304
  let writePath = join(budibaseTempDir(), v4())
305
305
  fs.mkdirSync(writePath)
306
306
  const objects = await listAllObjects(bucketName, path)
@@ -324,7 +324,7 @@ export const retrieveDirectory = async (bucketName: string, path: string) => {
324
324
  /**
325
325
  * Delete a single file.
326
326
  */
327
- export const deleteFile = async (bucketName: string, filepath: string) => {
327
+ export async function deleteFile(bucketName: string, filepath: string) {
328
328
  const objectStore = ObjectStore(bucketName)
329
329
  await makeSureBucketExists(objectStore, bucketName)
330
330
  const params = {
@@ -334,7 +334,7 @@ export const deleteFile = async (bucketName: string, filepath: string) => {
334
334
  return objectStore.deleteObject(params).promise()
335
335
  }
336
336
 
337
- export const deleteFiles = async (bucketName: string, filepaths: string[]) => {
337
+ export async function deleteFiles(bucketName: string, filepaths: string[]) {
338
338
  const objectStore = ObjectStore(bucketName)
339
339
  await makeSureBucketExists(objectStore, bucketName)
340
340
  const params = {
@@ -349,10 +349,10 @@ export const deleteFiles = async (bucketName: string, filepaths: string[]) => {
349
349
  /**
350
350
  * Delete a path, including everything within.
351
351
  */
352
- export const deleteFolder = async (
352
+ export async function deleteFolder(
353
353
  bucketName: string,
354
354
  folder: string
355
- ): Promise<any> => {
355
+ ): Promise<any> {
356
356
  bucketName = sanitizeBucket(bucketName)
357
357
  folder = sanitizeKey(folder)
358
358
  const client = ObjectStore(bucketName)
@@ -383,11 +383,11 @@ export const deleteFolder = async (
383
383
  }
384
384
  }
385
385
 
386
- export const uploadDirectory = async (
386
+ export async function uploadDirectory(
387
387
  bucketName: string,
388
388
  localPath: string,
389
389
  bucketPath: string
390
- ) => {
390
+ ) {
391
391
  bucketName = sanitizeBucket(bucketName)
392
392
  let uploads = []
393
393
  const files = fs.readdirSync(localPath, { withFileTypes: true })
@@ -404,11 +404,11 @@ export const uploadDirectory = async (
404
404
  return files
405
405
  }
406
406
 
407
- export const downloadTarballDirect = async (
407
+ export async function downloadTarballDirect(
408
408
  url: string,
409
409
  path: string,
410
410
  headers = {}
411
- ) => {
411
+ ) {
412
412
  path = sanitizeKey(path)
413
413
  const response = await fetch(url, { headers })
414
414
  if (!response.ok) {
@@ -418,11 +418,11 @@ export const downloadTarballDirect = async (
418
418
  await streamPipeline(response.body, zlib.createUnzip(), tar.extract(path))
419
419
  }
420
420
 
421
- export const downloadTarball = async (
421
+ export async function downloadTarball(
422
422
  url: string,
423
423
  bucketName: string,
424
424
  path: string
425
- ) => {
425
+ ) {
426
426
  bucketName = sanitizeBucket(bucketName)
427
427
  path = sanitizeKey(path)
428
428
  const response = await fetch(url)
@@ -438,3 +438,17 @@ export const downloadTarball = async (
438
438
  // return the temporary path incase there is a use for it
439
439
  return tmpPath
440
440
  }
441
+
442
+ export async function getReadStream(
443
+ bucketName: string,
444
+ path: string
445
+ ): Promise<Readable> {
446
+ bucketName = sanitizeBucket(bucketName)
447
+ path = sanitizeKey(path)
448
+ const client = ObjectStore(bucketName)
449
+ const params = {
450
+ Bucket: bucketName,
451
+ Key: path,
452
+ }
453
+ return client.getObject(params).createReadStream()
454
+ }