feat: create upload functions
This commit is contained in:
parent
e01b22eda1
commit
ac34be1528
|
@ -29,8 +29,9 @@
|
|||
"settings": {},
|
||||
"rules": {
|
||||
"@typescript-eslint/explicit-function-return-type": 0,
|
||||
"@typescript-eslint/explicit-module-boundary-types" : 0,
|
||||
"@typescript-eslint/no-explicit-any": 0,
|
||||
"@typescript-eslint/camelcase": 0,
|
||||
"camelcase": 0,
|
||||
"@typescript-eslint/no-empty-function": 0,
|
||||
"no-unused-expressions": 0,
|
||||
"indent": [
|
||||
|
|
13
package.json
13
package.json
|
@ -4,16 +4,21 @@
|
|||
"main": "index.js",
|
||||
"author": "Andrey Ponomarenko",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"r": "yarn webpack && node dist/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": "^3.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"promise.allsettled": "^1.0.2",
|
||||
"ts-node": "^8.10.2",
|
||||
"typescript": "^3.9.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"husky": "^4.3.0",
|
||||
"@commitlint/cli": "^9.1.2",
|
||||
"@commitlint/config-conventional": "^9.1.2",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/node-fetch": "^2.5.7",
|
||||
"@types/promise.allsettled": "^1.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^3.7.1",
|
||||
"@typescript-eslint/parser": "^3.7.1",
|
||||
|
@ -22,7 +27,11 @@
|
|||
"eslint-plugin-import": "^2.22.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.3.1",
|
||||
"eslint-plugin-react": "^7.20.4",
|
||||
"ts-loader": "^8.0.2"
|
||||
"husky": "^4.3.0",
|
||||
"ts-loader": "^8.0.2",
|
||||
"webpack": "^4.44.1",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-node-externals": "^2.5.0"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
export const API_UPLOAD = 'https://upload.diawi.com';
|
||||
export const API_STATUS = 'https://upload.diawi.com/status';
|
||||
export const MAX_API_STATUS_CALLS = 30;
|
||||
|
||||
export enum JOB_STATUS {
|
||||
OK = 2000,
|
||||
PROCEEDING = 2001,
|
||||
ERROR = 4000,
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import { API_STATUS } from '@app/constants';
|
||||
import { ApiStatusParams, ApiStatusResponse } from '@app/types';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
export const fetchJobStatus = async ({ token, job }: ApiStatusParams): Promise<ApiStatusResponse> => {
|
||||
const url = `${API_STATUS}/?token=${token}&job=${job}`;
|
||||
const res = await fetch(url);
|
||||
|
||||
return res.json();
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
export { fetchJobStatus } from './fetchJobStatus';
|
||||
export { rawUpload } from './rawUpload';
|
||||
export { request } from './request';
|
||||
export { upload } from './upload';
|
|
@ -0,0 +1,41 @@
|
|||
import { AnyFunction, ApiUploadProps, ApiUploadResponse, UploadOptions } from '@app/types';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { createFormData, noop } from '@app/utils';
|
||||
import { API_UPLOAD } from '@app/constants';
|
||||
import { request } from '@app/core';
|
||||
|
||||
// upload file, without checking the status
|
||||
export const rawUpload = async (params: UploadOptions): Promise<ApiUploadResponse> => {
|
||||
const {
|
||||
file,
|
||||
onUploadProgress = noop,
|
||||
...restParams
|
||||
} = params;
|
||||
|
||||
const filePath = path.resolve(file);
|
||||
|
||||
// check file existence
|
||||
if (!fs.existsSync(filePath)) throw new Error(`file ${filePath} not found`);
|
||||
|
||||
const fileStream = fs.createReadStream(filePath);
|
||||
const form = createFormData({ file: fileStream, ...restParams });
|
||||
|
||||
// displays file upload progress
|
||||
const fileSize = fs.lstatSync(filePath).size;
|
||||
let bytesWritten = 0;
|
||||
fileStream.on('data', (chunk) => {
|
||||
bytesWritten += chunk.length;
|
||||
const progressPercent = ((bytesWritten / fileSize) * 100).toFixed(2);
|
||||
onUploadProgress({ progressPercent, bytesWritten, fileSize });
|
||||
});
|
||||
|
||||
const data = await request({
|
||||
method: 'post',
|
||||
host: new URL(API_UPLOAD).host,
|
||||
headers: form.getHeaders(),
|
||||
formData: form,
|
||||
});
|
||||
|
||||
return JSON.parse(data);
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
import * as https from 'https';
|
||||
import { RequestOptions } from 'https';
|
||||
import * as FormData from 'form-data';
|
||||
|
||||
interface Params extends RequestOptions {
|
||||
formData: FormData
|
||||
}
|
||||
|
||||
export const request = async (props: Params): Promise<any> => {
|
||||
const { formData, ...requestOptions } = props;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = https.request(requestOptions, (request) => {
|
||||
request.on('data', (data) => {
|
||||
resolve(data);
|
||||
});
|
||||
|
||||
request.on('error', reject);
|
||||
});
|
||||
|
||||
request.on('response', (res) => {
|
||||
if (res.statusCode !== 200) {
|
||||
reject(new Error(`API Response error: Status: ${res.statusCode}; Message: ${res.statusMessage} `));
|
||||
}
|
||||
});
|
||||
|
||||
formData.pipe(request);
|
||||
});
|
||||
};
|
|
@ -0,0 +1,46 @@
|
|||
import { ApiStatusResponse, UploadOptions } from '@app/types';
|
||||
import { rawUpload, fetchJobStatus } from '@app/core';
|
||||
import { JOB_STATUS, MAX_API_STATUS_CALLS } from '@app/constants';
|
||||
|
||||
interface Options {
|
||||
maxApiStatusCalls?: number;
|
||||
}
|
||||
|
||||
// upload file, and wait for proceeding
|
||||
export const upload = async (props: UploadOptions, options: Options = {}) => {
|
||||
const { token } = props;
|
||||
const {
|
||||
maxApiStatusCalls = MAX_API_STATUS_CALLS,
|
||||
} = options;
|
||||
|
||||
const { job } = await rawUpload(props);
|
||||
let statusCallsCount = 0;
|
||||
|
||||
const checkStatus = async (): Promise<ApiStatusResponse> => {
|
||||
const jobStatus = await fetchJobStatus({ token, job });
|
||||
const { status, message } = jobStatus;
|
||||
|
||||
if (statusCallsCount > maxApiStatusCalls) {
|
||||
statusCallsCount += 1;
|
||||
throw new Error('max status api calls exceeded');
|
||||
}
|
||||
|
||||
console.log('status', status);
|
||||
|
||||
switch (status) {
|
||||
case JOB_STATUS.ERROR: {
|
||||
throw new Error(message);
|
||||
}
|
||||
case JOB_STATUS.PROCEEDING: {
|
||||
console.log('status PROCEEDING', status);
|
||||
return checkStatus();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return jobStatus;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return checkStatus();
|
||||
};
|
13
src/index.ts
13
src/index.ts
|
@ -1,9 +1,8 @@
|
|||
export interface A {
|
||||
a: 1,
|
||||
}
|
||||
// import { upload } from '@app/core';
|
||||
|
||||
export const z = ():A => ({
|
||||
a: 1,
|
||||
});
|
||||
(async () => {
|
||||
// const res = await upload({ token, file });
|
||||
|
||||
console.log(123);
|
||||
console.dir('============== COMPLETE ==============');
|
||||
// console.dir(res);
|
||||
})();
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/* eslint-disable no-multi-spaces */
|
||||
|
||||
export interface ApiUploadProps {
|
||||
token: string; // your API access token
|
||||
file: string; // path to file
|
||||
find_by_udid?: boolean; // allow your testers to find the app on Diawi's mobile web app using their UDID (iOS only)
|
||||
wall_of_apps?: boolean; // allow Diawi to display the app's icon on the wall of apps
|
||||
password?: string; // protect your app with a password; it will be required to access the installation page
|
||||
comment?: string; // additional information to your users on this build; the comment will be displayed on the installation page
|
||||
callback_url?: string; // the URL Diawi should call with the result
|
||||
callback_emails?: string; // the email addresses Diawi will send the result to (up to 5 separated by commas for starter/premium/enterprise accounts, 1 for free accounts)
|
||||
installation_notifications?: boolean; // receive notifications each time someone installs the app (only starter/premium/enterprise accounts)
|
||||
}
|
||||
|
||||
export interface UploadOptions extends ApiUploadProps {
|
||||
onUploadProgress?: AnyFunction;
|
||||
}
|
||||
|
||||
export interface ApiUploadResponse {
|
||||
job: string;
|
||||
}
|
||||
|
||||
export interface ApiStatusParams {
|
||||
token: string; // your API access token
|
||||
job: string; // the upload job hash from the upload response
|
||||
}
|
||||
|
||||
export interface ApiStatusResponse {
|
||||
status: number;
|
||||
message: string;
|
||||
hash?: string;
|
||||
link?: string;
|
||||
qrcode?: string,
|
||||
links?: string[],
|
||||
qrcodes?: string[]
|
||||
}
|
||||
|
||||
export type AnyFunction = (...args: any) => any;
|
||||
export type AnyObject = Record<any, any>;
|
|
@ -0,0 +1,14 @@
|
|||
import * as FormData from 'form-data';
|
||||
|
||||
// async await timer
|
||||
export const sleep = async (ms: number): Promise<void> => (
|
||||
new Promise((resolve) => setTimeout(resolve, ms))
|
||||
);
|
||||
|
||||
export const createFormData = (data: Record<string, any>): FormData => {
|
||||
const formData = new FormData();
|
||||
Object.keys(data).forEach((key) => formData.append(key, data[key]));
|
||||
return formData;
|
||||
};
|
||||
|
||||
export const noop = () => {};
|
|
@ -0,0 +1,34 @@
|
|||
const path = require('path');
|
||||
const nodeExternals = require('webpack-node-externals');
|
||||
const webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
core: './src/core/index.ts',
|
||||
},
|
||||
output: {
|
||||
filename: '[name]/index.js',
|
||||
path: `${__dirname}/dist`,
|
||||
},
|
||||
mode: 'development',
|
||||
externals: [nodeExternals()],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
],
|
||||
},
|
||||
target: 'node',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@app': path.resolve(__dirname, './src'),
|
||||
},
|
||||
extensions: ['.ts', '.js'],
|
||||
},
|
||||
plugins: [
|
||||
new webpack.BannerPlugin({ banner: '#!/usr/bin/env node', raw: true }),
|
||||
],
|
||||
};
|
Loading…
Reference in New Issue