@capgo/capacitor-updater 3.0.3
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/CHANGELOG.md +326 -0
- package/CapacitorUpdater.podspec +20 -0
- package/LICENCE +661 -0
- package/README.md +456 -0
- package/android/build.gradle +60 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +402 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +404 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +523 -0
- package/dist/esm/definitions.d.ts +132 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +36 -0
- package/dist/esm/web.js +49 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +65 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +68 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/CapacitorUpdater.swift +266 -0
- package/ios/Plugin/CapacitorUpdaterPlugin.h +10 -0
- package/ios/Plugin/CapacitorUpdaterPlugin.m +18 -0
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +307 -0
- package/ios/Plugin/Info.plist +28 -0
- package/package.json +85 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
package ee.forgr.capacitor_updater;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.content.Context;
|
|
5
|
+
import android.content.SharedPreferences;
|
|
6
|
+
import android.content.pm.PackageInfo;
|
|
7
|
+
import android.content.pm.PackageManager;
|
|
8
|
+
import android.util.Log;
|
|
9
|
+
|
|
10
|
+
import com.android.volley.AuthFailureError;
|
|
11
|
+
import com.android.volley.Request;
|
|
12
|
+
import com.android.volley.RequestQueue;
|
|
13
|
+
import com.android.volley.Response;
|
|
14
|
+
import com.android.volley.VolleyError;
|
|
15
|
+
import com.android.volley.toolbox.StringRequest;
|
|
16
|
+
import com.android.volley.toolbox.Volley;
|
|
17
|
+
|
|
18
|
+
import org.json.JSONException;
|
|
19
|
+
import org.json.JSONObject;
|
|
20
|
+
|
|
21
|
+
import java.io.BufferedInputStream;
|
|
22
|
+
import java.io.DataInputStream;
|
|
23
|
+
import java.io.DataOutputStream;
|
|
24
|
+
import java.io.File;
|
|
25
|
+
import java.io.FileInputStream;
|
|
26
|
+
import java.io.FileNotFoundException;
|
|
27
|
+
import java.io.FileOutputStream;
|
|
28
|
+
import java.io.FilenameFilter;
|
|
29
|
+
import java.io.IOException;
|
|
30
|
+
import java.io.InputStream;
|
|
31
|
+
import java.net.HttpURLConnection;
|
|
32
|
+
import java.net.URL;
|
|
33
|
+
import java.net.URLConnection;
|
|
34
|
+
import java.security.SecureRandom;
|
|
35
|
+
import java.util.HashMap;
|
|
36
|
+
import java.util.Map;
|
|
37
|
+
import java.util.zip.ZipEntry;
|
|
38
|
+
import java.util.zip.ZipInputStream;
|
|
39
|
+
import java.util.ArrayList;
|
|
40
|
+
import android.provider.Settings.Secure;
|
|
41
|
+
|
|
42
|
+
interface Callback {
|
|
43
|
+
void callback(JSONObject jsonObject);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public class CapacitorUpdater {
|
|
47
|
+
public String statsUrl = "";
|
|
48
|
+
public String appId = "";
|
|
49
|
+
public String deviceID = "";
|
|
50
|
+
private String pluginVersion = "3.0.3";
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
private FilenameFilter filter = new FilenameFilter() {
|
|
54
|
+
@Override
|
|
55
|
+
public boolean accept(File f, String name) {
|
|
56
|
+
// ignore directories generated by mac os x
|
|
57
|
+
return !name.startsWith("__MACOSX") && !name.startsWith(".") && !name.startsWith(".DS_Store");
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
private final CapacitorUpdaterPlugin plugin;
|
|
61
|
+
private String versionBuild = "";
|
|
62
|
+
private String TAG = "Capacitor-updater";
|
|
63
|
+
private Context context;
|
|
64
|
+
private String basePathHot = "versions";
|
|
65
|
+
private SharedPreferences prefs;
|
|
66
|
+
private SharedPreferences.Editor editor;
|
|
67
|
+
|
|
68
|
+
static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
69
|
+
static SecureRandom rnd = new SecureRandom();
|
|
70
|
+
|
|
71
|
+
private int calcTotalPercent(int percent, int min, int max) {
|
|
72
|
+
return (percent * (max - min)) / 100 + min;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private String randomString(int len){
|
|
76
|
+
StringBuilder sb = new StringBuilder(len);
|
|
77
|
+
for(int i = 0; i < len; i++)
|
|
78
|
+
sb.append(AB.charAt(rnd.nextInt(AB.length())));
|
|
79
|
+
return sb.toString();
|
|
80
|
+
}
|
|
81
|
+
public CapacitorUpdater (Context context) throws PackageManager.NameNotFoundException {
|
|
82
|
+
this.context = context;
|
|
83
|
+
this.plugin = new CapacitorUpdaterPlugin();
|
|
84
|
+
this.prefs = context.getSharedPreferences("CapWebViewSettings", Activity.MODE_PRIVATE);
|
|
85
|
+
this.editor = prefs.edit();
|
|
86
|
+
this.deviceID = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);
|
|
87
|
+
PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
|
88
|
+
this.versionBuild = pInfo.versionName;
|
|
89
|
+
}
|
|
90
|
+
public CapacitorUpdater (Context context, CapacitorUpdaterPlugin plugin) throws PackageManager.NameNotFoundException {
|
|
91
|
+
this.context = context;
|
|
92
|
+
this.plugin = plugin;
|
|
93
|
+
this.prefs = context.getSharedPreferences("CapWebViewSettings", Activity.MODE_PRIVATE);
|
|
94
|
+
this.editor = prefs.edit();
|
|
95
|
+
this.deviceID = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);
|
|
96
|
+
PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
|
|
97
|
+
this.versionBuild = pInfo.versionName;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private Boolean unzip(String source, String dest) {
|
|
101
|
+
File zipFile = new File(this.context.getFilesDir() + "/" + source);
|
|
102
|
+
File targetDirectory = new File(this.context.getFilesDir() + "/" + dest);
|
|
103
|
+
ZipInputStream zis = null;
|
|
104
|
+
try {
|
|
105
|
+
zis = new ZipInputStream(
|
|
106
|
+
new BufferedInputStream(new FileInputStream(zipFile)));
|
|
107
|
+
} catch (FileNotFoundException e) {
|
|
108
|
+
e.printStackTrace();
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
ZipEntry ze;
|
|
113
|
+
int count;
|
|
114
|
+
int buffLength = 8192;
|
|
115
|
+
byte[] buffer = new byte[buffLength];
|
|
116
|
+
long totalLength = zipFile.length();
|
|
117
|
+
long readedLength = buffLength;
|
|
118
|
+
int percent = 0;
|
|
119
|
+
this.plugin.notifyDownload(75);
|
|
120
|
+
while ((ze = zis.getNextEntry()) != null) {
|
|
121
|
+
File file = new File(targetDirectory, ze.getName());
|
|
122
|
+
String canonicalPath = file.getCanonicalPath();
|
|
123
|
+
String canonicalDir = (new File(String.valueOf(targetDirectory))).getCanonicalPath();
|
|
124
|
+
File dir = ze.isDirectory() ? file : file.getParentFile();
|
|
125
|
+
if (!canonicalPath.startsWith(canonicalDir)) {
|
|
126
|
+
throw new FileNotFoundException("SecurityException, Failed to ensure directory is the start path : " +
|
|
127
|
+
canonicalDir + " of " + canonicalPath);
|
|
128
|
+
}
|
|
129
|
+
if (!dir.isDirectory() && !dir.mkdirs())
|
|
130
|
+
throw new FileNotFoundException("Failed to ensure directory: " +
|
|
131
|
+
dir.getAbsolutePath());
|
|
132
|
+
if (ze.isDirectory())
|
|
133
|
+
continue;
|
|
134
|
+
FileOutputStream fout = new FileOutputStream(file);
|
|
135
|
+
try {
|
|
136
|
+
while ((count = zis.read(buffer)) != -1)
|
|
137
|
+
fout.write(buffer, 0, count);
|
|
138
|
+
} finally {
|
|
139
|
+
fout.close();
|
|
140
|
+
}
|
|
141
|
+
int newPercent = (int)((readedLength * 100) / totalLength);
|
|
142
|
+
if (totalLength > 1 && newPercent != percent) {
|
|
143
|
+
percent = newPercent;
|
|
144
|
+
this.plugin.notifyDownload(calcTotalPercent((int)percent, 75, 90));
|
|
145
|
+
}
|
|
146
|
+
readedLength += ze.getCompressedSize();
|
|
147
|
+
}
|
|
148
|
+
} catch (Exception e) {
|
|
149
|
+
Log.i(TAG, "unzip error", e);
|
|
150
|
+
return false;
|
|
151
|
+
} finally {
|
|
152
|
+
try {
|
|
153
|
+
zis.close();
|
|
154
|
+
} catch (IOException e) {
|
|
155
|
+
e.printStackTrace();
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private Boolean flattenAssets(String source, String dest) {
|
|
163
|
+
File current = new File(this.context.getFilesDir() + "/" + source);
|
|
164
|
+
if (!current.exists()) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
File fDest = new File(this.context.getFilesDir() + "/" + dest);
|
|
168
|
+
fDest.getParentFile().mkdirs();
|
|
169
|
+
String[] pathsName = current.list(filter);
|
|
170
|
+
if (pathsName == null || pathsName.length == 0) {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
if (pathsName.length == 1 && !pathsName[0].equals("index.html")) {
|
|
174
|
+
File newFlat = new File(current.getPath() + "/" + pathsName[0]);
|
|
175
|
+
newFlat.renameTo(fDest);
|
|
176
|
+
} else {
|
|
177
|
+
current.renameTo(fDest);
|
|
178
|
+
}
|
|
179
|
+
current.delete();
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private Boolean downloadFile(String url, String dest) throws JSONException {
|
|
184
|
+
try {
|
|
185
|
+
URL u = new URL(url);
|
|
186
|
+
URLConnection uc = u.openConnection();
|
|
187
|
+
InputStream is = u.openStream();
|
|
188
|
+
DataInputStream dis = new DataInputStream(is);
|
|
189
|
+
long totalLength = uc.getContentLength();
|
|
190
|
+
int buffLength = 1024;
|
|
191
|
+
byte[] buffer = new byte[buffLength];
|
|
192
|
+
int length;
|
|
193
|
+
File downFile = new File(this.context.getFilesDir() + "/" + dest);
|
|
194
|
+
downFile.getParentFile().mkdirs();
|
|
195
|
+
downFile.createNewFile();
|
|
196
|
+
FileOutputStream fos = new FileOutputStream(downFile);
|
|
197
|
+
int readedLength = buffLength;
|
|
198
|
+
int percent = 0;
|
|
199
|
+
this.plugin.notifyDownload(10);
|
|
200
|
+
while ((length = dis.read(buffer))>0) {
|
|
201
|
+
fos.write(buffer, 0, length);
|
|
202
|
+
int newPercent = (int)((readedLength * 100) / totalLength);
|
|
203
|
+
if (totalLength > 1 && newPercent != percent) {
|
|
204
|
+
percent = newPercent;
|
|
205
|
+
this.plugin.notifyDownload(calcTotalPercent(percent, 10, 70));
|
|
206
|
+
}
|
|
207
|
+
readedLength += length;
|
|
208
|
+
}
|
|
209
|
+
} catch (Exception e) {
|
|
210
|
+
Log.e(TAG, "downloadFile error", e);
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private void deleteDirectory(File file) throws IOException {
|
|
217
|
+
if (file.isDirectory()) {
|
|
218
|
+
File[] entries = file.listFiles();
|
|
219
|
+
if (entries != null) {
|
|
220
|
+
for (File entry : entries) {
|
|
221
|
+
deleteDirectory(entry);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (!file.delete()) {
|
|
226
|
+
throw new IOException("Failed to delete " + file);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
public String download(String url) {
|
|
231
|
+
try {
|
|
232
|
+
this.plugin.notifyDownload(0);
|
|
233
|
+
String folderNameZip = this.randomString(10);
|
|
234
|
+
File fileZip = new File(this.context.getFilesDir() + "/" + folderNameZip);
|
|
235
|
+
String folderNameUnZip = this.randomString(10);
|
|
236
|
+
String version = this.randomString(10);
|
|
237
|
+
String folderName = basePathHot + "/" + version;
|
|
238
|
+
this.plugin.notifyDownload(5);
|
|
239
|
+
Boolean downloaded = this.downloadFile(url, folderNameZip);
|
|
240
|
+
if(!downloaded) return "";
|
|
241
|
+
this.plugin.notifyDownload(71);
|
|
242
|
+
Boolean unzipped = this.unzip(folderNameZip, folderNameUnZip);
|
|
243
|
+
if(!unzipped) return "";
|
|
244
|
+
fileZip.delete();
|
|
245
|
+
this.plugin.notifyDownload(91);
|
|
246
|
+
Boolean flatt = this.flattenAssets(folderNameUnZip, folderName);
|
|
247
|
+
if(!flatt) return "";
|
|
248
|
+
this.plugin.notifyDownload(100);
|
|
249
|
+
return version;
|
|
250
|
+
} catch (Exception e) {
|
|
251
|
+
Log.e(TAG, "updateApp error", e);
|
|
252
|
+
return "";
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
public ArrayList<String> list() {
|
|
257
|
+
ArrayList<String> res = new ArrayList<String>();
|
|
258
|
+
File destHot = new File(this.context.getFilesDir() + "/" + basePathHot);
|
|
259
|
+
Log.i(TAG, "list File : " + destHot.getPath());
|
|
260
|
+
if (destHot.exists()) {
|
|
261
|
+
for (File i : destHot.listFiles()) {
|
|
262
|
+
res.add(i.getName());
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
Log.i(TAG, "No version available" + destHot);
|
|
266
|
+
}
|
|
267
|
+
return res;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
public Boolean delete(String version, String versionName) throws IOException {
|
|
271
|
+
File destHot = new File(this.context.getFilesDir() + "/" + basePathHot + "/" + version);
|
|
272
|
+
if (destHot.exists()) {
|
|
273
|
+
deleteDirectory(destHot);
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
Log.i(TAG, "Directory not removed: " + destHot.getPath());
|
|
277
|
+
this.sendStats("delete", versionName);
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
public Boolean set(String version, String versionName) {
|
|
282
|
+
File destHot = new File(this.context.getFilesDir() + "/" + basePathHot + "/" + version);
|
|
283
|
+
File destIndex = new File(destHot.getPath() + "/index.html");
|
|
284
|
+
if (destHot.exists() && destIndex.exists()) {
|
|
285
|
+
editor.putString("lastPathHot", destHot.getPath());
|
|
286
|
+
editor.putString("serverBasePath", destHot.getPath());
|
|
287
|
+
editor.putString("versionName", versionName);
|
|
288
|
+
editor.commit();
|
|
289
|
+
sendStats("set", versionName);
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
sendStats("set_fail", versionName);
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
public void getLatest(String url, Callback callback) {
|
|
297
|
+
String deviceID = this.deviceID;
|
|
298
|
+
String appId = this.appId;
|
|
299
|
+
String versionBuild = this.versionBuild;
|
|
300
|
+
String pluginVersion = this.pluginVersion;
|
|
301
|
+
String versionName = getVersionName().equals("") ? "builtin" : getVersionName();
|
|
302
|
+
StringRequest stringRequest = new StringRequest(Request.Method.GET, url,
|
|
303
|
+
new Response.Listener<String>() {
|
|
304
|
+
@Override
|
|
305
|
+
public void onResponse(String response) {
|
|
306
|
+
try {
|
|
307
|
+
JSONObject jsonObject = new JSONObject(response);
|
|
308
|
+
callback.callback(jsonObject);
|
|
309
|
+
} catch (JSONException e) {
|
|
310
|
+
e.printStackTrace();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}, new Response.ErrorListener() {
|
|
314
|
+
@Override
|
|
315
|
+
public void onErrorResponse(VolleyError error) {
|
|
316
|
+
Log.e(TAG, "Error getting Latest" + error);
|
|
317
|
+
}
|
|
318
|
+
}) {
|
|
319
|
+
@Override
|
|
320
|
+
public Map<String, String> getHeaders() throws AuthFailureError {
|
|
321
|
+
Map<String, String> params = new HashMap<String, String>();
|
|
322
|
+
params.put("cap_platform", "android");
|
|
323
|
+
params.put("cap_device_id", deviceID);
|
|
324
|
+
params.put("cap_app_id", appId);
|
|
325
|
+
params.put("cap_version_build", versionBuild);
|
|
326
|
+
params.put("cap_version_name", versionName);
|
|
327
|
+
params.put("cap_plugin_version", pluginVersion);
|
|
328
|
+
return params;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
RequestQueue requestQueue = Volley.newRequestQueue(this.context);
|
|
332
|
+
requestQueue.add(stringRequest);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
public String getLastPathHot() {
|
|
336
|
+
return prefs.getString("lastPathHot", "public");
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
public String getVersionName() {
|
|
340
|
+
return prefs.getString("versionName", "");
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
public void reset() {
|
|
344
|
+
String version = prefs.getString("versionName", "");
|
|
345
|
+
this.sendStats("reset", version);
|
|
346
|
+
editor.putString("lastPathHot", "public");
|
|
347
|
+
editor.putString("serverBasePath", "public");
|
|
348
|
+
editor.putString("versionName", "");
|
|
349
|
+
editor.commit();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
public void sendStats(String action, String version) {
|
|
353
|
+
if (statsUrl == "") { return; }
|
|
354
|
+
URL url;
|
|
355
|
+
JSONObject json = new JSONObject();
|
|
356
|
+
String jsonString;
|
|
357
|
+
try {
|
|
358
|
+
url = new URL(statsUrl);
|
|
359
|
+
json.put("platform", "android");
|
|
360
|
+
json.put("action", action);
|
|
361
|
+
json.put("version_name", version);
|
|
362
|
+
json.put("device_id", this.deviceID);
|
|
363
|
+
json.put("version_build", this.versionBuild);
|
|
364
|
+
json.put("plugin_version", this.pluginVersion);
|
|
365
|
+
json.put("app_id", this.appId);
|
|
366
|
+
jsonString = json.toString();
|
|
367
|
+
} catch (Exception ex) {
|
|
368
|
+
Log.e(TAG, "Error get stats", ex);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
new Thread(new Runnable(){
|
|
372
|
+
@Override
|
|
373
|
+
public void run() {
|
|
374
|
+
HttpURLConnection con = null;
|
|
375
|
+
try {
|
|
376
|
+
con = (HttpURLConnection) url.openConnection();
|
|
377
|
+
con.setRequestMethod("POST");
|
|
378
|
+
con.setRequestProperty("Content-Type", "application/json");
|
|
379
|
+
con.setRequestProperty("Accept", "application/json");
|
|
380
|
+
con.setRequestProperty("Content-Length", Integer.toString(jsonString.getBytes().length));
|
|
381
|
+
con.setDoOutput(true);
|
|
382
|
+
con.setConnectTimeout(500);
|
|
383
|
+
DataOutputStream wr = new DataOutputStream (con.getOutputStream());
|
|
384
|
+
wr.writeBytes(jsonString);
|
|
385
|
+
wr.close();
|
|
386
|
+
int responseCode = con.getResponseCode();
|
|
387
|
+
if (responseCode != 200) {
|
|
388
|
+
Log.e(TAG, "Stats error responseCode: " + responseCode);
|
|
389
|
+
} else {
|
|
390
|
+
Log.i(TAG, "Stats send for \"" + action + "\", version " + version);
|
|
391
|
+
}
|
|
392
|
+
} catch (Exception ex) {
|
|
393
|
+
Log.e(TAG, "Error post stats", ex);
|
|
394
|
+
} finally {
|
|
395
|
+
if (con != null) {
|
|
396
|
+
con.disconnect();
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}).start();
|
|
401
|
+
}
|
|
402
|
+
}
|