-
Notifications
You must be signed in to change notification settings - Fork 13.7k
Expand file tree
/
Copy pathAppPackageParser.ts
More file actions
142 lines (110 loc) · 3.88 KB
/
Copy pathAppPackageParser.ts
File metadata and controls
142 lines (110 loc) · 3.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import * as path from 'path';
import * as AdmZip from 'adm-zip';
import * as semver from 'semver';
import { v4 as uuidv4 } from 'uuid';
import { AppImplements } from '.';
import type { IParseAppPackageResult } from './IParseAppPackageResult';
import type { IAppInfo } from '../../definition/metadata/IAppInfo';
import { ENGINE_VERSION } from '../../definition/version';
import { RequiredApiVersionError } from '../errors';
export class AppPackageParser {
public static uuid4Regex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/;
private allowedIconExts: Array<string> = ['.png', '.jpg', '.jpeg', '.gif'];
private appsEngineVersion: string = ENGINE_VERSION;
public async unpackageApp(appPackage: Buffer): Promise<IParseAppPackageResult> {
const zip = new AdmZip(appPackage);
const infoZip = zip.getEntry('app.json');
let info: IAppInfo;
if (infoZip && !infoZip.isDirectory) {
try {
info = JSON.parse(infoZip.getData().toString()) as IAppInfo;
if (!AppPackageParser.uuid4Regex.test(info.id)) {
info.id = uuidv4();
console.warn(
'WARNING: We automatically generated a uuid v4 id for',
info.name,
'since it did not provide us an id. This is NOT',
'recommended as the same App can be installed several times.',
);
}
} catch {
throw new Error('Invalid App package. The "app.json" file is not valid json.');
}
} else {
throw new Error('Invalid App package. No "app.json" file.');
}
info.classFile = info.classFile.replace('.ts', '.js');
if (!semver.satisfies(this.appsEngineVersion, info.requiredApiVersion)) {
throw new RequiredApiVersionError(info, this.appsEngineVersion);
}
// Load all of the TypeScript only files
const files: { [s: string]: string } = {};
zip
.getEntries()
.filter((entry) => !entry.isDirectory && entry.entryName.endsWith('.js'))
.forEach((entry) => {
const norm = path.normalize(entry.entryName);
// Files which start with `.` are supposed to be hidden
if (norm.startsWith('.')) {
return;
}
files[norm] = entry.getData().toString();
});
// Ensure that the main class file exists
if (!files[path.normalize(info.classFile)]) {
throw new Error(`Invalid App package. Could not find the classFile (${info.classFile}) file.`);
}
const languageContent = this.getLanguageContent(zip);
// Get the icon's content
const iconFile = this.getIconFile(zip, info.iconFile);
if (iconFile) {
info.iconFileContent = iconFile;
}
const implemented = new AppImplements();
if (Array.isArray(info.implements)) {
info.implements.forEach((interfaceName) => implemented.setImplements(interfaceName));
}
return {
info,
files,
languageContent,
implemented,
};
}
private getLanguageContent(zip: AdmZip): { [key: string]: object } {
const languageContent: { [key: string]: object } = {};
zip
.getEntries()
.filter((entry) => !entry.isDirectory && entry.entryName.startsWith('i18n/') && entry.entryName.endsWith('.json'))
.forEach((entry) => {
const entrySplit = entry.entryName.split('/');
const lang = entrySplit[entrySplit.length - 1].split('.')[0].toLowerCase();
let content;
try {
content = JSON.parse(entry.getData().toString());
} catch {
// Failed to parse it, maybe warn them? idk yet
}
languageContent[lang] = Object.assign(languageContent[lang] || {}, content);
});
return languageContent;
}
private getIconFile(zip: AdmZip, filePath: string): string {
if (!filePath) {
return undefined;
}
const ext = path.extname(filePath);
if (!this.allowedIconExts.includes(ext)) {
return undefined;
}
const entry = zip.getEntry(filePath);
if (!entry) {
return undefined;
}
if (entry.isDirectory) {
return undefined;
}
const base64 = entry.getData().toString('base64');
return `data:image/${ext.replace('.', '')};base64,${base64}`;
}
}