1
0
mirror of https://github.com/sasjs/adapter.git synced 2025-12-10 17:04:36 +00:00

Merge pull request #57 from sasjs/change-code-style

chore(*): change code style to remove semicolons
This commit is contained in:
Krishna Acondy
2020-09-01 12:07:24 +01:00
committed by GitHub
39 changed files with 1001 additions and 1003 deletions

View File

@@ -1,6 +1,6 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": true,
"semi": false,
"singleQuote": false
}

View File

@@ -1,8 +1,8 @@
import { isLogInRequired, needsRetry } from "./utils";
import { CsrfToken } from "./types/CsrfToken";
import { UploadFile } from "./types/UploadFile";
import { isLogInRequired, needsRetry } from "./utils"
import { CsrfToken } from "./types/CsrfToken"
import { UploadFile } from "./types/UploadFile"
const requestRetryLimit = 5;
const requestRetryLimit = 5
export class FileUploader {
constructor(
@@ -12,38 +12,38 @@ export class FileUploader {
private setCsrfTokenWeb: any,
private csrfToken: CsrfToken | null = null
) {}
private retryCount = 0;
private retryCount = 0
public uploadFile(sasJob: string, files: UploadFile[], params: any) {
if (files?.length < 1) throw new Error("Atleast one file must be provided");
if (files?.length < 1) throw new Error("Atleast one file must be provided")
let paramsString = "";
let paramsString = ""
for (let param in params) {
if (params.hasOwnProperty(param)) {
paramsString += `&${param}=${params[param]}`;
paramsString += `&${param}=${params[param]}`
}
}
const program = this.appLoc
? this.appLoc.replace(/\/?$/, "/") + sasJob.replace(/^\//, "")
: sasJob;
: sasJob
const uploadUrl = `${this.serverUrl}${this.jobsPath}/?${
"_program=" + program
}${paramsString}`;
}${paramsString}`
const headers = {
"cache-control": "no-cache"
};
}
return new Promise((resolve, reject) => {
const formData = new FormData();
const formData = new FormData()
for (let file of files) {
formData.append("file", file.file, file.fileName);
formData.append("file", file.file, file.fileName)
}
if (this.csrfToken) formData.append("_csrf", this.csrfToken.value);
if (this.csrfToken) formData.append("_csrf", this.csrfToken.value)
fetch(uploadUrl, {
method: "POST",
@@ -54,47 +54,47 @@ export class FileUploader {
.then(async (response) => {
if (!response.ok) {
if (response.status === 403) {
const tokenHeader = response.headers.get("X-CSRF-HEADER");
const tokenHeader = response.headers.get("X-CSRF-HEADER")
if (tokenHeader) {
const token = response.headers.get(tokenHeader);
const token = response.headers.get(tokenHeader)
this.csrfToken = {
headerName: tokenHeader,
value: token || ""
};
}
this.setCsrfTokenWeb(this.csrfToken);
this.setCsrfTokenWeb(this.csrfToken)
}
}
}
return response.text();
return response.text()
})
.then((responseText) => {
if (isLogInRequired(responseText))
reject("You must be logged in to upload a fle");
reject("You must be logged in to upload a fle")
if (needsRetry(responseText)) {
if (this.retryCount < requestRetryLimit) {
this.retryCount++;
this.retryCount++
this.uploadFile(sasJob, files, params).then(
(res: any) => resolve(res),
(err: any) => reject(err)
);
)
} else {
this.retryCount = 0;
reject(responseText);
this.retryCount = 0
reject(responseText)
}
} else {
this.retryCount = 0;
this.retryCount = 0
try {
resolve(JSON.parse(responseText));
resolve(JSON.parse(responseText))
} catch (e) {
reject(e);
reject(e)
}
}
});
});
})
})
}
}

View File

@@ -11,7 +11,7 @@ export class SAS9ApiClient {
public getConfig() {
return {
serverUrl: this.serverUrl
};
}
}
/**
@@ -19,7 +19,7 @@ export class SAS9ApiClient {
* @param serverUrl - the URL of the server.
*/
public setConfig(serverUrl: string) {
if (serverUrl) this.serverUrl = serverUrl;
if (serverUrl) this.serverUrl = serverUrl
}
/**
@@ -33,19 +33,19 @@ export class SAS9ApiClient {
serverName: string,
repositoryName: string
) {
const requestPayload = linesOfCode.join("\n");
const requestPayload = linesOfCode.join("\n")
const executeScriptRequest = {
method: "PUT",
headers: {
Accept: "application/json"
},
body: `command=${requestPayload}`
};
}
const executeScriptResponse = await fetch(
`${this.serverUrl}/sas/servers/${serverName}/cmd?repositoryName=${repositoryName}`,
executeScriptRequest
).then((res) => res.text());
).then((res) => res.text())
return executeScriptResponse;
return executeScriptResponse
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,36 +1,36 @@
import SASjs from "./index";
import SASjs from "./index"
const adapter = new SASjs();
const adapter = new SASjs()
it("should parse SAS9 source code", async (done) => {
expect(sampleResponse).toBeTruthy();
const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse);
expect(parsedSourceCode).toBeTruthy();
const sourceCodeLines = parsedSourceCode.split("\r\n");
expect(sourceCodeLines.length).toEqual(5);
expect(sourceCodeLines[0].startsWith("6")).toBeTruthy();
expect(sourceCodeLines[1].startsWith("7")).toBeTruthy();
expect(sourceCodeLines[2].startsWith("8")).toBeTruthy();
expect(sourceCodeLines[3].startsWith("9")).toBeTruthy();
expect(sourceCodeLines[4].startsWith("10")).toBeTruthy();
done();
});
expect(sampleResponse).toBeTruthy()
const parsedSourceCode = (adapter as any).parseSAS9SourceCode(sampleResponse)
expect(parsedSourceCode).toBeTruthy()
const sourceCodeLines = parsedSourceCode.split("\r\n")
expect(sourceCodeLines.length).toEqual(5)
expect(sourceCodeLines[0].startsWith("6")).toBeTruthy()
expect(sourceCodeLines[1].startsWith("7")).toBeTruthy()
expect(sourceCodeLines[2].startsWith("8")).toBeTruthy()
expect(sourceCodeLines[3].startsWith("9")).toBeTruthy()
expect(sourceCodeLines[4].startsWith("10")).toBeTruthy()
done()
})
it("should parse generated code", async (done) => {
expect(sampleResponse).toBeTruthy();
expect(sampleResponse).toBeTruthy()
const parsedGeneratedCode = (adapter as any).parseGeneratedCode(
sampleResponse
);
expect(parsedGeneratedCode).toBeTruthy();
const generatedCodeLines = parsedGeneratedCode.split("\r\n");
expect(generatedCodeLines.length).toEqual(5);
expect(generatedCodeLines[0].startsWith("MPRINT(MM_WEBIN)")).toBeTruthy();
expect(generatedCodeLines[1].startsWith("MPRINT(MM_WEBLEFT)")).toBeTruthy();
expect(generatedCodeLines[2].startsWith("MPRINT(MM_WEBOUT)")).toBeTruthy();
expect(generatedCodeLines[3].startsWith("MPRINT(MM_WEBRIGHT)")).toBeTruthy();
expect(generatedCodeLines[4].startsWith("MPRINT(MM_WEBOUT)")).toBeTruthy();
done();
});
)
expect(parsedGeneratedCode).toBeTruthy()
const generatedCodeLines = parsedGeneratedCode.split("\r\n")
expect(generatedCodeLines.length).toEqual(5)
expect(generatedCodeLines[0].startsWith("MPRINT(MM_WEBIN)")).toBeTruthy()
expect(generatedCodeLines[1].startsWith("MPRINT(MM_WEBLEFT)")).toBeTruthy()
expect(generatedCodeLines[2].startsWith("MPRINT(MM_WEBOUT)")).toBeTruthy()
expect(generatedCodeLines[3].startsWith("MPRINT(MM_WEBRIGHT)")).toBeTruthy()
expect(generatedCodeLines[4].startsWith("MPRINT(MM_WEBOUT)")).toBeTruthy()
done()
})
/* tslint:disable */
const sampleResponse = `<meta http-equiv="Content-Type" content="text/html; charset=windows-1252"/>
@@ -44,5 +44,5 @@ MPRINT(MM_WEBLEFT): filename _temp temp lrecl=999999;
MPRINT(MM_WEBOUT): data _null_;
MPRINT(MM_WEBRIGHT): file _temp;
MPRINT(MM_WEBOUT): if upcase(symget('_debug'))='LOG' then put '&gt;&gt;weboutBEGIN&lt;&lt;';
`;
`
/* tslint:enable */

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
import { Session, Context, CsrfToken } from "./types";
import { asyncForEach, makeRequest } from "./utils";
import { Session, Context, CsrfToken } from "./types"
import { asyncForEach, makeRequest } from "./utils"
const MAX_SESSION_COUNT = 1;
const MAX_SESSION_COUNT = 1
export class SessionManager {
constructor(
@@ -9,49 +9,49 @@ export class SessionManager {
private contextName: string,
private setCsrfToken: (csrfToken: CsrfToken) => void
) {}
private sessions: Session[] = [];
private currentContext: Context | null = null;
private csrfToken: CsrfToken | null = null;
private sessions: Session[] = []
private currentContext: Context | null = null
private csrfToken: CsrfToken | null = null
async getSession(accessToken?: string) {
await this.createSessions(accessToken);
this.createAndWaitForSession(accessToken);
const session = this.sessions.pop();
await this.createSessions(accessToken)
this.createAndWaitForSession(accessToken)
const session = this.sessions.pop()
const secondsSinceSessionCreation =
(new Date().getTime() - new Date(session!.creationTimeStamp).getTime()) /
1000;
1000
if (
secondsSinceSessionCreation >= session!.attributes.sessionInactiveTimeout
) {
await this.createSessions(accessToken);
const freshSession = this.sessions.pop();
return freshSession;
await this.createSessions(accessToken)
const freshSession = this.sessions.pop()
return freshSession
}
return session;
return session
}
async clearSession(id: string, accessToken?: string) {
const deleteSessionRequest = {
method: "DELETE",
headers: this.getHeaders(accessToken)
};
}
return await this.request<Session>(
`${this.serverUrl}/compute/sessions/${id}`,
deleteSessionRequest
).then(() => {
this.sessions = this.sessions.filter((s) => s.id !== id);
});
this.sessions = this.sessions.filter((s) => s.id !== id)
})
}
private async createSessions(accessToken?: string) {
if (!this.sessions.length) {
if (!this.currentContext) {
await this.setCurrentContext(accessToken);
await this.setCurrentContext(accessToken)
}
await asyncForEach(new Array(MAX_SESSION_COUNT), async () => {
const createdSession = await this.createAndWaitForSession(accessToken);
this.sessions.push(createdSession);
});
const createdSession = await this.createAndWaitForSession(accessToken)
this.sessions.push(createdSession)
})
}
}
@@ -59,53 +59,53 @@ export class SessionManager {
const createSessionRequest = {
method: "POST",
headers: this.getHeaders(accessToken)
};
}
const { result: createdSession, etag } = await this.request<Session>(
`${this.serverUrl}/compute/contexts/${this.currentContext!.id}/sessions`,
createSessionRequest
);
)
await this.waitForSession(createdSession, etag);
this.sessions.push(createdSession);
return createdSession;
await this.waitForSession(createdSession, etag)
this.sessions.push(createdSession)
return createdSession
}
private async setCurrentContext(accessToken?: string) {
if (!this.currentContext) {
const { result: contexts } = await this.request<{
items: Context[];
items: Context[]
}>(`${this.serverUrl}/compute/contexts`, {
headers: this.getHeaders(accessToken)
});
})
const contextsList =
contexts && contexts.items && contexts.items.length
? contexts.items
: [];
: []
const currentContext = contextsList.find(
(c: any) => c.name === this.contextName
);
)
if (!currentContext) {
throw new Error(
`The context ${this.contextName} was not found on the server ${this.serverUrl}`
);
)
}
this.currentContext = currentContext;
this.currentContext = currentContext
}
}
private getHeaders(accessToken?: string) {
const headers: any = {
"Content-Type": "application/json"
};
}
if (accessToken) {
headers.Authorization = `Bearer ${accessToken}`;
headers.Authorization = `Bearer ${accessToken}`
}
return headers;
return headers
}
private async waitForSession(
@@ -114,17 +114,17 @@ export class SessionManager {
accessToken?: string,
silent = false
) {
let sessionState = session.state;
let sessionState = session.state
const headers: any = {
...this.getHeaders(accessToken),
"If-None-Match": etag
};
const stateLink = session.links.find((l: any) => l.rel === "state");
}
const stateLink = session.links.find((l: any) => l.rel === "state")
return new Promise(async (resolve, _) => {
if (sessionState === "pending") {
if (stateLink) {
if (!silent) {
console.log("Polling session status... \n");
console.log("Polling session status... \n")
}
const { result: state } = await this.request<string>(
`${this.serverUrl}${stateLink.href}?wait=30`,
@@ -132,18 +132,18 @@ export class SessionManager {
headers
},
"text"
);
)
sessionState = state.trim();
sessionState = state.trim()
if (!silent) {
console.log(`Current state: ${sessionState}\n`);
console.log(`Current state: ${sessionState}\n`)
}
resolve(sessionState);
resolve(sessionState)
}
} else {
resolve(sessionState);
resolve(sessionState)
}
});
})
}
private async request<T>(
@@ -155,16 +155,16 @@ export class SessionManager {
options.headers = {
...options.headers,
[this.csrfToken.headerName]: this.csrfToken.value
};
}
}
return await makeRequest<T>(
url,
options,
(token) => {
this.csrfToken = token;
this.setCsrfToken(token);
this.csrfToken = token
this.setCsrfToken(token)
},
contentType
);
)
}
}

View File

@@ -1,5 +1,5 @@
import SASjs from "./SASjs";
export * from "./types";
export * from "./SASViyaApiClient";
export * from "./SAS9ApiClient";
export default SASjs;
import SASjs from "./SASjs"
export * from "./types"
export * from "./SASViyaApiClient"
export * from "./SAS9ApiClient"
export default SASjs

View File

@@ -1,6 +1,6 @@
export interface Context {
name: string;
id: string;
createdBy: string;
version: number;
name: string
id: string
createdBy: string
version: number
}

View File

@@ -1,4 +1,4 @@
export interface CsrfToken {
headerName: string;
value: string;
headerName: string
value: string
}

View File

@@ -1,7 +1,7 @@
import { Link } from "./Link";
import { Link } from "./Link"
export interface Folder {
id: string;
uri: string;
links: Link[];
id: string
uri: string
links: Link[]
}

View File

@@ -1,13 +1,13 @@
import { Link } from "./Link";
import { JobResult } from "./JobResult";
import { Link } from "./Link"
import { JobResult } from "./JobResult"
export interface Job {
id: string;
name: string;
uri: string;
createdBy: string;
code?: string;
links: Link[];
results: JobResult;
error?: any;
id: string
name: string
uri: string
createdBy: string
code?: string
links: Link[]
results: JobResult
error?: any
}

View File

@@ -1,3 +1,3 @@
export interface JobDefinition {
code: string;
code: string
}

View File

@@ -1,3 +1,3 @@
export interface JobResult {
"_webout.json": string;
"_webout.json": string
}

View File

@@ -1,7 +1,7 @@
export interface Link {
method: string;
rel: string;
href: string;
uri: string;
type: string;
method: string
rel: string
href: string
uri: string
type: string
}

View File

@@ -1,4 +1,4 @@
import { ServerType } from "./ServerType";
import { ServerType } from "./ServerType"
/**
* Specifies the configuration for the SASjs instance.
@@ -10,22 +10,22 @@ export class SASjsConfig {
* Can be omitted, eg if serving directly from the SAS Web Server or being
* streamed.
*/
serverUrl: string = "";
pathSAS9: string = "";
pathSASViya: string = "";
serverUrl: string = ""
pathSAS9: string = ""
pathSASViya: string = ""
/**
* The appLoc is the parent folder under which the SAS services (STPs or Job
* Execution Services) are stored.
*/
appLoc: string = "";
appLoc: string = ""
/**
* Can be SAS9 or SASVIYA
*/
serverType: ServerType | null = null;
serverType: ServerType | null = null
/**
* Set to `true` to enable additional debugging.
*/
debug: boolean = true;
contextName: string = "";
useComputeApi = false;
debug: boolean = true
contextName: string = ""
useComputeApi = false
}

View File

@@ -3,10 +3,10 @@
*
*/
export interface SASjsRequest {
serviceLink: string;
timestamp: Date;
sourceCode: string;
generatedCode: string;
logFile: string;
SASWORK: any;
serviceLink: string
timestamp: Date
sourceCode: string
generatedCode: string
logFile: string
SASWORK: any
}

View File

@@ -4,11 +4,11 @@
*/
export interface SASjsWaitingRequest {
requestPromise: {
promise: any;
resolve: any;
reject: any;
};
SASjob: string;
data: any;
config?: any;
promise: any
resolve: any
reject: any
}
SASjob: string
data: any
config?: any
}

View File

@@ -1,11 +1,11 @@
import { Link } from "./Link";
import { Link } from "./Link"
export interface Session {
id: string;
state: string;
links: Link[];
id: string
state: string
links: Link[]
attributes: {
sessionInactiveTimeout: number;
};
creationTimeStamp: string;
sessionInactiveTimeout: number
}
creationTimeStamp: string
}

View File

@@ -3,6 +3,6 @@
*
*/
export interface UploadFile {
file: File;
fileName: string;
file: File
fileName: string
}

View File

@@ -1,11 +1,11 @@
export * from "./Context";
export * from "./CsrfToken";
export * from "./Folder";
export * from "./Job";
export * from "./Link";
export * from "./SASjsConfig";
export * from "./SASjsRequest";
export * from "./SASjsWaitingRequest";
export * from "./ServerType";
export * from "./Session";
export * from "./UploadFile";
export * from "./Context"
export * from "./CsrfToken"
export * from "./Folder"
export * from "./Job"
export * from "./Link"
export * from "./SASjsConfig"
export * from "./SASjsRequest"
export * from "./SASjsWaitingRequest"
export * from "./ServerType"
export * from "./Session"
export * from "./UploadFile"

View File

@@ -1,5 +1,5 @@
export async function asyncForEach(array: any[], callback: any) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
await callback(array[index], index, array)
}
}

View File

@@ -1,9 +1,9 @@
import { SASjsRequest } from "../types/SASjsRequest";
import { SASjsRequest } from "../types/SASjsRequest"
/**
* Comparator for SASjs request timestamps
*
*/
export const compareTimestamps = (a: SASjsRequest, b: SASjsRequest) => {
return b.timestamp.getTime() - a.timestamp.getTime();
};
return b.timestamp.getTime() - a.timestamp.getTime()
}

View File

@@ -3,14 +3,14 @@
* @param data - the JSON object to convert.
*/
export const convertToCSV = (data: any) => {
const replacer = (key: any, value: any) => (value === null ? "" : value);
const headerFields = Object.keys(data[0]);
let csvTest;
let invalidString = false;
const replacer = (key: any, value: any) => (value === null ? "" : value)
const headerFields = Object.keys(data[0])
let csvTest
let invalidString = false
const headers = headerFields.map((field) => {
let firstFoundType: string | null = null;
let hasMixedTypes: boolean = false;
let rowNumError: number = -1;
let firstFoundType: string | null = null
let hasMixedTypes: boolean = false
let rowNumError: number = -1
const longestValueForField = data
.map((row: any, index: number) => {
@@ -19,46 +19,46 @@ export const convertToCSV = (data: any) => {
let currentFieldType =
row[field] === "" || typeof row[field] === "string"
? "chars"
: "number";
: "number"
if (!hasMixedTypes) {
hasMixedTypes = currentFieldType !== firstFoundType;
rowNumError = hasMixedTypes ? index + 1 : -1;
hasMixedTypes = currentFieldType !== firstFoundType
rowNumError = hasMixedTypes ? index + 1 : -1
}
} else {
if (row[field] === "") {
firstFoundType = "chars";
firstFoundType = "chars"
} else {
firstFoundType =
typeof row[field] === "string" ? "chars" : "number";
typeof row[field] === "string" ? "chars" : "number"
}
}
let byteSize;
let byteSize
if (typeof row[field] === "string") {
let doubleQuotesFound = row[field]
.split("")
.filter((char: any) => char === '"');
.filter((char: any) => char === '"')
byteSize = getByteSize(row[field]);
byteSize = getByteSize(row[field])
if (doubleQuotesFound.length > 0) {
byteSize += doubleQuotesFound.length;
byteSize += doubleQuotesFound.length
}
}
return byteSize;
return byteSize
}
})
.sort((a: number, b: number) => b - a)[0];
.sort((a: number, b: number) => b - a)[0]
if (longestValueForField && longestValueForField > 32765) {
invalidString = true;
invalidString = true
}
if (hasMixedTypes) {
console.error(
`Row (${rowNumError}), Column (${field}) has mixed types: ERROR`
);
)
}
return `${field}:${firstFoundType === "chars" ? "$" : ""}${
@@ -67,30 +67,30 @@ export const convertToCSV = (data: any) => {
: firstFoundType === "chars"
? "1"
: "best"
}.`;
});
}.`
})
if (invalidString) {
return "ERROR: LARGE STRING LENGTH";
return "ERROR: LARGE STRING LENGTH"
}
csvTest = data.map((row: any) => {
const fields = Object.keys(row).map((fieldName, index) => {
let value;
let containsSpecialChar = false;
const currentCell = row[fieldName];
let value
let containsSpecialChar = false
const currentCell = row[fieldName]
if (JSON.stringify(currentCell).search(/(\\t|\\n|\\r)/gm) > -1) {
value = currentCell.toString();
containsSpecialChar = true;
value = currentCell.toString()
containsSpecialChar = true
} else {
value = JSON.stringify(currentCell, replacer);
value = JSON.stringify(currentCell, replacer)
}
value = value.replace(/\\\\/gm, "\\");
value = value.replace(/\\\\/gm, "\\")
if (containsSpecialChar) {
if (value.includes(",") || value.includes('"')) {
value = '"' + value + '"';
value = '"' + value + '"'
}
} else {
if (
@@ -98,36 +98,36 @@ export const convertToCSV = (data: any) => {
value.includes('"') &&
!value.includes('\\"')
) {
value = value.substring(1, value.length - 1);
value = value.substring(1, value.length - 1)
}
value = value.replace(/\\"/gm, '""');
value = value.replace(/\\"/gm, '""')
}
value = value.replace(/\r\n/gm, "\n");
value = value.replace(/\r\n/gm, "\n")
if (value === "" && headers[index].includes("best")) {
value = ".";
value = "."
}
return value;
});
return fields.join(",");
});
return value
})
return fields.join(",")
})
let finalCSV =
headers.join(",").replace(/,/g, " ") + "\r\n" + csvTest.join("\r\n");
headers.join(",").replace(/,/g, " ") + "\r\n" + csvTest.join("\r\n")
return finalCSV;
};
return finalCSV
}
const getByteSize = (str: string) => {
let byteSize = str.length;
let byteSize = str.length
for (let i = str.length - 1; i >= 0; i--) {
const code = str.charCodeAt(i);
if (code > 0x7f && code <= 0x7ff) byteSize++;
else if (code > 0x7ff && code <= 0xffff) byteSize += 2;
if (code >= 0xdc00 && code <= 0xdfff) i--; //trail surrogate
const code = str.charCodeAt(i)
if (code > 0x7f && code <= 0x7ff) byteSize++
else if (code > 0x7ff && code <= 0xffff) byteSize += 2
if (code >= 0xdc00 && code <= 0xdfff) i-- //trail surrogate
}
return byteSize;
};
return byteSize
}

View File

@@ -1,33 +1,33 @@
import { convertToCSV } from "./convertToCsv";
import { splitChunks } from "./splitChunks";
import { convertToCSV } from "./convertToCsv"
import { splitChunks } from "./splitChunks"
export const formatDataForRequest = (data: any) => {
const sasjsTables = [];
let tableCounter = 0;
const result: any = {};
const sasjsTables = []
let tableCounter = 0
const result: any = {}
for (const tableName in data) {
tableCounter++;
sasjsTables.push(tableName);
const csv = convertToCSV(data[tableName]);
tableCounter++
sasjsTables.push(tableName)
const csv = convertToCSV(data[tableName])
if (csv === "ERROR: LARGE STRING LENGTH") {
throw new Error(
"The max length of a string value in SASjs is 32765 characters."
);
)
}
// if csv has length more then 16k, send in chunks
if (csv.length > 16000) {
const csvChunks = splitChunks(csv);
const csvChunks = splitChunks(csv)
// append chunks to form data with same key
result[`sasjs${tableCounter}data0`] = csvChunks.length;
result[`sasjs${tableCounter}data0`] = csvChunks.length
csvChunks.forEach((chunk, index) => {
result[`sasjs${tableCounter}data${index + 1}`] = chunk;
});
result[`sasjs${tableCounter}data${index + 1}`] = chunk
})
} else {
result[`sasjs${tableCounter}data`] = csv;
result[`sasjs${tableCounter}data`] = csv
}
}
result["sasjs_tables"] = sasjsTables.join(" ");
result["sasjs_tables"] = sasjsTables.join(" ")
return result;
};
return result
}

View File

@@ -1,15 +1,15 @@
export * from "./asyncForEach";
export * from "./compareTimestamps";
export * from "./convertToCsv";
export * from "./isAuthorizeFormRequired";
export * from "./isLoginRequired";
export * from "./isLoginSuccess";
export * from "./makeRequest";
export * from "./needsRetry";
export * from "./parseAndSubmitAuthorizeForm";
export * from "./parseGeneratedCode";
export * from "./parseSourceCode";
export * from "./parseSasViyaLog";
export * from "./serialize";
export * from "./splitChunks";
export * from "./parseWeboutResponse";
export * from "./asyncForEach"
export * from "./compareTimestamps"
export * from "./convertToCsv"
export * from "./isAuthorizeFormRequired"
export * from "./isLoginRequired"
export * from "./isLoginSuccess"
export * from "./makeRequest"
export * from "./needsRetry"
export * from "./parseAndSubmitAuthorizeForm"
export * from "./parseGeneratedCode"
export * from "./parseSourceCode"
export * from "./parseSasViyaLog"
export * from "./serialize"
export * from "./splitChunks"
export * from "./parseWeboutResponse"

View File

@@ -1,3 +1,3 @@
export const isAuthorizeFormRequired = (response: string): boolean => {
return /<form.+action="(.*Logon\/oauth\/authorize[^"]*).*>/gm.test(response);
};
return /<form.+action="(.*Logon\/oauth\/authorize[^"]*).*>/gm.test(response)
}

View File

@@ -1,34 +1,34 @@
export function isIEorEdgeOrOldFirefox() {
if (typeof window === "undefined") {
return false;
return false
}
const ua = window.navigator.userAgent;
const ua = window.navigator.userAgent
if (ua.indexOf("Firefox") > 0) {
const version = parseInt(
ua.substring(ua.lastIndexOf("Firefox/") + 8, ua.length),
10
);
return version <= 60;
)
return version <= 60
}
const msie = ua.indexOf("MSIE ");
const msie = ua.indexOf("MSIE ")
if (msie > 0) {
// IE 10 or older => return version number
return true;
return true
}
const trident = ua.indexOf("Trident/");
const trident = ua.indexOf("Trident/")
if (trident > 0) {
return true;
return true
}
const edge = ua.indexOf("Edge/");
const edge = ua.indexOf("Edge/")
if (edge > 0) {
// Edge (IE 12+) => return version number
return true;
return true
}
// other browser
return false;
return false
}

View File

@@ -1,5 +1,5 @@
export const isLogInRequired = (response: string): boolean => {
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/gm;
const matches = pattern.test(response);
return matches;
};
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/gm
const matches = pattern.test(response)
return matches
}

View File

@@ -1,2 +1,2 @@
export const isLogInSuccess = (response: string): boolean =>
/You have signed in/gm.test(response);
/You have signed in/gm.test(response)

View File

@@ -1,8 +1,8 @@
import { CsrfToken } from "../types";
import { needsRetry } from "./needsRetry";
import { CsrfToken } from "../types"
import { needsRetry } from "./needsRetry"
let retryCount: number = 0;
let retryLimit: number = 5;
let retryCount: number = 0
let retryLimit: number = 5
export async function makeRequest<T>(
url: string,
@@ -10,98 +10,98 @@ export async function makeRequest<T>(
callback: (value: CsrfToken) => any,
contentType: "text" | "json" = "json"
): Promise<{ result: T; etag: string | null }> {
let retryRequest: any = null;
let retryRequest: any = null
const responseTransform =
contentType === "json"
? (res: Response) => res.json()
: (res: Response) => res.text();
let etag = null;
: (res: Response) => res.text()
let etag = null
const result = await fetch(url, request).then(async (response) => {
if (response.redirected && response.url.includes("SASLogon/login")) {
return Promise.reject({ status: 401 });
return Promise.reject({ status: 401 })
}
if (!response.ok) {
if (response.status === 403) {
const tokenHeader = response.headers.get("X-CSRF-HEADER");
const tokenHeader = response.headers.get("X-CSRF-HEADER")
if (tokenHeader) {
const token = response.headers.get(tokenHeader);
const token = response.headers.get(tokenHeader)
callback({
headerName: tokenHeader,
value: token || ""
});
})
retryRequest = {
...request,
headers: { ...request.headers, [tokenHeader]: token }
};
}
return fetch(url, retryRequest).then((res) => {
etag = res.headers.get("ETag");
return responseTransform(res);
});
etag = res.headers.get("ETag")
return responseTransform(res)
})
}
} else {
const body = await response.text();
const body = await response.text()
if (needsRetry(body)) {
if (retryCount < retryLimit) {
retryCount++;
retryCount++
let retryResponse = await makeRequest(
url,
retryRequest || request,
callback,
contentType
);
retryCount = 0;
)
retryCount = 0
etag = retryResponse.etag;
return retryResponse.result;
etag = retryResponse.etag
return retryResponse.result
} else {
retryCount = 0;
retryCount = 0
throw new Error("Request retry limit exceeded");
throw new Error("Request retry limit exceeded")
}
}
return Promise.reject({ status: response.status, body });
return Promise.reject({ status: response.status, body })
}
} else {
if (response.status === 204) {
return Promise.resolve();
return Promise.resolve()
}
const responseTransformed = await responseTransform(response);
let responseText = "";
const responseTransformed = await responseTransform(response)
let responseText = ""
if (typeof responseTransformed === "string") {
responseText = responseTransformed;
responseText = responseTransformed
} else {
responseText = JSON.stringify(responseTransformed);
responseText = JSON.stringify(responseTransformed)
}
if (needsRetry(responseText)) {
if (retryCount < retryLimit) {
retryCount++;
retryCount++
const retryResponse = await makeRequest(
url,
retryRequest || request,
callback,
contentType
);
retryCount = 0;
)
retryCount = 0
etag = retryResponse.etag;
return retryResponse.result;
etag = retryResponse.etag
return retryResponse.result
} else {
retryCount = 0;
retryCount = 0
throw new Error("Request retry limit exceeded");
throw new Error("Request retry limit exceeded")
}
}
etag = response.headers.get("ETag");
return responseTransformed;
etag = response.headers.get("ETag")
return responseTransformed
}
});
return { result, etag };
})
return { result, etag }
}

View File

@@ -10,5 +10,5 @@ export const needsRetry = (responseText: string): boolean => {
responseText.includes(
"Authentication success, retry original request"
)))
);
};
)
}

View File

@@ -2,31 +2,31 @@ export const parseAndSubmitAuthorizeForm = async (
response: string,
serverUrl: string
) => {
let authUrl: string | null = null;
const params: any = {};
let authUrl: string | null = null
const params: any = {}
const responseBody = response.split("<body>")[1].split("</body>")[0];
const bodyElement = document.createElement("div");
bodyElement.innerHTML = responseBody;
const responseBody = response.split("<body>")[1].split("</body>")[0]
const bodyElement = document.createElement("div")
bodyElement.innerHTML = responseBody
const form = bodyElement.querySelector("#application_authorization");
authUrl = form ? serverUrl + form.getAttribute("action") : null;
const form = bodyElement.querySelector("#application_authorization")
authUrl = form ? serverUrl + form.getAttribute("action") : null
const inputs: any = form?.querySelectorAll("input");
const inputs: any = form?.querySelectorAll("input")
for (const input of inputs) {
if (input.name === "user_oauth_approval") {
input.value = "true";
input.value = "true"
}
params[input.name] = input.value;
params[input.name] = input.value
}
const formData = new FormData();
const formData = new FormData()
for (const key in params) {
if (params.hasOwnProperty(key)) {
formData.append(key, params[key]);
formData.append(key, params[key])
}
}
@@ -40,10 +40,10 @@ export const parseAndSubmitAuthorizeForm = async (
})
.then((res) => res.text())
.then((res) => {
resolve(res);
});
resolve(res)
})
} else {
reject("Auth form url is null");
reject("Auth form url is null")
}
});
};
})
}

View File

@@ -1,7 +1,7 @@
export const parseGeneratedCode = (log: string) => {
const startsWith = "MPRINT";
const startsWith = "MPRINT"
const isGeneratedCodeLine = (line: string) =>
line.trim().startsWith(startsWith);
const logLines = log.split("\n").filter(isGeneratedCodeLine);
return logLines.join("\r\n");
};
line.trim().startsWith(startsWith)
const logLines = log.split("\n").filter(isGeneratedCodeLine)
return logLines.join("\r\n")
}

View File

@@ -1,12 +1,12 @@
export const parseSasViyaLog = (logResponse: { items: any[] }) => {
let log;
let log
try {
log = logResponse.items
? logResponse.items.map((i) => i.line).join("\n")
: JSON.stringify(logResponse);
: JSON.stringify(logResponse)
} catch (e) {
console.error("An error has occurred while parsing the log response", e);
log = logResponse;
console.error("An error has occurred while parsing the log response", e)
log = logResponse
}
return log;
};
return log
}

View File

@@ -1,6 +1,6 @@
export const parseSourceCode = (log: string): string => {
const isSourceCodeLine = (line: string) =>
line.trim().substring(0, 10).trimStart().match(/^\d/);
const logLines = log.split("\n").filter(isSourceCodeLine);
return logLines.join("\r\n");
};
line.trim().substring(0, 10).trimStart().match(/^\d/)
const logLines = log.split("\n").filter(isSourceCodeLine)
return logLines.join("\r\n")
}

View File

@@ -1,16 +1,16 @@
export const parseWeboutResponse = (response: string) => {
let sasResponse = "";
let sasResponse = ""
if (response.includes(">>weboutBEGIN<<")) {
try {
sasResponse = response
.split(">>weboutBEGIN<<")[1]
.split(">>weboutEND<<")[0];
.split(">>weboutEND<<")[0]
} catch (e) {
sasResponse = "";
console.error(e);
sasResponse = ""
console.error(e)
}
}
return sasResponse;
};
return sasResponse
}

View File

@@ -1,15 +1,15 @@
export const serialize = (obj: any) => {
const str: any[] = [];
const str: any[] = []
for (const p in obj) {
if (obj.hasOwnProperty(p)) {
if (obj[p] instanceof Array) {
for (let i = 0, n = obj[p].length; i < n; i++) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p][i]));
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p][i]))
}
} else {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]))
}
}
}
return str.join("&");
};
return str.join("&")
}

View File

@@ -1,12 +1,12 @@
export const splitChunks = (content: string) => {
const size = 16000;
const size = 16000
const numChunks = Math.ceil(content.length / size);
const chunks = new Array(numChunks);
const numChunks = Math.ceil(content.length / size)
const chunks = new Array(numChunks)
for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
chunks[i] = content.substr(o, size);
chunks[i] = content.substr(o, size)
}
return chunks;
};
return chunks
}