updateflow 1.0.0

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/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # UpdateFlow
2
+
3
+ > **Live OTA updates for Ionic Capacitor apps — No App Store review needed.**
4
+
5
+ Update your app's HTML, CSS, JS — instantly. Without waiting for App Store or Play Store approval.
6
+
7
+ [![npm](https://img.shields.io/npm/v/updateflow.svg)](https://www.npmjs.com/package/updateflow)
8
+ [![Platform](https://img.shields.io/badge/platform-iOS%20%7C%20Android-blue.svg)](https://updateflow.in)
9
+
10
+ ---
11
+
12
+ ## How it works
13
+
14
+ ```
15
+ You upload new zip → User opens app → Update downloads silently
16
+ → User restarts app → New version active ✅
17
+ ```
18
+
19
+ ---
20
+
21
+ ## Quick Start
22
+
23
+ ### 1. Install
24
+
25
+ ```bash
26
+ npm install updateflow
27
+ npm install cordova-plugin-zip
28
+ npx cap sync
29
+ ```
30
+
31
+ ### 2. iOS setup (one command)
32
+
33
+ ```bash
34
+ node node_modules/updateflow/scripts/ios-setup.js
35
+ ```
36
+
37
+ Then in Xcode: `Product → Clean Build Folder (Cmd+Shift+K)`
38
+
39
+ ### 3. Android setup (one command)
40
+
41
+ ```bash
42
+ node node_modules/updateflow/scripts/android-setup.js
43
+ ionic build && npx cap sync android
44
+ ```
45
+
46
+ ### 4. Add to your Angular app
47
+
48
+ **`app.module.ts`:**
49
+ ```typescript
50
+ import { HttpClientModule } from '@angular/common/http';
51
+
52
+ @NgModule({
53
+ imports: [
54
+ HttpClientModule,
55
+ // ...
56
+ ]
57
+ })
58
+ export class AppModule {}
59
+ ```
60
+
61
+ **`app.component.ts`:**
62
+ ```typescript
63
+ import { UpdateFlowService } from 'updateflow';
64
+
65
+ @Component({ ... })
66
+ export class AppComponent {
67
+
68
+ constructor(private updateFlow: UpdateFlowService) {}
69
+
70
+ async ngOnInit() {
71
+ await this.updateFlow.init({
72
+ apiUrl: 'https://YOUR_DASHBOARD/check_update.php',
73
+ apiKey: 'YOUR_API_KEY', // Get from UpdateFlow dashboard
74
+ debug: false
75
+ });
76
+ }
77
+ }
78
+ ```
79
+
80
+ ---
81
+
82
+ ## Releasing an Update
83
+
84
+ ```bash
85
+ # 1. Build your app
86
+ ionic build
87
+
88
+ # 2. Create zip from www folder
89
+ cd www && zip -r ../update_2.0.zip . && cd ..
90
+
91
+ # 3. Upload zip to your UpdateFlow dashboard
92
+ # 4. Users will get the update on next app open
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Configuration
98
+
99
+ | Option | Type | Required | Default | Description |
100
+ |--------|------|----------|---------|-------------|
101
+ | `apiUrl` | `string` | ✅ | — | Your backend URL |
102
+ | `apiKey` | `string` | ✅ | — | API key from dashboard |
103
+ | `showAlerts` | `boolean` | ❌ | `true` | Show update alerts |
104
+ | `debug` | `boolean` | ❌ | `false` | Enable console logs |
105
+
106
+ ---
107
+
108
+ ## API
109
+
110
+ ```typescript
111
+ // Check for updates manually
112
+ this.updateFlow.checkNow();
113
+
114
+ // Get current version
115
+ const version = this.updateFlow.getVersion();
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Platform Support
121
+
122
+ | Platform | Status |
123
+ |----------|--------|
124
+ | iOS | ✅ Supported |
125
+ | Android | ✅ Supported |
126
+
127
+ ---
128
+
129
+ ## Links
130
+
131
+ - 🌐 Dashboard: [updateflow.in](https://updateflow.in)
132
+ - 📖 Docs: [updateflow.in/docs](https://updateflow.in/docs)
133
+ - 💬 Support: admin@codecartel.co.in
134
+ - 🐛 Issues: [GitHub Issues](https://github.com/codecartel-in/updateflow/issues)
135
+
136
+ ---
137
+
138
+ ## License
139
+
140
+ MIT © [CodeCartel](https://codecartel.co.in)
@@ -0,0 +1,91 @@
1
+ package io.ionic.starter;
2
+
3
+ import android.content.SharedPreferences;
4
+ import android.os.Bundle;
5
+ import android.util.Log;
6
+
7
+ import com.getcapacitor.BridgeActivity;
8
+
9
+ import java.io.File;
10
+
11
+ public class MainActivity extends BridgeActivity {
12
+
13
+ private static final String TAG = "OTA";
14
+
15
+ // FIX: Yeh keys Angular service se match karni chahiye
16
+ private static final String KEY_VERSION = "app_current_version"; // ← Angular service wali key
17
+ private static final String KEY_PATH = "local_ota_path";
18
+
19
+ @Override
20
+ public void onCreate(Bundle savedInstanceState) {
21
+ super.onCreate(savedInstanceState);
22
+ applyOtaUpdateIfAvailable();
23
+ }
24
+
25
+ @Override
26
+ public void onResume() {
27
+ super.onResume();
28
+ applyOtaUpdateIfAvailable();
29
+ }
30
+
31
+ private void applyOtaUpdateIfAvailable() {
32
+ try {
33
+ // Capacitor Preferences "CapacitorStorage" mein save karta hai
34
+ SharedPreferences prefs = getSharedPreferences("CapacitorStorage", MODE_PRIVATE);
35
+
36
+ // FIX: Key name match karo Angular service se
37
+ String otaVersion = prefs.getString(KEY_VERSION, null);
38
+
39
+ if (otaVersion == null) {
40
+ Log.i(TAG, "No OTA version saved — standard boot.");
41
+ return;
42
+ }
43
+
44
+ Log.i(TAG, "OTA version found: " + otaVersion);
45
+
46
+ // Path build karo
47
+ String otaFolderPath = getFilesDir().getAbsolutePath() + "/updates/" + otaVersion;
48
+ File indexFile = new File(otaFolderPath + "/index.html");
49
+ File wwwIndexFile = new File(otaFolderPath + "/www/index.html");
50
+
51
+ Log.i(TAG, "Checking path: " + otaFolderPath);
52
+
53
+ if (wwwIndexFile.exists()) {
54
+ // www/index.html mila
55
+ otaFolderPath = otaFolderPath + "/www";
56
+ Log.i(TAG, "Found www/index.html");
57
+ } else if (indexFile.exists()) {
58
+ // root index.html mila
59
+ Log.i(TAG, "Found root index.html");
60
+ } else {
61
+ Log.w(TAG, "index.html nahi mila — clearing OTA, standard boot.");
62
+ clearOtaPreferences();
63
+ return;
64
+ }
65
+
66
+ setCapacitorServerPath(otaFolderPath);
67
+ Log.i(TAG, "OTA ACTIVE: v" + otaVersion + " -> " + otaFolderPath);
68
+
69
+ } catch (Exception e) {
70
+ Log.e(TAG, "OTA error: " + e.getMessage());
71
+ }
72
+ }
73
+
74
+ private void setCapacitorServerPath(String path) {
75
+ getBridge().setServerBasePath(path);
76
+
77
+ getSharedPreferences("CapWebViewSettings", MODE_PRIVATE)
78
+ .edit().putString("serverBasePath", path).apply();
79
+ }
80
+
81
+ private void clearOtaPreferences() {
82
+ getSharedPreferences("CapacitorStorage", MODE_PRIVATE)
83
+ .edit()
84
+ .remove(KEY_VERSION)
85
+ .remove(KEY_PATH)
86
+ .apply();
87
+
88
+ getSharedPreferences("CapWebViewSettings", MODE_PRIVATE)
89
+ .edit().remove("serverBasePath").apply();
90
+ }
91
+ }
@@ -0,0 +1,3 @@
1
+ export { UpdateFlowService } from './updateflow.service';
2
+ export type { UpdateFlowConfig, UpdateInfo } from './updateflow.service';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UpdateFlowService = void 0;
4
+ var updateflow_service_1 = require("./updateflow.service");
5
+ Object.defineProperty(exports, "UpdateFlowService", { enumerable: true, get: function () { return updateflow_service_1.UpdateFlowService; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,2DAAyD;AAAhD,uHAAA,iBAAiB,OAAA"}
@@ -0,0 +1,46 @@
1
+ import { HttpClient } from '@angular/common/http';
2
+ import { Platform } from '@ionic/angular';
3
+ export interface UpdateFlowConfig {
4
+ /** UpdateFlow dashboard se API URL — Required */
5
+ apiUrl: string;
6
+ /** Dashboard se milne wali unique API key — Required */
7
+ apiKey: string;
8
+ /** Update ke baad alert dikhaye? Default: true */
9
+ showAlerts?: boolean;
10
+ /** Debug logs? Default: false */
11
+ debug?: boolean;
12
+ }
13
+ export interface UpdateInfo {
14
+ update_available: boolean;
15
+ new_version?: string;
16
+ url?: string;
17
+ is_critical?: boolean;
18
+ release_notes?: string;
19
+ }
20
+ export declare class UpdateFlowService {
21
+ private http;
22
+ private platform;
23
+ currentVersion: string;
24
+ private config;
25
+ private readonly KEY_VERSION;
26
+ private readonly KEY_PATH;
27
+ constructor(http: HttpClient, platform: Platform);
28
+ /**
29
+ * App start par call karo — app.component.ts ke ngOnInit mein
30
+ *
31
+ * @example
32
+ * await this.updateFlow.init({
33
+ * apiUrl: 'https://YOUR-DASHBOARD/check_update.php',
34
+ * apiKey: 'YOUR_API_KEY'
35
+ * });
36
+ */
37
+ init(config: UpdateFlowConfig): Promise<void>;
38
+ private checkForUpdate;
39
+ private downloadAndApply;
40
+ /** Current version return karta hai */
41
+ getVersion(): string;
42
+ /** Manually update check trigger karo */
43
+ checkNow(): void;
44
+ private log;
45
+ }
46
+ //# sourceMappingURL=updateflow.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updateflow.service.d.ts","sourceRoot":"","sources":["../src/updateflow.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGlD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAK1C,MAAM,WAAW,gBAAgB;IAC/B,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iCAAiC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBACa,iBAAiB;IAU1B,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,QAAQ;IATX,cAAc,SAAM;IAC3B,OAAO,CAAC,MAAM,CAAoB;IAGlC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAyB;IACrD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuB;gBAGtC,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,QAAQ;IAG5B;;;;;;;;OAQG;IACU,IAAI,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IA0B1D,OAAO,CAAC,cAAc;YAwBR,gBAAgB;IAuD9B,uCAAuC;IAChC,UAAU,IAAI,MAAM;IAE3B,yCAAyC;IAClC,QAAQ,IAAI,IAAI;IAIvB,OAAO,CAAC,GAAG;CAGZ"}
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var _a;
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.UpdateFlowService = void 0;
14
+ const core_1 = require("@angular/core");
15
+ const http_1 = require("@angular/common/http");
16
+ const filesystem_1 = require("@capacitor/filesystem");
17
+ const preferences_1 = require("@capacitor/preferences");
18
+ const angular_1 = require("@ionic/angular");
19
+ const app_1 = require("@capacitor/app");
20
+ let UpdateFlowService = class UpdateFlowService {
21
+ constructor(http, platform) {
22
+ this.http = http;
23
+ this.platform = platform;
24
+ this.currentVersion = '';
25
+ // iOS AppDelegate + Android MainActivity se match karne wali keys
26
+ this.KEY_VERSION = 'app_current_version';
27
+ this.KEY_PATH = 'local_ota_path';
28
+ }
29
+ /**
30
+ * App start par call karo — app.component.ts ke ngOnInit mein
31
+ *
32
+ * @example
33
+ * await this.updateFlow.init({
34
+ * apiUrl: 'https://YOUR-DASHBOARD/check_update.php',
35
+ * apiKey: 'YOUR_API_KEY'
36
+ * });
37
+ */
38
+ async init(config) {
39
+ this.config = Object.assign({ showAlerts: true, debug: false }, config);
40
+ if (!this.platform.is('hybrid')) {
41
+ this.log('Browser mode — UpdateFlow skipped.');
42
+ return;
43
+ }
44
+ if (!config.apiKey || !config.apiUrl) {
45
+ console.error('[UpdateFlow] apiUrl aur apiKey dono required hain!');
46
+ return;
47
+ }
48
+ try {
49
+ const { value } = await preferences_1.Preferences.get({ key: this.KEY_VERSION });
50
+ const appInfo = await app_1.App.getInfo();
51
+ this.currentVersion = value || appInfo.version;
52
+ this.log('Version: ' + this.currentVersion);
53
+ this.checkForUpdate();
54
+ }
55
+ catch (e) {
56
+ console.error('[UpdateFlow] Init error:', e);
57
+ }
58
+ }
59
+ checkForUpdate() {
60
+ const url = `${this.config.apiUrl}?current_version=${this.currentVersion}&api_key=${this.config.apiKey}`;
61
+ this.http.get(url).subscribe({
62
+ next: async (res) => {
63
+ var _a;
64
+ if ((res === null || res === void 0 ? void 0 : res.update_available) && res.new_version && res.url) {
65
+ this.log('New version: ' + res.new_version);
66
+ await this.downloadAndApply(res.url, res.new_version, (_a = res.is_critical) !== null && _a !== void 0 ? _a : false);
67
+ }
68
+ else {
69
+ this.log('Already up to date.');
70
+ }
71
+ },
72
+ error: (err) => {
73
+ if (err.status === 401) {
74
+ console.error('[UpdateFlow] Invalid API key');
75
+ }
76
+ else if (err.status === 403) {
77
+ console.error('[UpdateFlow] Account suspended');
78
+ }
79
+ else {
80
+ console.error('[UpdateFlow] Server error:', err.message);
81
+ }
82
+ }
83
+ });
84
+ }
85
+ async downloadAndApply(zipUrl, newVersion, isCritical) {
86
+ try {
87
+ this.log('Downloading v' + newVersion + '...');
88
+ const download = await filesystem_1.Filesystem.downloadFile({
89
+ url: zipUrl,
90
+ path: `updateflow_${newVersion}.zip`,
91
+ directory: filesystem_1.Directory.Data
92
+ });
93
+ const targetUri = await filesystem_1.Filesystem.getUri({
94
+ directory: filesystem_1.Directory.Data,
95
+ path: `updates/${newVersion}`
96
+ });
97
+ this.log('Extracting...');
98
+ zip.unzip(download.path, targetUri.uri, async (result) => {
99
+ if (result === 0) {
100
+ this.log('Done! v' + newVersion + ' ready.');
101
+ await preferences_1.Preferences.set({ key: this.KEY_VERSION, value: newVersion });
102
+ await preferences_1.Preferences.set({
103
+ key: this.KEY_PATH,
104
+ value: targetUri.uri.replace('file://', '')
105
+ });
106
+ this.currentVersion = newVersion;
107
+ await filesystem_1.Filesystem.deleteFile({
108
+ path: `updateflow_${newVersion}.zip`,
109
+ directory: filesystem_1.Directory.Data
110
+ }).catch(() => { });
111
+ if (this.config.showAlerts) {
112
+ alert(isCritical
113
+ ? 'Zaruri update install hua! App band karke dobara kholen.'
114
+ : 'Naya update install hua. App restart karein.');
115
+ }
116
+ }
117
+ else {
118
+ console.error('[UpdateFlow] Extraction failed:', result);
119
+ }
120
+ });
121
+ }
122
+ catch (e) {
123
+ console.error('[UpdateFlow] Error:', (e === null || e === void 0 ? void 0 : e.message) || e);
124
+ }
125
+ }
126
+ /** Current version return karta hai */
127
+ getVersion() { return this.currentVersion; }
128
+ /** Manually update check trigger karo */
129
+ checkNow() {
130
+ if (this.currentVersion)
131
+ this.checkForUpdate();
132
+ }
133
+ log(msg) {
134
+ var _a;
135
+ if ((_a = this.config) === null || _a === void 0 ? void 0 : _a.debug)
136
+ console.log('[UpdateFlow]', msg);
137
+ }
138
+ };
139
+ exports.UpdateFlowService = UpdateFlowService;
140
+ exports.UpdateFlowService = UpdateFlowService = __decorate([
141
+ (0, core_1.Injectable)({ providedIn: 'root' }),
142
+ __metadata("design:paramtypes", [typeof (_a = typeof http_1.HttpClient !== "undefined" && http_1.HttpClient) === "function" ? _a : Object, angular_1.Platform])
143
+ ], UpdateFlowService);
144
+ //# sourceMappingURL=updateflow.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updateflow.service.js","sourceRoot":"","sources":["../src/updateflow.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ import UIKit
2
+ import Capacitor
3
+
4
+ @UIApplicationMain
5
+ class AppDelegate: UIResponder, UIApplicationDelegate {
6
+
7
+ var window: UIWindow?
8
+
9
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
10
+
11
+ let defaults = UserDefaults.standard
12
+ let fileManager = FileManager.default
13
+
14
+ guard let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
15
+ return true
16
+ }
17
+
18
+ let updatesFolderURL = documentsURL.appendingPathComponent("updates", isDirectory: true)
19
+
20
+ var bestPath: String? = nil
21
+ var maxVer: Double = 0.0
22
+
23
+ if fileManager.fileExists(atPath: updatesFolderURL.path) {
24
+ do {
25
+ let folders = try fileManager.contentsOfDirectory(
26
+ at: updatesFolderURL,
27
+ includingPropertiesForKeys: nil,
28
+ options: [.skipsHiddenFiles]
29
+ )
30
+
31
+ for folderURL in folders {
32
+ let versionStr = folderURL.lastPathComponent
33
+ .lowercased()
34
+ .replacingOccurrences(of: "v", with: "")
35
+
36
+ guard let vDouble = Double(versionStr), vDouble >= maxVer else { continue }
37
+
38
+ let wwwIndex = folderURL.appendingPathComponent("www/index.html")
39
+ let rootIndex = folderURL.appendingPathComponent("index.html")
40
+
41
+ if fileManager.fileExists(atPath: wwwIndex.path) {
42
+ maxVer = vDouble
43
+ bestPath = folderURL.appendingPathComponent("www").path
44
+ } else if fileManager.fileExists(atPath: rootIndex.path) {
45
+ maxVer = vDouble
46
+ bestPath = folderURL.path
47
+ }
48
+ }
49
+ } catch {
50
+ print("[OTA] Error scanning updates folder: \(error)")
51
+ }
52
+ }
53
+
54
+ if let target = bestPath {
55
+ defaults.set(target, forKey: "serverBasePath")
56
+ defaults.synchronize()
57
+ print("[OTA] Active: v\(maxVer) from \(target)")
58
+ } else {
59
+ defaults.removeObject(forKey: "serverBasePath")
60
+ defaults.synchronize()
61
+ print("[OTA] Standard boot — no update found.")
62
+ }
63
+
64
+ return true
65
+ }
66
+
67
+ func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
68
+ return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
69
+ }
70
+
71
+ func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
72
+ return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
73
+ }
74
+
75
+ func applicationWillResignActive(_ application: UIApplication) { }
76
+ func applicationDidEnterBackground(_ application: UIApplication) { }
77
+ func applicationWillEnterForeground(_ application: UIApplication) { }
78
+ func applicationDidBecomeActive(_ application: UIApplication) { }
79
+ func applicationWillTerminate(_ application: UIApplication) { }
80
+ }
@@ -0,0 +1,19 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14111" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
3
+ <device id="retina4_7" orientation="portrait">
4
+ <adaptation id="fullscreen"/>
5
+ </device>
6
+ <dependencies>
7
+ <deployment identifier="iOS"/>
8
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
9
+ </dependencies>
10
+ <scenes>
11
+ <!--OTA Bridge View Controller-->
12
+ <scene sceneID="tne-QT-ifu">
13
+ <objects>
14
+ <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="App" sceneMemberID="viewController"/>
15
+ <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
16
+ </objects>
17
+ </scene>
18
+ </scenes>
19
+ </document>
@@ -0,0 +1,24 @@
1
+ import UIKit
2
+ import Capacitor
3
+
4
+ @objc(ViewController)
5
+ class ViewController: CAPBridgeViewController {
6
+
7
+ override func viewDidLoad() {
8
+ super.viewDidLoad()
9
+
10
+ if let otaPath = UserDefaults.standard.string(forKey: "serverBasePath") {
11
+ let indexPath = otaPath + "/index.html"
12
+
13
+ if FileManager.default.fileExists(atPath: indexPath) {
14
+ self.setServerBasePath(path: otaPath)
15
+ print("[OTA] ViewController: Path applied -> \(otaPath)")
16
+ } else {
17
+ UserDefaults.standard.removeObject(forKey: "serverBasePath")
18
+ print("[OTA] ViewController: Path invalid, standard boot.")
19
+ }
20
+ } else {
21
+ print("[OTA] ViewController: Standard boot.")
22
+ }
23
+ }
24
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "updateflow",
3
+ "version": "1.0.0",
4
+ "description": "Live OTA updates for Ionic Capacitor apps — iOS & Android. No App Store needed.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist/",
9
+ "ios-setup/",
10
+ "android-setup/",
11
+ "scripts/",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc --project tsconfig.json",
16
+ "postinstall": "node scripts/postinstall.js"
17
+ },
18
+ "keywords": [
19
+ "ionic",
20
+ "capacitor",
21
+ "ota",
22
+ "live-update",
23
+ "ios",
24
+ "android",
25
+ "hot-update",
26
+ "app-update",
27
+ "over-the-air",
28
+ "updateflow"
29
+ ],
30
+ "author": "CodeCartel <admin@codecartel.co.in> (https://codecartel.co.in)",
31
+ "license": "MIT",
32
+ "homepage": "https://updateflow.in",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/codecartel-in/updateflow"
36
+ },
37
+ "bugs": {
38
+ "url": "https://github.com/codecartel-in/updateflow/issues",
39
+ "email": "admin@codecartel.co.in"
40
+ },
41
+ "peerDependencies": {
42
+ "@angular/common": ">=12.0.0",
43
+ "@angular/core": ">=12.0.0",
44
+ "@capacitor/app": ">=1.0.0",
45
+ "@capacitor/core": ">=3.0.0",
46
+ "@capacitor/filesystem": ">=1.0.0",
47
+ "@capacitor/preferences": ">=1.0.0",
48
+ "@ionic/angular": ">=6.0.0"
49
+ },
50
+ "devDependencies": {
51
+ "typescript": "^5.9.3"
52
+ }
53
+ }
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const GREEN = '\x1b[32m';
7
+ const YELLOW = '\x1b[33m';
8
+ const RED = '\x1b[31m';
9
+ const CYAN = '\x1b[36m';
10
+ const RESET = '\x1b[0m';
11
+ const log = (c, i, m) => console.log(`${c}${i} ${m}${RESET}`);
12
+
13
+ const PKG_ANDROID = path.join(__dirname, '..', 'android-setup');
14
+ const PROJ_ROOT = process.cwd();
15
+
16
+ function findMainActivity() {
17
+ const base = path.join(PROJ_ROOT, 'android', 'app', 'src', 'main', 'java');
18
+ if (!fs.existsSync(base)) return null;
19
+
20
+ function walk(dir, depth = 0) {
21
+ if (depth > 5) return null;
22
+ try {
23
+ const items = fs.readdirSync(dir);
24
+ for (const item of items) {
25
+ const full = path.join(dir, item);
26
+ if (fs.statSync(full).isDirectory()) {
27
+ if (fs.existsSync(path.join(full, 'MainActivity.java'))) {
28
+ return path.join(full, 'MainActivity.java');
29
+ }
30
+ const found = walk(full, depth + 1);
31
+ if (found) return found;
32
+ }
33
+ }
34
+ } catch(e) {}
35
+ return null;
36
+ }
37
+ return walk(base);
38
+ }
39
+
40
+ console.log('\n' + CYAN + '⚡ UpdateFlow — Android Setup' + RESET);
41
+ console.log('─'.repeat(40));
42
+
43
+ const mainActivityPath = findMainActivity();
44
+ if (!mainActivityPath) {
45
+ log(RED, '✗', 'MainActivity.java nahi mili.');
46
+ log(YELLOW, '→', 'Run: ionic cap add android');
47
+ process.exit(1);
48
+ }
49
+
50
+ log(GREEN, '✓', 'Found: ' + mainActivityPath);
51
+
52
+ // Existing file se package naam nikalo
53
+ let packageName = '';
54
+ const existing = fs.readFileSync(mainActivityPath, 'utf8');
55
+ const match = existing.match(/^package\s+([\w.]+);/m);
56
+ if (match) {
57
+ packageName = match[1];
58
+ log(GREEN, '✓', 'Package naam: ' + packageName);
59
+ }
60
+
61
+ // Backup
62
+ fs.copyFileSync(mainActivityPath, mainActivityPath + '.backup');
63
+ log(YELLOW, '⚠', 'Backup: MainActivity.java.backup');
64
+
65
+ // New file copy karo + package naam replace karo
66
+ let newContent = fs.readFileSync(path.join(PKG_ANDROID, 'MainActivity.java'), 'utf8');
67
+ if (packageName) {
68
+ newContent = newContent.replace(/^package\s+[\w.]+;/m, `package ${packageName};`);
69
+ }
70
+
71
+ fs.writeFileSync(mainActivityPath, newContent, 'utf8');
72
+ log(GREEN, '✓', 'MainActivity.java updated!');
73
+
74
+ console.log('─'.repeat(40));
75
+ log(GREEN, '✅', 'Android setup complete!');
76
+ console.log('\n Next steps:');
77
+ console.log(' ionic build');
78
+ console.log(' npx cap sync android');
79
+ console.log(' npx cap open android → Run\n');
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const GREEN = '\x1b[32m';
7
+ const YELLOW = '\x1b[33m';
8
+ const RED = '\x1b[31m';
9
+ const CYAN = '\x1b[36m';
10
+ const RESET = '\x1b[0m';
11
+ const log = (c, i, m) => console.log(`${c}${i} ${m}${RESET}`);
12
+
13
+ const PKG_IOS = path.join(__dirname, '..', 'ios-setup');
14
+ const PROJ_ROOT = process.cwd();
15
+
16
+ function findIosFolder() {
17
+ const paths = [
18
+ path.join(PROJ_ROOT, 'ios', 'App', 'App'),
19
+ path.join(PROJ_ROOT, 'ios', 'app', 'app'),
20
+ ];
21
+ return paths.find(p => fs.existsSync(p)) || null;
22
+ }
23
+
24
+ console.log('\n' + CYAN + '⚡ UpdateFlow — iOS Setup' + RESET);
25
+ console.log('─'.repeat(40));
26
+
27
+ const iosFolder = findIosFolder();
28
+ if (!iosFolder) {
29
+ log(RED, '✗', 'ios/App/App folder nahi mila.');
30
+ log(YELLOW, '→', 'Run: ionic cap add ios');
31
+ process.exit(1);
32
+ }
33
+
34
+ log(GREEN, '✓', 'iOS folder found!');
35
+
36
+ const files = [
37
+ { src: 'AppDelegate.swift', dest: 'AppDelegate.swift' },
38
+ { src: 'ViewController.swift', dest: 'ViewController.swift' },
39
+ { src: 'Main.storyboard', dest: 'Base.lproj/Main.storyboard' },
40
+ ];
41
+
42
+ let ok = true;
43
+ for (const f of files) {
44
+ const src = path.join(PKG_IOS, f.src);
45
+ const dest = path.join(iosFolder, f.dest);
46
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
47
+
48
+ if (fs.existsSync(dest)) {
49
+ fs.copyFileSync(dest, dest + '.backup');
50
+ log(YELLOW, '⚠', 'Backup created: ' + f.dest + '.backup');
51
+ }
52
+
53
+ try {
54
+ fs.copyFileSync(src, dest);
55
+ log(GREEN, '✓', 'Copied: ' + f.dest);
56
+ } catch (e) {
57
+ log(RED, '✗', 'Failed: ' + f.dest);
58
+ ok = false;
59
+ }
60
+ }
61
+
62
+ console.log('─'.repeat(40));
63
+ if (ok) {
64
+ log(GREEN, '✅', 'iOS setup complete!');
65
+ console.log('\n Xcode mein:');
66
+ console.log(' Product → Clean Build Folder (Cmd+Shift+K)');
67
+ console.log(' Build karo aur iPhone par install karo\n');
68
+ } else {
69
+ log(RED, '✗', 'Kuch files copy nahi huin. Manually copy karo ios-setup/ se.');
70
+ }
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+
3
+ const CYAN = '\x1b[36m';
4
+ const GREEN = '\x1b[32m';
5
+ const YELLOW = '\x1b[33m';
6
+ const RESET = '\x1b[0m';
7
+
8
+ console.log('\n' + CYAN + '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' + RESET);
9
+ console.log(CYAN + ' ⚡ UpdateFlow installed successfully!' + RESET);
10
+ console.log(CYAN + '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' + RESET);
11
+ console.log('');
12
+ console.log(YELLOW + ' Next steps:' + RESET);
13
+ console.log('');
14
+ console.log(' 1️⃣ iOS setup:');
15
+ console.log(GREEN + ' node node_modules/updateflow/scripts/ios-setup.js' + RESET);
16
+ console.log('');
17
+ console.log(' 2️⃣ Android setup:');
18
+ console.log(GREEN + ' node node_modules/updateflow/scripts/android-setup.js' + RESET);
19
+ console.log('');
20
+ console.log(' 3️⃣ app.component.ts mein add karo:');
21
+ console.log(GREEN + ' import { UpdateFlowService } from \'updateflow\';' + RESET);
22
+ console.log('');
23
+ console.log(' 📖 Docs: https://updateflow.in/docs');
24
+ console.log(' 💬 Support: admin@codecartel.co.in');
25
+ console.log('');
26
+ console.log(CYAN + '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━' + RESET);
27
+ console.log('');