react-native-update-cli 1.20.0 → 1.20.6
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.
- package/lib/utils/app-info-parser/apk.js +86 -0
- package/lib/utils/app-info-parser/index.js +37 -0
- package/lib/utils/app-info-parser/ipa.js +96 -0
- package/lib/utils/app-info-parser/resource-finder.js +486 -0
- package/lib/utils/app-info-parser/utils.js +158 -0
- package/lib/utils/app-info-parser/xml-parser/binary.js +671 -0
- package/lib/utils/app-info-parser/xml-parser/manifest.js +224 -0
- package/lib/utils/app-info-parser/zip.js +50 -0
- package/lib/utils/index.js +1 -1
- package/package.json +1 -7
- package/src/.DS_Store +0 -0
- package/src/utils/.DS_Store +0 -0
- package/src/utils/app-info-parser/apk.js +90 -0
- package/src/utils/app-info-parser/index.js +35 -0
- package/src/utils/app-info-parser/ipa.js +92 -0
- package/src/utils/app-info-parser/resource-finder.js +499 -0
- package/src/utils/app-info-parser/utils.js +167 -0
- package/src/utils/app-info-parser/xml-parser/binary.js +674 -0
- package/src/utils/app-info-parser/xml-parser/manifest.js +216 -0
- package/src/utils/app-info-parser/zip.js +48 -0
- package/src/utils/index.js +1 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// From https://github.com/openstf/adbkit-apkreader
|
|
4
|
+
const BinaryXmlParser = require('./binary');
|
|
5
|
+
|
|
6
|
+
const INTENT_MAIN = 'android.intent.action.MAIN';
|
|
7
|
+
const CATEGORY_LAUNCHER = 'android.intent.category.LAUNCHER';
|
|
8
|
+
|
|
9
|
+
class ManifestParser {
|
|
10
|
+
constructor(buffer, options = {}) {
|
|
11
|
+
this.buffer = buffer;
|
|
12
|
+
this.xmlParser = new BinaryXmlParser(this.buffer, options);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
collapseAttributes(element) {
|
|
16
|
+
const collapsed = Object.create(null);
|
|
17
|
+
for (let attr of Array.from(element.attributes)) {
|
|
18
|
+
collapsed[attr.name] = attr.typedValue.value;
|
|
19
|
+
}
|
|
20
|
+
return collapsed;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
parseIntents(element, target) {
|
|
24
|
+
target.intentFilters = [];
|
|
25
|
+
target.metaData = [];
|
|
26
|
+
|
|
27
|
+
return element.childNodes.forEach(element => {
|
|
28
|
+
switch (element.nodeName) {
|
|
29
|
+
case 'intent-filter':
|
|
30
|
+
{
|
|
31
|
+
const intentFilter = this.collapseAttributes(element);
|
|
32
|
+
|
|
33
|
+
intentFilter.actions = [];
|
|
34
|
+
intentFilter.categories = [];
|
|
35
|
+
intentFilter.data = [];
|
|
36
|
+
|
|
37
|
+
element.childNodes.forEach(element => {
|
|
38
|
+
switch (element.nodeName) {
|
|
39
|
+
case 'action':
|
|
40
|
+
intentFilter.actions.push(this.collapseAttributes(element));
|
|
41
|
+
break;
|
|
42
|
+
case 'category':
|
|
43
|
+
intentFilter.categories.push(this.collapseAttributes(element));
|
|
44
|
+
break;
|
|
45
|
+
case 'data':
|
|
46
|
+
intentFilter.data.push(this.collapseAttributes(element));
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
target.intentFilters.push(intentFilter);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
case 'meta-data':
|
|
55
|
+
target.metaData.push(this.collapseAttributes(element));
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
parseApplication(element) {
|
|
62
|
+
const app = this.collapseAttributes(element);
|
|
63
|
+
|
|
64
|
+
app.activities = [];
|
|
65
|
+
app.activityAliases = [];
|
|
66
|
+
app.launcherActivities = [];
|
|
67
|
+
app.services = [];
|
|
68
|
+
app.receivers = [];
|
|
69
|
+
app.providers = [];
|
|
70
|
+
app.usesLibraries = [];
|
|
71
|
+
app.metaData = [];
|
|
72
|
+
|
|
73
|
+
element.childNodes.forEach(element => {
|
|
74
|
+
switch (element.nodeName) {
|
|
75
|
+
case 'activity':
|
|
76
|
+
{
|
|
77
|
+
const activity = this.collapseAttributes(element);
|
|
78
|
+
this.parseIntents(element, activity);
|
|
79
|
+
app.activities.push(activity);
|
|
80
|
+
if (this.isLauncherActivity(activity)) {
|
|
81
|
+
app.launcherActivities.push(activity);
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case 'activity-alias':
|
|
86
|
+
{
|
|
87
|
+
const activityAlias = this.collapseAttributes(element);
|
|
88
|
+
this.parseIntents(element, activityAlias);
|
|
89
|
+
app.activityAliases.push(activityAlias);
|
|
90
|
+
if (this.isLauncherActivity(activityAlias)) {
|
|
91
|
+
app.launcherActivities.push(activityAlias);
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case 'service':
|
|
96
|
+
{
|
|
97
|
+
const service = this.collapseAttributes(element);
|
|
98
|
+
this.parseIntents(element, service);
|
|
99
|
+
app.services.push(service);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case 'receiver':
|
|
103
|
+
{
|
|
104
|
+
const receiver = this.collapseAttributes(element);
|
|
105
|
+
this.parseIntents(element, receiver);
|
|
106
|
+
app.receivers.push(receiver);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case 'provider':
|
|
110
|
+
{
|
|
111
|
+
const provider = this.collapseAttributes(element);
|
|
112
|
+
|
|
113
|
+
provider.grantUriPermissions = [];
|
|
114
|
+
provider.metaData = [];
|
|
115
|
+
provider.pathPermissions = [];
|
|
116
|
+
|
|
117
|
+
element.childNodes.forEach(element => {
|
|
118
|
+
switch (element.nodeName) {
|
|
119
|
+
case 'grant-uri-permission':
|
|
120
|
+
provider.grantUriPermissions.push(this.collapseAttributes(element));
|
|
121
|
+
break;
|
|
122
|
+
case 'meta-data':
|
|
123
|
+
provider.metaData.push(this.collapseAttributes(element));
|
|
124
|
+
break;
|
|
125
|
+
case 'path-permission':
|
|
126
|
+
provider.pathPermissions.push(this.collapseAttributes(element));
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
app.providers.push(provider);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case 'uses-library':
|
|
135
|
+
app.usesLibraries.push(this.collapseAttributes(element));
|
|
136
|
+
break;
|
|
137
|
+
case 'meta-data':
|
|
138
|
+
app.metaData.push(this.collapseAttributes(element));
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return app;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
isLauncherActivity(activity) {
|
|
147
|
+
return activity.intentFilters.some(function (filter) {
|
|
148
|
+
const hasMain = filter.actions.some(action => action.name === INTENT_MAIN);
|
|
149
|
+
if (!hasMain) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
return filter.categories.some(category => category.name === CATEGORY_LAUNCHER);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
parse() {
|
|
157
|
+
const document = this.xmlParser.parse();
|
|
158
|
+
const manifest = this.collapseAttributes(document);
|
|
159
|
+
|
|
160
|
+
manifest.usesPermissions = [];
|
|
161
|
+
manifest.usesPermissionsSDK23 = [];
|
|
162
|
+
manifest.permissions = [];
|
|
163
|
+
manifest.permissionTrees = [];
|
|
164
|
+
manifest.permissionGroups = [];
|
|
165
|
+
manifest.instrumentation = null;
|
|
166
|
+
manifest.usesSdk = null;
|
|
167
|
+
manifest.usesConfiguration = null;
|
|
168
|
+
manifest.usesFeatures = [];
|
|
169
|
+
manifest.supportsScreens = null;
|
|
170
|
+
manifest.compatibleScreens = [];
|
|
171
|
+
manifest.supportsGlTextures = [];
|
|
172
|
+
manifest.application = Object.create(null);
|
|
173
|
+
|
|
174
|
+
document.childNodes.forEach(element => {
|
|
175
|
+
switch (element.nodeName) {
|
|
176
|
+
case 'uses-permission':
|
|
177
|
+
manifest.usesPermissions.push(this.collapseAttributes(element));
|
|
178
|
+
break;
|
|
179
|
+
case 'uses-permission-sdk-23':
|
|
180
|
+
manifest.usesPermissionsSDK23.push(this.collapseAttributes(element));
|
|
181
|
+
break;
|
|
182
|
+
case 'permission':
|
|
183
|
+
manifest.permissions.push(this.collapseAttributes(element));
|
|
184
|
+
break;
|
|
185
|
+
case 'permission-tree':
|
|
186
|
+
manifest.permissionTrees.push(this.collapseAttributes(element));
|
|
187
|
+
break;
|
|
188
|
+
case 'permission-group':
|
|
189
|
+
manifest.permissionGroups.push(this.collapseAttributes(element));
|
|
190
|
+
break;
|
|
191
|
+
case 'instrumentation':
|
|
192
|
+
manifest.instrumentation = this.collapseAttributes(element);
|
|
193
|
+
break;
|
|
194
|
+
case 'uses-sdk':
|
|
195
|
+
manifest.usesSdk = this.collapseAttributes(element);
|
|
196
|
+
break;
|
|
197
|
+
case 'uses-configuration':
|
|
198
|
+
manifest.usesConfiguration = this.collapseAttributes(element);
|
|
199
|
+
break;
|
|
200
|
+
case 'uses-feature':
|
|
201
|
+
manifest.usesFeatures.push(this.collapseAttributes(element));
|
|
202
|
+
break;
|
|
203
|
+
case 'supports-screens':
|
|
204
|
+
manifest.supportsScreens = this.collapseAttributes(element);
|
|
205
|
+
break;
|
|
206
|
+
case 'compatible-screens':
|
|
207
|
+
element.childNodes.forEach(screen => {
|
|
208
|
+
return manifest.compatibleScreens.push(this.collapseAttributes(screen));
|
|
209
|
+
});
|
|
210
|
+
break;
|
|
211
|
+
case 'supports-gl-texture':
|
|
212
|
+
manifest.supportsGlTextures.push(this.collapseAttributes(element));
|
|
213
|
+
break;
|
|
214
|
+
case 'application':
|
|
215
|
+
manifest.application = this.parseApplication(element);
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return manifest;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
module.exports = ManifestParser;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Unzip = require('isomorphic-unzip');
|
|
4
|
+
const { isBrowser, decodeNullUnicode } = require('./utils');
|
|
5
|
+
|
|
6
|
+
class Zip {
|
|
7
|
+
constructor(file) {
|
|
8
|
+
if (isBrowser()) {
|
|
9
|
+
if (!(file instanceof window.Blob || typeof file.size !== 'undefined')) {
|
|
10
|
+
throw new Error('Param error: [file] must be an instance of Blob or File in browser.');
|
|
11
|
+
}
|
|
12
|
+
this.file = file;
|
|
13
|
+
} else {
|
|
14
|
+
if (typeof file !== 'string') {
|
|
15
|
+
throw new Error('Param error: [file] must be file path in Node.');
|
|
16
|
+
}
|
|
17
|
+
this.file = require('path').resolve(file);
|
|
18
|
+
}
|
|
19
|
+
this.unzip = new Unzip(this.file);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* get entries by regexps, the return format is: { <filename>: <Buffer|Blob> }
|
|
24
|
+
* @param {Array} regexps // regexps for matching files
|
|
25
|
+
* @param {String} type // return type, can be buffer or blob, default buffer
|
|
26
|
+
*/
|
|
27
|
+
getEntries(regexps, type = 'buffer') {
|
|
28
|
+
regexps = regexps.map(regex => decodeNullUnicode(regex));
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
this.unzip.getBuffer(regexps, { type }, (err, buffers) => {
|
|
31
|
+
err ? reject(err) : resolve(buffers);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* get entry by regex, return an instance of Buffer or Blob
|
|
37
|
+
* @param {Regex} regex // regex for matching file
|
|
38
|
+
* @param {String} type // return type, can be buffer or blob, default buffer
|
|
39
|
+
*/
|
|
40
|
+
getEntry(regex, type = 'buffer') {
|
|
41
|
+
regex = decodeNullUnicode(regex);
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
this.unzip.getBuffer([regex], { type }, (err, buffers) => {
|
|
44
|
+
err ? reject(err) : resolve(buffers[regex]);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = Zip;
|
package/lib/utils/index.js
CHANGED
|
@@ -31,7 +31,7 @@ var _package = require('../../package.json');
|
|
|
31
31
|
|
|
32
32
|
var _package2 = _interopRequireDefault(_package);
|
|
33
33
|
|
|
34
|
-
var _appInfoParser = require('app-info-parser');
|
|
34
|
+
var _appInfoParser = require('./app-info-parser');
|
|
35
35
|
|
|
36
36
|
var _appInfoParser2 = _interopRequireDefault(_appInfoParser);
|
|
37
37
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-update-cli",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.6",
|
|
4
4
|
"description": "Command tools for javaScript updater with `pushy` service for react native apps.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
},
|
|
32
32
|
"homepage": "https://github.com/reactnativecn/react-native-pushy/tree/master/react-native-pushy-cli",
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"app-info-parser": "^1.1.6",
|
|
35
34
|
"cli-arguments": "^0.2.1",
|
|
36
35
|
"filesize-parser": "^1.5.0",
|
|
37
36
|
"fs-extra": "8",
|
|
@@ -56,10 +55,5 @@
|
|
|
56
55
|
},
|
|
57
56
|
"engines": {
|
|
58
57
|
"node": ">= 10"
|
|
59
|
-
},
|
|
60
|
-
"pnpm": {
|
|
61
|
-
"patchedDependencies": {
|
|
62
|
-
"app-info-parser@1.1.6": "patches/app-info-parser@1.1.6.patch"
|
|
63
|
-
}
|
|
64
58
|
}
|
|
65
59
|
}
|
package/src/.DS_Store
ADDED
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const Zip = require('./zip')
|
|
2
|
+
const { mapInfoResource, findApkIconPath, getBase64FromBuffer } = require('./utils')
|
|
3
|
+
const ManifestName = /^androidmanifest\.xml$/
|
|
4
|
+
const ResourceName = /^resources\.arsc$/
|
|
5
|
+
|
|
6
|
+
const ManifestXmlParser = require('./xml-parser/manifest')
|
|
7
|
+
const ResourceFinder = require('./resource-finder')
|
|
8
|
+
|
|
9
|
+
class ApkParser extends Zip {
|
|
10
|
+
/**
|
|
11
|
+
* parser for parsing .apk file
|
|
12
|
+
* @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser
|
|
13
|
+
*/
|
|
14
|
+
constructor (file) {
|
|
15
|
+
super(file)
|
|
16
|
+
if (!(this instanceof ApkParser)) {
|
|
17
|
+
return new ApkParser(file)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
parse () {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
this.getEntries([ManifestName, ResourceName]).then(buffers => {
|
|
23
|
+
if (!buffers[ManifestName]) {
|
|
24
|
+
throw new Error('AndroidManifest.xml can\'t be found.')
|
|
25
|
+
}
|
|
26
|
+
let apkInfo = this._parseManifest(buffers[ManifestName])
|
|
27
|
+
let resourceMap
|
|
28
|
+
if (!buffers[ResourceName]) {
|
|
29
|
+
resolve(apkInfo)
|
|
30
|
+
} else {
|
|
31
|
+
// parse resourceMap
|
|
32
|
+
resourceMap = this._parseResourceMap(buffers[ResourceName])
|
|
33
|
+
// update apkInfo with resourceMap
|
|
34
|
+
apkInfo = mapInfoResource(apkInfo, resourceMap)
|
|
35
|
+
|
|
36
|
+
// find icon path and parse icon
|
|
37
|
+
const iconPath = findApkIconPath(apkInfo)
|
|
38
|
+
if (iconPath) {
|
|
39
|
+
this.getEntry(iconPath).then(iconBuffer => {
|
|
40
|
+
apkInfo.icon = iconBuffer ? getBase64FromBuffer(iconBuffer) : null
|
|
41
|
+
resolve(apkInfo)
|
|
42
|
+
}).catch(e => {
|
|
43
|
+
apkInfo.icon = null
|
|
44
|
+
resolve(apkInfo)
|
|
45
|
+
console.warn('[Warning] failed to parse icon: ', e)
|
|
46
|
+
})
|
|
47
|
+
} else {
|
|
48
|
+
apkInfo.icon = null
|
|
49
|
+
resolve(apkInfo)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}).catch(e => {
|
|
53
|
+
reject(e)
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Parse manifest
|
|
59
|
+
* @param {Buffer} buffer // manifest file's buffer
|
|
60
|
+
*/
|
|
61
|
+
_parseManifest (buffer) {
|
|
62
|
+
try {
|
|
63
|
+
const parser = new ManifestXmlParser(buffer, {
|
|
64
|
+
ignore: [
|
|
65
|
+
'application.activity',
|
|
66
|
+
'application.service',
|
|
67
|
+
'application.receiver',
|
|
68
|
+
'application.provider',
|
|
69
|
+
'permission-group'
|
|
70
|
+
]
|
|
71
|
+
})
|
|
72
|
+
return parser.parse()
|
|
73
|
+
} catch (e) {
|
|
74
|
+
throw new Error('Parse AndroidManifest.xml error: ', e)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Parse resourceMap
|
|
79
|
+
* @param {Buffer} buffer // resourceMap file's buffer
|
|
80
|
+
*/
|
|
81
|
+
_parseResourceMap (buffer) {
|
|
82
|
+
try {
|
|
83
|
+
return new ResourceFinder().processResourceTable(buffer)
|
|
84
|
+
} catch (e) {
|
|
85
|
+
throw new Error('Parser resources.arsc error: ' + e)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = ApkParser
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const ApkParser = require('./apk')
|
|
2
|
+
const IpaParser = require('./ipa')
|
|
3
|
+
const supportFileTypes = ['ipa', 'apk']
|
|
4
|
+
|
|
5
|
+
class AppInfoParser {
|
|
6
|
+
/**
|
|
7
|
+
* parser for parsing .ipa or .apk file
|
|
8
|
+
* @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser
|
|
9
|
+
*/
|
|
10
|
+
constructor (file) {
|
|
11
|
+
if (!file) {
|
|
12
|
+
throw new Error('Param miss: file(file\'s path in Node, instance of File or Blob in browser).')
|
|
13
|
+
}
|
|
14
|
+
const splits = (file.name || file).split('.')
|
|
15
|
+
const fileType = splits[splits.length - 1].toLowerCase()
|
|
16
|
+
if (!supportFileTypes.includes(fileType)) {
|
|
17
|
+
throw new Error('Unsupported file type, only support .ipa or .apk file.')
|
|
18
|
+
}
|
|
19
|
+
this.file = file
|
|
20
|
+
|
|
21
|
+
switch (fileType) {
|
|
22
|
+
case 'ipa':
|
|
23
|
+
this.parser = new IpaParser(this.file)
|
|
24
|
+
break
|
|
25
|
+
case 'apk':
|
|
26
|
+
this.parser = new ApkParser(this.file)
|
|
27
|
+
break
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
parse () {
|
|
31
|
+
return this.parser.parse()
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = AppInfoParser
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const parsePlist = require('plist').parse
|
|
2
|
+
const parseBplist = require('bplist-parser').parseBuffer
|
|
3
|
+
const cgbiToPng = require('cgbi-to-png')
|
|
4
|
+
|
|
5
|
+
const Zip = require('./zip')
|
|
6
|
+
const { findIpaIconPath, getBase64FromBuffer, isBrowser } = require('./utils')
|
|
7
|
+
|
|
8
|
+
const PlistName = new RegExp('payload/[^/]+?.app/info.plist$', 'i')
|
|
9
|
+
const ProvisionName = /payload\/.+?\.app\/embedded.mobileprovision/
|
|
10
|
+
|
|
11
|
+
class IpaParser extends Zip {
|
|
12
|
+
/**
|
|
13
|
+
* parser for parsing .ipa file
|
|
14
|
+
* @param {String | File | Blob} file // file's path in Node, instance of File or Blob in Browser
|
|
15
|
+
*/
|
|
16
|
+
constructor (file) {
|
|
17
|
+
super(file)
|
|
18
|
+
if (!(this instanceof IpaParser)) {
|
|
19
|
+
return new IpaParser(file)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
parse () {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
this.getEntries([PlistName, ProvisionName]).then(buffers => {
|
|
25
|
+
if (!buffers[PlistName]) {
|
|
26
|
+
throw new Error('Info.plist can\'t be found.')
|
|
27
|
+
}
|
|
28
|
+
const plistInfo = this._parsePlist(buffers[PlistName])
|
|
29
|
+
// parse mobile provision
|
|
30
|
+
const provisionInfo = this._parseProvision(buffers[ProvisionName])
|
|
31
|
+
plistInfo.mobileProvision = provisionInfo
|
|
32
|
+
|
|
33
|
+
// find icon path and parse icon
|
|
34
|
+
const iconRegex = new RegExp(findIpaIconPath(plistInfo).toLowerCase())
|
|
35
|
+
this.getEntry(iconRegex).then(iconBuffer => {
|
|
36
|
+
try {
|
|
37
|
+
// In general, the ipa file's icon has been specially processed, should be converted
|
|
38
|
+
plistInfo.icon = iconBuffer ? getBase64FromBuffer(cgbiToPng.revert(iconBuffer)) : null
|
|
39
|
+
} catch (err) {
|
|
40
|
+
if (isBrowser()) {
|
|
41
|
+
// Normal conversion in other cases
|
|
42
|
+
plistInfo.icon = iconBuffer ? getBase64FromBuffer(window.btoa(String.fromCharCode(...iconBuffer))) : null
|
|
43
|
+
} else {
|
|
44
|
+
plistInfo.icon = null
|
|
45
|
+
console.warn('[Warning] failed to parse icon: ', err)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
resolve(plistInfo)
|
|
49
|
+
}).catch(e => {
|
|
50
|
+
reject(e)
|
|
51
|
+
})
|
|
52
|
+
}).catch(e => {
|
|
53
|
+
reject(e)
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Parse plist
|
|
59
|
+
* @param {Buffer} buffer // plist file's buffer
|
|
60
|
+
*/
|
|
61
|
+
_parsePlist (buffer) {
|
|
62
|
+
let result
|
|
63
|
+
const bufferType = buffer[0]
|
|
64
|
+
if (bufferType === 60 || bufferType === '<' || bufferType === 239) {
|
|
65
|
+
result = parsePlist(buffer.toString())
|
|
66
|
+
} else if (bufferType === 98) {
|
|
67
|
+
result = parseBplist(buffer)[0]
|
|
68
|
+
} else {
|
|
69
|
+
throw new Error('Unknown plist buffer type.')
|
|
70
|
+
}
|
|
71
|
+
return result
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* parse provision
|
|
75
|
+
* @param {Buffer} buffer // provision file's buffer
|
|
76
|
+
*/
|
|
77
|
+
_parseProvision (buffer) {
|
|
78
|
+
let info = {}
|
|
79
|
+
if (buffer) {
|
|
80
|
+
let content = buffer.toString('utf-8')
|
|
81
|
+
const firstIndex = content.indexOf('<?xml')
|
|
82
|
+
const endIndex = content.indexOf('</plist>')
|
|
83
|
+
content = content.slice(firstIndex, endIndex + 8)
|
|
84
|
+
if (content) {
|
|
85
|
+
info = parsePlist(content)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return info
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = IpaParser
|