knack-api-helper
Advanced tools
+270
-1
| (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.KnackAPI = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ | ||
| //Import custom fetch library | ||
| const _fetch = require('@callum.boase/fetch'); | ||
| //Use native browser FormData if in browser, or require('form-data') if in Nodejs | ||
| if(inBrowser()) { | ||
| FormData = window.FormData; | ||
| } else { | ||
| var FormData = require('form-data'); | ||
| } | ||
| //Helper function to check if we are in the browser | ||
| function inBrowser(){ | ||
| try { | ||
| window.fetch; | ||
| return true | ||
| } catch (err){ | ||
| return false; | ||
| } | ||
| } | ||
| //The main knack-api-helper | ||
| function KnackAPI(config) { | ||
@@ -313,2 +333,251 @@ | ||
| // Helper function to check that a Filestream (for file uploads) is readable | ||
| // This is a way to check if the file exists & has a size among other things | ||
| this.checkStreamReadable = async function(stream) { | ||
| if (!stream) { | ||
| throw new Error('No stream provided to check'); | ||
| } | ||
| // For browser File/Blob objects, just check size exists | ||
| if (inBrowser()) { | ||
| if (stream.size === undefined) { | ||
| throw new Error('Invalid or inaccessible stream: no size property found'); | ||
| } | ||
| return true; | ||
| } | ||
| // For Node.js ReadStream or other readable streams | ||
| if (stream.readable) { | ||
| try { | ||
| await new Promise((resolve, reject) => { | ||
| const cleanup = () => { | ||
| stream.removeListener('readable', onReadable); | ||
| stream.removeListener('error', onError); | ||
| }; | ||
| const onReadable = () => { | ||
| cleanup(); | ||
| // Reset stream position | ||
| stream.unshift(stream.read(1)); | ||
| resolve(true); | ||
| }; | ||
| const onError = (error) => { | ||
| cleanup(); | ||
| reject(new Error(`Invalid or inaccessible stream: ${error.message}`)); | ||
| }; | ||
| stream.once('readable', onReadable); | ||
| stream.once('error', onError); | ||
| }); | ||
| return true; | ||
| } catch (err) { | ||
| throw new Error(`Error accessing stream: ${err.message}`); | ||
| } | ||
| } | ||
| throw new Error('Provided stream is not readable'); | ||
| }; | ||
| // Helper function to check filestreams and prepare for upload to Knack | ||
| this.uploadFilePrep = async function(settings = {uploadType, fileStream, fileName, helperData, retries}) { | ||
| // Check that uplaodType is either 'file' or 'image' | ||
| if (!settings.uploadType || (settings.uploadType !== 'file' && settings.uploadType !== 'image')) { | ||
| throw new Error('you must specify the uploadType ("file" or "image") when running uploadFile or uploadFiles'); | ||
| } | ||
| // Validate the presence of fileStream | ||
| if (!settings.fileStream) { | ||
| throw new Error('uploadFile requires a fileStream to be provided'); | ||
| } | ||
| // Make sure the filestream is an object with a readable or blob-like structure | ||
| if (typeof settings.fileStream !== 'object' || (!settings.fileStream.readable && !settings.fileStream.size)) { | ||
| throw new Error('uploadFile requires fileStream to be a readable stream or a Blob/File-like object'); | ||
| } | ||
| // Verify stream is readable | ||
| await this.checkStreamReadable(settings.fileStream); | ||
| // Check if fileName is provided and is a string | ||
| if (!settings.fileName) { | ||
| throw new Error('uploadFile requires a fileName to be provided'); | ||
| } | ||
| if (typeof settings.fileName !== 'string') { | ||
| throw new Error('uploadFile requires fileName to be a string'); | ||
| } | ||
| // Check for the presence of a file extension | ||
| if (!settings.fileName.includes('.') || settings.fileName.split('.').pop() === settings.fileName) { | ||
| throw new Error('uploadFile requires fileName to include a file extension'); | ||
| } | ||
| // Prepare the form data for file upload | ||
| let formData = new FormData(); | ||
| formData.append("files", settings.fileStream, settings.fileName); | ||
| //Construct headers | ||
| const headers = { | ||
| 'X-Knack-REST-API-Key': 'knack', | ||
| 'X-Knack-Application-ID': config.applicationId, | ||
| } | ||
| //Manually add formData info to the headers if in nodejs | ||
| if(!inBrowser()) { | ||
| Object.assign(headers, formData.getHeaders()); | ||
| } | ||
| //Determine the url, based on whether we are uploading a file or image | ||
| let url; | ||
| if(settings.uploadType === 'image') { | ||
| url = `${this.urlBase}/applications/${config.applicationId}/assets/image/upload`; | ||
| } else { | ||
| url = `${this.urlBase}/applications/${config.applicationId}/assets/file/upload`; | ||
| } | ||
| // Construct the request object for _fetch | ||
| const req = { | ||
| url, | ||
| options: { | ||
| method: 'POST', | ||
| body: formData, | ||
| headers: headers, | ||
| }, | ||
| retries: this.getRetries(settings.retries), | ||
| helperData: settings.helperData | ||
| }; | ||
| return req; | ||
| }; | ||
| this.uploadFile = async function(settings = {uploadType, fileStream, fileName, helperData, retries}) { | ||
| const req = await this.uploadFilePrep(settings); | ||
| const response = await _fetch.one(req); | ||
| return { | ||
| response, | ||
| uploadedFileId: response.json?.id, | ||
| } | ||
| }; | ||
| this.uploadFiles = async function(settings = {filesToUpload: [{uploadType, fileStream, fileName}], helperData, retries, progressCbs}) { | ||
| const filesToUpload = settings.filesToUpload; | ||
| //Validate data | ||
| if (!filesToUpload) { | ||
| throw new Error('uploadFiles requires a value for filesToUpload (an array of objects {fileStream, fileName})'); | ||
| } | ||
| if (!Array.isArray(filesToUpload)) { | ||
| throw new Error('uploadFiles requires filesToUpload to be an array'); | ||
| } | ||
| if (filesToUpload.length === 0) { | ||
| throw new Error('uploadFiles requires filesToUpload to contain at least one item {fileStream, fileName}'); | ||
| } | ||
| //Build the requests to upload files | ||
| const requests = []; | ||
| for (const file of filesToUpload) { | ||
| const request = await this.uploadFilePrep({ | ||
| uploadType: file.uploadType, | ||
| fileStream: file.fileStream, | ||
| fileName: file.fileName, | ||
| helperData: settings.helperData, | ||
| retries: settings.retries | ||
| }); | ||
| requests.push(request); | ||
| } | ||
| const progressCbs = this.progressCbsSetup(settings); | ||
| // Run the requests | ||
| const results = await _fetch.many({requests, delayMs: 125, progressCbs}); | ||
| // Extract helpful information from the results | ||
| // We break from the standard many results format here to improve usability | ||
| // In a future release, other many methods will be updated to use this format | ||
| const summary = this.tools.manyResultsReport.calc(results); | ||
| const allSucceeded = summary.rejected === 0; | ||
| const uploadedFileIds = results.map(result => result.value?.json?.id); | ||
| return { | ||
| results, | ||
| summary, | ||
| allSucceeded, | ||
| uploadedFileIds | ||
| } | ||
| }; | ||
| // Helper to prep for uploadFileFromInput and uploadFilesFromInput | ||
| this.uploadFileFromInputPrep = function (fileInput) { | ||
| //This method is only valid in the browser | ||
| if (inBrowser() === false) { | ||
| throw new Error('uploadFileFromInput and uploadFilesFromInput methods are only valid if running knack-api-helper in the browser. Use uploadFile() instead.'); | ||
| } | ||
| // Validate the presence of fileInput | ||
| if (!fileInput) { | ||
| throw new Error('uploadFileFromInput requires a fileInput to be provided'); | ||
| } | ||
| // Check fileInput is an object with a files property | ||
| if (!fileInput.files) { | ||
| throw new Error('uploadFileFromInput requires fileInput to have a files property'); | ||
| } | ||
| // Validate there's at least one file and it has a size | ||
| if (fileInput.files.length === 0) { | ||
| throw new Error('uploadFileFromInput: no files found in fileInput. Could not continue'); | ||
| } | ||
| const files = fileInput.files; | ||
| // Check if each file has a size | ||
| for (let i = 0; i < files.length; i++) { | ||
| const file = files[i]; | ||
| // Check for file size | ||
| if (!file.size) { | ||
| throw new Error(`uploadFileFromInput: file (index ${i}) has no size. Could not continue`); | ||
| } | ||
| } | ||
| return files; | ||
| }; | ||
| // Modify existing uploadFileFromInput to use the new prep function | ||
| this.uploadFileFromInput = async function (settings = { uploadType, fileInput, helperData, retries }) { | ||
| const files = this.uploadFileFromInputPrep(settings.fileInput); | ||
| const file = files[0]; | ||
| // Create a FormData object and append the file | ||
| const formData = new FormData(); | ||
| formData.append('fileStream', file, file.name); | ||
| //Upload the file to Knack servers | ||
| return await this.uploadFile({ | ||
| uploadType: settings.uploadType, | ||
| fileStream: formData.get('fileStream'), | ||
| fileName: file.name, | ||
| helperData: settings.helperData, | ||
| retries: settings.retries | ||
| }); | ||
| }; | ||
| // Add new uploadFilesFromInput function | ||
| this.uploadFilesFromInput = async function (settings = { uploadType, fileInput, helperData, retries, progressCbs }) { | ||
| const files = this.uploadFileFromInputPrep(settings.fileInput); | ||
| const filesToUpload = Array.from(files).map(file => ({ | ||
| fileStream: file, | ||
| fileName: file.name, | ||
| uploadType: settings.uploadType | ||
| })); | ||
| return await this.uploadFiles({ | ||
| filesToUpload, | ||
| helperData: settings.helperData, | ||
| retries: settings.retries, | ||
| progressCbs: settings.progressCbs | ||
| }); | ||
| }; | ||
| this.tools = { | ||
@@ -459,3 +728,3 @@ progressBar: { | ||
| module.exports = KnackAPI; | ||
| },{"@callum.boase/fetch":2}],2:[function(require,module,exports){ | ||
| },{"@callum.boase/fetch":2,"form-data":3}],2:[function(require,module,exports){ | ||
| //Only load node-fetch in nodeJs environment | ||
@@ -462,0 +731,0 @@ //If we're running this file in browser, we'll use the browser's fetch API which is built-in |
+16
-7
| # Knack-api-helper CHANGELOG | ||
| ## 2022/04/26 Version 2.2.4 -> 2.2.5 -> 2.2.6 | ||
| ## 2024/12/17 Version 2.2.6 -> 2.3.0 | ||
| * Added new methods for file uploads | ||
| * `uploadFile()` | ||
| * `uploadFiles()` | ||
| * `uploadFileFromInput()` | ||
| * `uploadFilesFromInput()` | ||
| * All other methods are unchanged. No breaking changes. | ||
| * Fix incorrect dates in the changelog (all dates were listed as 2022 despite some changes beeing from 2023 and 2024) | ||
| ## 2024/04/26 Version 2.2.4 -> 2.2.5 -> 2.2.6 | ||
| * Updated mistake in readme for `putMany()` | ||
| * Skipped 2.2.5 due to unpublishing version 2.2.5 from NPM due to publishing mistake. | ||
| ## 2022/01/29 Version 2.2.3 -> 2.2.4 | ||
| ## 2024/01/29 Version 2.2.3 -> 2.2.4 | ||
| * Added new method getFromReportView() the enables getting data from report view (when using view-based auth) | ||
| * Refactored knack-api-helper.js to support this, but there are no functionality changes to existing methods (get, post, put, delete, getMany, postMany, putMany, deleteMany, login, remoteLogin, validateSession) | ||
| ## 2022/09/16 Version 2.2.2 -> 2.2.3 | ||
| ## 2023/09/16 Version 2.2.2 -> 2.2.3 | ||
| * Fixed browser build (browser loadable file). Otherwise unchanged | ||
| ## 2022/09/11 Version 2.2.1 -> 2.2.2 | ||
| ## 2023/09/11 Version 2.2.1 -> 2.2.2 | ||
| * Added MIT license file. | ||
| ## 2022/09/11 - Version 2.2.0 -> 2.2.1 | ||
| ## 2023/09/11 - Version 2.2.0 -> 2.2.1 | ||
| * Updated the readme to fix error in instructions for loading knack-api-helper into Knack javascript area. | ||
| ## 2022/08/29 - Version 2.1.5 -> 2.2.0 | ||
| ## 2023/08/29 - Version 2.1.5 -> 2.2.0 | ||
| * Added `validateSession()` method (see readme for details) | ||
| ## 2022/05/08 - Version 2.1.4 -> 2.1.5 | ||
| ## 2023/05/08 - Version 2.1.4 -> 2.1.5 | ||
| * Updated readme.md with more documentation and examples | ||
@@ -25,0 +34,0 @@ |
+269
-0
@@ -0,3 +1,23 @@ | ||
| //Import custom fetch library | ||
| const _fetch = require('@callum.boase/fetch'); | ||
| //Use native browser FormData if in browser, or require('form-data') if in Nodejs | ||
| if(inBrowser()) { | ||
| FormData = window.FormData; | ||
| } else { | ||
| var FormData = require('form-data'); | ||
| } | ||
| //Helper function to check if we are in the browser | ||
| function inBrowser(){ | ||
| try { | ||
| window.fetch; | ||
| return true | ||
| } catch (err){ | ||
| return false; | ||
| } | ||
| } | ||
| //The main knack-api-helper | ||
| function KnackAPI(config) { | ||
@@ -312,2 +332,251 @@ | ||
| // Helper function to check that a Filestream (for file uploads) is readable | ||
| // This is a way to check if the file exists & has a size among other things | ||
| this.checkStreamReadable = async function(stream) { | ||
| if (!stream) { | ||
| throw new Error('No stream provided to check'); | ||
| } | ||
| // For browser File/Blob objects, just check size exists | ||
| if (inBrowser()) { | ||
| if (stream.size === undefined) { | ||
| throw new Error('Invalid or inaccessible stream: no size property found'); | ||
| } | ||
| return true; | ||
| } | ||
| // For Node.js ReadStream or other readable streams | ||
| if (stream.readable) { | ||
| try { | ||
| await new Promise((resolve, reject) => { | ||
| const cleanup = () => { | ||
| stream.removeListener('readable', onReadable); | ||
| stream.removeListener('error', onError); | ||
| }; | ||
| const onReadable = () => { | ||
| cleanup(); | ||
| // Reset stream position | ||
| stream.unshift(stream.read(1)); | ||
| resolve(true); | ||
| }; | ||
| const onError = (error) => { | ||
| cleanup(); | ||
| reject(new Error(`Invalid or inaccessible stream: ${error.message}`)); | ||
| }; | ||
| stream.once('readable', onReadable); | ||
| stream.once('error', onError); | ||
| }); | ||
| return true; | ||
| } catch (err) { | ||
| throw new Error(`Error accessing stream: ${err.message}`); | ||
| } | ||
| } | ||
| throw new Error('Provided stream is not readable'); | ||
| }; | ||
| // Helper function to check filestreams and prepare for upload to Knack | ||
| this.uploadFilePrep = async function(settings = {uploadType, fileStream, fileName, helperData, retries}) { | ||
| // Check that uplaodType is either 'file' or 'image' | ||
| if (!settings.uploadType || (settings.uploadType !== 'file' && settings.uploadType !== 'image')) { | ||
| throw new Error('you must specify the uploadType ("file" or "image") when running uploadFile or uploadFiles'); | ||
| } | ||
| // Validate the presence of fileStream | ||
| if (!settings.fileStream) { | ||
| throw new Error('uploadFile requires a fileStream to be provided'); | ||
| } | ||
| // Make sure the filestream is an object with a readable or blob-like structure | ||
| if (typeof settings.fileStream !== 'object' || (!settings.fileStream.readable && !settings.fileStream.size)) { | ||
| throw new Error('uploadFile requires fileStream to be a readable stream or a Blob/File-like object'); | ||
| } | ||
| // Verify stream is readable | ||
| await this.checkStreamReadable(settings.fileStream); | ||
| // Check if fileName is provided and is a string | ||
| if (!settings.fileName) { | ||
| throw new Error('uploadFile requires a fileName to be provided'); | ||
| } | ||
| if (typeof settings.fileName !== 'string') { | ||
| throw new Error('uploadFile requires fileName to be a string'); | ||
| } | ||
| // Check for the presence of a file extension | ||
| if (!settings.fileName.includes('.') || settings.fileName.split('.').pop() === settings.fileName) { | ||
| throw new Error('uploadFile requires fileName to include a file extension'); | ||
| } | ||
| // Prepare the form data for file upload | ||
| let formData = new FormData(); | ||
| formData.append("files", settings.fileStream, settings.fileName); | ||
| //Construct headers | ||
| const headers = { | ||
| 'X-Knack-REST-API-Key': 'knack', | ||
| 'X-Knack-Application-ID': config.applicationId, | ||
| } | ||
| //Manually add formData info to the headers if in nodejs | ||
| if(!inBrowser()) { | ||
| Object.assign(headers, formData.getHeaders()); | ||
| } | ||
| //Determine the url, based on whether we are uploading a file or image | ||
| let url; | ||
| if(settings.uploadType === 'image') { | ||
| url = `${this.urlBase}/applications/${config.applicationId}/assets/image/upload`; | ||
| } else { | ||
| url = `${this.urlBase}/applications/${config.applicationId}/assets/file/upload`; | ||
| } | ||
| // Construct the request object for _fetch | ||
| const req = { | ||
| url, | ||
| options: { | ||
| method: 'POST', | ||
| body: formData, | ||
| headers: headers, | ||
| }, | ||
| retries: this.getRetries(settings.retries), | ||
| helperData: settings.helperData | ||
| }; | ||
| return req; | ||
| }; | ||
| this.uploadFile = async function(settings = {uploadType, fileStream, fileName, helperData, retries}) { | ||
| const req = await this.uploadFilePrep(settings); | ||
| const response = await _fetch.one(req); | ||
| return { | ||
| response, | ||
| uploadedFileId: response.json?.id, | ||
| } | ||
| }; | ||
| this.uploadFiles = async function(settings = {filesToUpload: [{uploadType, fileStream, fileName}], helperData, retries, progressCbs}) { | ||
| const filesToUpload = settings.filesToUpload; | ||
| //Validate data | ||
| if (!filesToUpload) { | ||
| throw new Error('uploadFiles requires a value for filesToUpload (an array of objects {fileStream, fileName})'); | ||
| } | ||
| if (!Array.isArray(filesToUpload)) { | ||
| throw new Error('uploadFiles requires filesToUpload to be an array'); | ||
| } | ||
| if (filesToUpload.length === 0) { | ||
| throw new Error('uploadFiles requires filesToUpload to contain at least one item {fileStream, fileName}'); | ||
| } | ||
| //Build the requests to upload files | ||
| const requests = []; | ||
| for (const file of filesToUpload) { | ||
| const request = await this.uploadFilePrep({ | ||
| uploadType: file.uploadType, | ||
| fileStream: file.fileStream, | ||
| fileName: file.fileName, | ||
| helperData: settings.helperData, | ||
| retries: settings.retries | ||
| }); | ||
| requests.push(request); | ||
| } | ||
| const progressCbs = this.progressCbsSetup(settings); | ||
| // Run the requests | ||
| const results = await _fetch.many({requests, delayMs: 125, progressCbs}); | ||
| // Extract helpful information from the results | ||
| // We break from the standard many results format here to improve usability | ||
| // In a future release, other many methods will be updated to use this format | ||
| const summary = this.tools.manyResultsReport.calc(results); | ||
| const allSucceeded = summary.rejected === 0; | ||
| const uploadedFileIds = results.map(result => result.value?.json?.id); | ||
| return { | ||
| results, | ||
| summary, | ||
| allSucceeded, | ||
| uploadedFileIds | ||
| } | ||
| }; | ||
| // Helper to prep for uploadFileFromInput and uploadFilesFromInput | ||
| this.uploadFileFromInputPrep = function (fileInput) { | ||
| //This method is only valid in the browser | ||
| if (inBrowser() === false) { | ||
| throw new Error('uploadFileFromInput and uploadFilesFromInput methods are only valid if running knack-api-helper in the browser. Use uploadFile() instead.'); | ||
| } | ||
| // Validate the presence of fileInput | ||
| if (!fileInput) { | ||
| throw new Error('uploadFileFromInput requires a fileInput to be provided'); | ||
| } | ||
| // Check fileInput is an object with a files property | ||
| if (!fileInput.files) { | ||
| throw new Error('uploadFileFromInput requires fileInput to have a files property'); | ||
| } | ||
| // Validate there's at least one file and it has a size | ||
| if (fileInput.files.length === 0) { | ||
| throw new Error('uploadFileFromInput: no files found in fileInput. Could not continue'); | ||
| } | ||
| const files = fileInput.files; | ||
| // Check if each file has a size | ||
| for (let i = 0; i < files.length; i++) { | ||
| const file = files[i]; | ||
| // Check for file size | ||
| if (!file.size) { | ||
| throw new Error(`uploadFileFromInput: file (index ${i}) has no size. Could not continue`); | ||
| } | ||
| } | ||
| return files; | ||
| }; | ||
| // Modify existing uploadFileFromInput to use the new prep function | ||
| this.uploadFileFromInput = async function (settings = { uploadType, fileInput, helperData, retries }) { | ||
| const files = this.uploadFileFromInputPrep(settings.fileInput); | ||
| const file = files[0]; | ||
| // Create a FormData object and append the file | ||
| const formData = new FormData(); | ||
| formData.append('fileStream', file, file.name); | ||
| //Upload the file to Knack servers | ||
| return await this.uploadFile({ | ||
| uploadType: settings.uploadType, | ||
| fileStream: formData.get('fileStream'), | ||
| fileName: file.name, | ||
| helperData: settings.helperData, | ||
| retries: settings.retries | ||
| }); | ||
| }; | ||
| // Add new uploadFilesFromInput function | ||
| this.uploadFilesFromInput = async function (settings = { uploadType, fileInput, helperData, retries, progressCbs }) { | ||
| const files = this.uploadFileFromInputPrep(settings.fileInput); | ||
| const filesToUpload = Array.from(files).map(file => ({ | ||
| fileStream: file, | ||
| fileName: file.name, | ||
| uploadType: settings.uploadType | ||
| })); | ||
| return await this.uploadFiles({ | ||
| filesToUpload, | ||
| helperData: settings.helperData, | ||
| retries: settings.retries, | ||
| progressCbs: settings.progressCbs | ||
| }); | ||
| }; | ||
| this.tools = { | ||
@@ -314,0 +583,0 @@ progressBar: { |
+4
-3
| { | ||
| "name": "knack-api-helper", | ||
| "version": "2.2.6", | ||
| "version": "2.3.0", | ||
| "description": "Methods for interacting with the Knack API using both view-based and object-based authentication", | ||
@@ -8,3 +8,3 @@ "main": "knack-api-helper.js", | ||
| "test": "echo \"Error: no test specified\" && exit 1", | ||
| "build": "browserify --ignore node-fetch --standalone KnackAPI knack-api-helper.js > browser.js" | ||
| "build": "browserify --ignore node-fetch --ignore form-data --standalone KnackAPI knack-api-helper.js > browser.js" | ||
| }, | ||
@@ -25,4 +25,5 @@ "repository": { | ||
| "dependencies": { | ||
| "@callum.boase/fetch": "^1.5.0" | ||
| "@callum.boase/fetch": "^1.5.0", | ||
| "form-data": "^4.0.0" | ||
| } | ||
| } |
+352
-0
@@ -792,1 +792,353 @@ # KnackAPI | ||
| ## File uploads | ||
| Uploading a file or image to Knack involves two steps: | ||
| 1. **Upload the file/image to Knack servers.** After uploading, the response will contain a file ID such as `675fafbf3cfc88031b154795`. | ||
| 2. **Create or update a Knack record**, using the ID of the file/image field as the value for a file or image field. | ||
| For more information on how Knack handle file uploads, refer to the [Knack File/Image Uploads Documentation](https://docs.knack.com/docs/fileimage-uploads). | ||
| Knack-api-helper requires this 2-step process but adds methods to make it easier. | ||
| The API calls to upload files to Knack servers have similar behaviour to the standard CRUD API calls listed above, including auto-retry when sensible. | ||
| There are 4 methods available for file uploads. These are: | ||
| * `uploadFile()` (usually for Node.js code, upload single file to Knack servers) | ||
| * `uploadFiles()` (usually for Node.js code, upload multiple files to Knack servers) | ||
| * `uploadFileFromInput()` (for client-side code only, upload single file from an HTML file input) | ||
| * `uploadFilesFromInput()` (for client-side code only, upload multiple files from an HTML file input) | ||
| All 4 methods share common parameters, as listed below: | ||
| ### Common parameters for file upload API calls | ||
| | Parameter | Type | Required? | Auth type applies to | Details | | ||
| | --- | --- | --- | --- | --- | | ||
| | helperData | object | No | Both | Any arbritrary object you want to include with the API call. This will get returned to you when you receive the response to the API call.<br><br> Useful for tracking data about the request that wouldn't ordinarily be easy to understand from the data received back from the API call. | | ||
| | retries | integar >= 0 | No | Both | The number of times to retry the API call/s on failure, if the error code indicates that a retry might fix the error ([see above](#auto-retry-and-delay-between-retries)). Defaults to 5 if no value is provided. | | ||
| ### uploadFile() | ||
| Upload a single file to Knack servers. This method is most helpful for usage in Node.js code or similar, where you have a file stored locally that you want to upload. However, it could be adapted for use in the browser environment too. | ||
| **Parameters:** | ||
| * The common parameters for file upload API calls plus: | ||
| | Parameter | Type | Required? | Auth type applies to | Details | | ||
| |----|----|----|----|----| | ||
| | uploadType | String | Yes | Both | Type of field you're uploading to, either `'file'` or `'image'`. | | ||
| | fileStream | Stream | Yes | Both | A readable stream of the file to upload. | | ||
| | fileName | String | Yes | Both | The name of the file to upload, including extension (eg `example.png`) | | ||
| #### uploadFile example (Node.js environment) | ||
| ```javascript | ||
| const KnackAPI = require('../knack-api-helper.js'); | ||
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| async function uploadFileTest() { | ||
| //Assuming there's a file stored in the same directory this is running called example.png | ||
| //Extract the necessary information from the file and create a file stream | ||
| const filePath = path.join(__dirname, 'example.png'); | ||
| const fileStream = fs.createReadStream(filePath); | ||
| const fileName = path.basename(filePath); | ||
| try { | ||
| //Initialise knack-api-helper | ||
| const knackAPI = new KnackAPI({ | ||
| auth: 'view-based', // You could also use object-based auth | ||
| applicationId: "YOUR-APPLICATION-ID", | ||
| //No need for userToken or login when uploading files. | ||
| }); | ||
| //Upload the file to Knack servers | ||
| const { uploadedFileId, response } = await knackAPI.uploadFile({ | ||
| uploadType: 'file',//Or 'image' | ||
| fileStream, | ||
| fileName | ||
| }); | ||
| console.log('Upload successful. Here is the file ID to save to your Knack record: ', uploadedFileId); | ||
| // Create a new record in Knack with the file attached | ||
| // This example uses a publicly accessible form, but you could also use a login protected one if you pass a userToken or run login first | ||
| // You could also use object-based auth if knackAPI was initialised with auth: 'object-based' | ||
| const newRecordResponse = await knackAPI.post({ | ||
| scene: 'scene_7', | ||
| view: 'view_6', | ||
| body: { | ||
| field_23: uploadedFileId, //Assuming field_23 is a file or image field in your Knack database | ||
| field_25: 'Hello', //Any other value(s) you want to fill | ||
| } | ||
| }) | ||
| console.log('Added new record', newRecordResponse); | ||
| } catch (error) { | ||
| console.error(error); | ||
| } | ||
| } | ||
| //Run the function defined above | ||
| uploadFileTest(); | ||
| ``` | ||
| ### uploadFiles() | ||
| Upload multiple files to Knack servers. As for `uploadFile`, this method is most helpful for usage in Node.js code or similar, where you have files stored locally that you want to upload. However, it could be adapted for use in the browser environment. | ||
| **Parameters:** | ||
| * The common parameters for file upload API calls plus: | ||
| | Parameter | Type | Required? | Auth type applies to | Details | | ||
| |----|----|----|----|----| | ||
| | filesToUpload | Array of objects | Yes | Both | An array of objects, 1 per file to upload, containing parameters `uploadType` (string: 'file' or 'image'), `fileStream` (a readable stream of the file to upload) and `fileName` (string eg `example.png`) | | ||
| #### uploadFiles() example (Node.js Environment) | ||
| ```javascript | ||
| const KnackAPI = require('../knack-api-helper.js'); | ||
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| async function uploadFilesTest() { | ||
| //Assuming there's are files stored in the same directory this is running called example..png, example2.png, example3.png | ||
| //Extract the necessary information from the file and create a file stream | ||
| const filePaths = [ | ||
| path.join(__dirname, 'example.png'), | ||
| path.join(__dirname, 'example2.png'), | ||
| path.join(__dirname, 'example3.png'), | ||
| ] | ||
| const filesToUpload = filePaths.map(filePath => { | ||
| return { | ||
| uploadType: 'file',//Or 'image' | ||
| fileStream: fs.createReadStream(filePath), | ||
| fileName: path.basename(filePath) | ||
| } | ||
| }); | ||
| try { | ||
| // Initialise knack-api-helper | ||
| const knackAPI = new KnackAPI({ | ||
| auth: 'view-based', | ||
| applicationId: "YOUR-APPLICATION-ID", | ||
| //No need for userToken or login when uploading files. | ||
| }); | ||
| //Upload the file to Knack servers | ||
| const { results, uploadedFileIds, allSucceeded, summary } = await knackAPI.uploadFiles({ | ||
| filesToUpload | ||
| }); | ||
| if (!allSucceeded) { | ||
| console.error('At least one file failed to upload. Here is a summary of the results:', summary); | ||
| console.log(results); | ||
| } else { | ||
| console.log('All files uploaded successfully. Here are the IDs of uploaded files to append to Knack records'); | ||
| console.log(uploadedFileIds); | ||
| } | ||
| // Create one record in Knack per uploaded file | ||
| // With the uploaded file attached to the record | ||
| // This example uses a publicly accessible scene and view, but you can also use a login-protected one if you run the login method first | ||
| // You could also use object-based auth if knackAPI was initialised with auth: 'object-based' | ||
| const records = uploadedFileIds.map(uploadedFileId => { | ||
| return { | ||
| field_23: uploadedFileId, //Assuming field_23 is a file or image field in your Knack database | ||
| field_25: 'Hello', //Any other value(s) you want to fill | ||
| } | ||
| }); | ||
| // Run the POST request to create the records in Knack | ||
| const newRecordResponses = await knackAPI.postMany({ | ||
| scene: 'scene_7', | ||
| view: 'view_6', | ||
| records | ||
| }) | ||
| // Check if all records were added successfully | ||
| if (newRecordResponses.summary.rejected > 0) { | ||
| console.error('Some records failed to post. Here is a summary of the results:', newRecordResponses.summary); | ||
| } else { | ||
| console.log('All records added successfully', newRecordResponses); | ||
| } | ||
| } catch (err) { | ||
| // Handle any other errors | ||
| // Errors from uploadFiles and postMany will NOT reach here | ||
| console.error(err); | ||
| } | ||
| } | ||
| uploadFilesTest(); | ||
| ``` | ||
| ### uploadFileFromInput() | ||
| Upload a single file from an HTML file input on a webpage to Knack servers. This method is ONLY for use in the browser environment eg in javascript code running on a webpage or a Knack page. | ||
| Since this method is for client-side (browser) code, we strongly recommend initialising knack-api-helper with view-based authentication to avoid exposing your API key to the public. | ||
| **Parameters:** | ||
| * The common parameters for file upload API calls plus: | ||
| | Parameter | Type | Required? | Auth type applies to | Details | | ||
| |----|----|----|----|----| | ||
| | uploadType | String | Yes | Both | Type of upload, either `'file'` or `'image'`.| | ||
| | fileInput | HTMLElement | Yes | Both | The HTML file input element containing the file. The first uploaded file (index 0) will be used so we recommend that the file input is configured to only allow selecting a single file. | | ||
| #### uploadFileFromInput() Example (Browser Environment) | ||
| The example below uses plain HTML and javascript to create a form with a file input and a submit button. When the form is submitted, the file is uploaded to Knack servers and a new record is created with the file attached. | ||
| This code could be adapted to use jquery instead of plain javascript, as is more conventional when writing code to use within a Knack app. | ||
| ```html | ||
| <!--HTML for a basic form with a file input (single select) and a submit button --> | ||
| <form id="fileUploadForm"> | ||
| <input type="file" id="fileInput" name="file"> | ||
| <button type="submit">Upload File</button> | ||
| </form> | ||
| <!-- Javascript to process form submit: upload file and create a new Knack record with it attached file --> | ||
| <script> | ||
| document.getElementById('fileUploadForm').addEventListener('submit', async function (e) { | ||
| e.preventDefault(); | ||
| try { | ||
| // Initialize the KnackAPI with your configuration | ||
| const knackAPI = new KnackAPI({ | ||
| auth: 'view-based', | ||
| applicationId: "YOUR-APPLICATION-ID", | ||
| // No need for userToken or login when uploading files. | ||
| }); | ||
| // Select the fileInput in the form | ||
| const myFileInput = document.getElementById('fileInput'); | ||
| // Run the API call to upload file to Knack server | ||
| const { response, uploadedFileId } = await knackAPI.uploadFileFromInput({ | ||
| uploadType: 'file', // Or 'image' | ||
| fileInput: myFileInput | ||
| }); | ||
| console.log('File uploaded successfully. Here is the ID to save to your Knack record:', uploadedFileId); | ||
| // Create a new record in Knack with the file attached | ||
| // This example uses a publicly accessible form, but you could also use a login protected one if you pass a userToken | ||
| // when initialising knackAPI or run the login method first | ||
| const newRecordResponse = await knackAPI.post({ | ||
| scene: 'scene_7', | ||
| view: 'view_6', | ||
| body: { | ||
| field_23: uploadedFileId, // Assuming field_23 is a file or image field in your Knack database | ||
| field_25: 'Hello', // Any other value(s) you want to fill | ||
| } | ||
| }); | ||
| console.log('Added new record', newRecordResponse); | ||
| } catch (error) { | ||
| console.error('File upload or record creation failed:', error); | ||
| } | ||
| }); | ||
| </script> | ||
| ``` | ||
| ### uploadFilesFromInput() | ||
| Upload multiple files from an HTML file input to Knack servers. As for `uploadFileFromInput`, this method is ONLY for use in the browser environment eg in javascript code running on a webpage or a Knack page. | ||
| Since this method is for client-side (browser) code, we strongly recommend initialising knack-api-helper with view-based authentication to avoid exposing your API key to the public. | ||
| **Parameters:** | ||
| * The common parameters for file upload API calls plus: | ||
| | Parameter | Type | Required? | Auth type applies to | Details | | ||
| |----|----|----|----|----| | ||
| | uploadType | String | Yes | Both | Type of upload, either `'file'` or `'image'`.| | ||
| | fileInput | HTMLElement | Yes | Both | The HTML file input element containing the file/s to upload. All files from the file input will be uploaded so you can configure it to accept multiple files. | | ||
| **Important note:** different to `uploadFiles()`, it's not possible in `uploadFilesFromInput()` to specify a different `uploadType` per file. Instead, we specify one `uploadType` for all files found in the `fileInput`. | ||
| #### uploadFilesFromInput() Example (Browser Environment) | ||
| ```html | ||
| <!-- A basic form with a file input that allows multiple selection, and a submit button --> | ||
| <form id="fileUploadForm"> | ||
| <input type="file" id="fileInput" name="file" multiple> | ||
| <button type="submit">Upload Multiple Files</button> | ||
| </form> | ||
| <!-- Javascript to process form submit: upload files and create new Knack records for each file --> | ||
| <script> | ||
| document.getElementById('fileUploadForm').addEventListener('submit', async function (e) { | ||
| e.preventDefault(); | ||
| try { | ||
| // Initialize the KnackAPI with your configuration | ||
| const knackAPI = new KnackAPI({ | ||
| auth: 'view-based', | ||
| applicationId: "YOUR-APPLICATION-ID", | ||
| // No need for userToken or login when uploading files. | ||
| }); | ||
| // Select the fileInput in the form | ||
| const myFileInput = document.getElementById('fileInput'); | ||
| // Run the API call to upload files to Knack servers | ||
| const { response, uploadedFileIds, allSucceeded, summary } = await knackAPI.uploadFilesFromInput({ | ||
| uploadType: 'file', // Or 'image' | ||
| fileInput: myFileInput | ||
| }); | ||
| // Check if all files were uploaded successfully | ||
| if (allSucceeded) { | ||
| console.log('All files uploaded successfully. Here are the IDs to save to your Knack records:', uploadedFileIds); | ||
| } else { | ||
| console.error('Some files failed to upload. Here is a summary of the results:', summary); | ||
| } | ||
| // Create one record in Knack per uploaded file | ||
| // This example uses a publicly accessible form, but you could also use a login protected one if you pass a userToken | ||
| // when initialising knackAPI or run the login method first | ||
| const records = uploadedFileIds.map(uploadedFileId => { | ||
| return { | ||
| field_23: uploadedFileId, // Assuming field_23 is a file or image field in your Knack database | ||
| field_25: 'Hello', // Any other value(s) you want to fill | ||
| }; | ||
| }); | ||
| const newRecordResponses = await knackAPI.postMany({ | ||
| scene: 'scene_7', | ||
| view: 'view_6', | ||
| records | ||
| }); | ||
| // Check if all records were added successfully | ||
| if (newRecordResponses.summary.rejected > 0) { | ||
| console.error('Some records failed to post. Here is a summary of the results:', newRecordResponses.summary); | ||
| } else { | ||
| console.log('All records added successfully', newRecordResponses); | ||
| } | ||
| } catch (err) { | ||
| console.error('Something went wrong', err); | ||
| } | ||
| }); | ||
| </script> | ||
| ``` |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
116643
47%1297
53.13%1143
44.32%2
100%23
21.05%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added