capacitor-sso-webview 0.0.1

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.
@@ -0,0 +1,17 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'CapacitorSsoWebview'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.license = package['license']
10
+ s.homepage = package['repository']['url']
11
+ s.author = package['author']
12
+ s.source = { :git => package['repository']['url'], :tag => s.version.to_s }
13
+ s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
14
+ s.ios.deployment_target = '15.0'
15
+ s.dependency 'Capacitor'
16
+ s.swift_version = '5.1'
17
+ end
package/Package.swift ADDED
@@ -0,0 +1,28 @@
1
+ // swift-tools-version: 5.9
2
+ import PackageDescription
3
+
4
+ let package = Package(
5
+ name: "CapacitorSsoWebview",
6
+ platforms: [.iOS(.v15)],
7
+ products: [
8
+ .library(
9
+ name: "CapacitorSsoWebview",
10
+ targets: ["SSOWebViewPlugin"])
11
+ ],
12
+ dependencies: [
13
+ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0")
14
+ ],
15
+ targets: [
16
+ .target(
17
+ name: "SSOWebViewPlugin",
18
+ dependencies: [
19
+ .product(name: "Capacitor", package: "capacitor-swift-pm"),
20
+ .product(name: "Cordova", package: "capacitor-swift-pm")
21
+ ],
22
+ path: "ios/Sources/SSOWebViewPlugin"),
23
+ .testTarget(
24
+ name: "SSOWebViewPluginTests",
25
+ dependencies: ["SSOWebViewPlugin"],
26
+ path: "ios/Tests/SSOWebViewPluginTests")
27
+ ]
28
+ )
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # capacitor-sso-webview
2
+
3
+ SSO WebView with URL intercept
4
+
5
+ ## Install
6
+
7
+ To use npm
8
+
9
+ ```bash
10
+ npm install capacitor-sso-webview
11
+ ````
12
+
13
+ To use yarn
14
+
15
+ ```bash
16
+ yarn add capacitor-sso-webview
17
+ ```
18
+
19
+ Sync native files
20
+
21
+ ```bash
22
+ npx cap sync
23
+ ```
24
+
25
+ ## API
26
+
27
+ <docgen-index>
28
+
29
+ * [`openSSO(...)`](#opensso)
30
+ * [`close()`](#close)
31
+ * [`addListener('ssoRedirect', ...)`](#addlistenerssoredirect-)
32
+ * [`addListener('ssoCancelled', ...)`](#addlistenerssocancelled-)
33
+ * [`removeAllListeners()`](#removealllisteners)
34
+ * [Interfaces](#interfaces)
35
+
36
+ </docgen-index>
37
+
38
+ <docgen-api>
39
+ <!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
40
+
41
+ ### openSSO(...)
42
+
43
+ ```typescript
44
+ openSSO(options: SSOOpenOptions) => Promise<void>
45
+ ```
46
+
47
+ Buka WebView SSO. Navigasi ke redirectHost akan di-intercept
48
+ dan DIBATALKAN sehingga authorization code tidak dikonsumsi.
49
+
50
+ | Param | Type |
51
+ | ------------- | --------------------------------------------------------- |
52
+ | **`options`** | <code><a href="#ssoopenoptions">SSOOpenOptions</a></code> |
53
+
54
+ --------------------
55
+
56
+
57
+ ### close()
58
+
59
+ ```typescript
60
+ close() => Promise<void>
61
+ ```
62
+
63
+ Tutup WebView secara manual
64
+
65
+ --------------------
66
+
67
+
68
+ ### addListener('ssoRedirect', ...)
69
+
70
+ ```typescript
71
+ addListener(eventName: 'ssoRedirect', listenerFunc: (event: SSORedirectEvent) => void) => Promise<PluginListenerHandle>
72
+ ```
73
+
74
+ Fired saat redirect ter-intercept — code masih valid
75
+
76
+ | Param | Type |
77
+ | ------------------ | --------------------------------------------------------------------------------- |
78
+ | **`eventName`** | <code>'ssoRedirect'</code> |
79
+ | **`listenerFunc`** | <code>(event: <a href="#ssoredirectevent">SSORedirectEvent</a>) =&gt; void</code> |
80
+
81
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
82
+
83
+ --------------------
84
+
85
+
86
+ ### addListener('ssoCancelled', ...)
87
+
88
+ ```typescript
89
+ addListener(eventName: 'ssoCancelled', listenerFunc: () => void) => Promise<PluginListenerHandle>
90
+ ```
91
+
92
+ Fired saat user menutup WebView tanpa menyelesaikan login
93
+
94
+ | Param | Type |
95
+ | ------------------ | --------------------------- |
96
+ | **`eventName`** | <code>'ssoCancelled'</code> |
97
+ | **`listenerFunc`** | <code>() =&gt; void</code> |
98
+
99
+ **Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>
100
+
101
+ --------------------
102
+
103
+
104
+ ### removeAllListeners()
105
+
106
+ ```typescript
107
+ removeAllListeners() => Promise<void>
108
+ ```
109
+
110
+ --------------------
111
+
112
+
113
+ ### Interfaces
114
+
115
+
116
+ #### SSOOpenOptions
117
+
118
+ | Prop | Type | Description |
119
+ | -------------------------- | ------------------- | ---------------------------------------------------------------- |
120
+ | **`url`** | <code>string</code> | URL SSO authorize endpoint |
121
+ | **`redirectHost`** | <code>string</code> | Host yang menandakan redirect, mis. "tdirectmbl.telkomsel.co.id" |
122
+ | **`redirectPathContains`** | <code>string</code> | Substring path redirect, mis. "mobilelogon" |
123
+ | **`title`** | <code>string</code> | Judul header WebView |
124
+
125
+
126
+ #### PluginListenerHandle
127
+
128
+ | Prop | Type |
129
+ | ------------ | ----------------------------------------- |
130
+ | **`remove`** | <code>() =&gt; Promise&lt;void&gt;</code> |
131
+
132
+
133
+ #### SSORedirectEvent
134
+
135
+ | Prop | Type | Description |
136
+ | ----------- | ------------------- | ------------------------------------- |
137
+ | **`url`** | <code>string</code> | Full redirect URL yang ter-intercept |
138
+ | **`code`** | <code>string</code> | Authorization code (belum dikonsumsi) |
139
+ | **`state`** | <code>string</code> | State parameter dari OAuth flow |
140
+
141
+ </docgen-api>
@@ -0,0 +1,59 @@
1
+ ext {
2
+ junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
4
+ androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
5
+ androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
6
+ }
7
+
8
+ buildscript {
9
+ repositories {
10
+ google()
11
+ mavenCentral()
12
+ }
13
+ dependencies {
14
+ classpath 'com.android.tools.build:gradle:8.13.0'
15
+ }
16
+ }
17
+
18
+ apply plugin: 'com.android.library'
19
+
20
+ android {
21
+ namespace = "com.telkomsel.ssowebview"
22
+ compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
23
+ defaultConfig {
24
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
25
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
26
+ versionCode 1
27
+ versionName "1.0"
28
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
29
+ }
30
+ buildTypes {
31
+ release {
32
+ minifyEnabled false
33
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
34
+ }
35
+ }
36
+ lintOptions {
37
+ abortOnError = false
38
+ }
39
+ compileOptions {
40
+ sourceCompatibility JavaVersion.VERSION_17
41
+ targetCompatibility JavaVersion.VERSION_17
42
+ }
43
+ }
44
+
45
+
46
+ repositories {
47
+ google()
48
+ mavenCentral()
49
+ }
50
+
51
+
52
+ dependencies {
53
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
54
+ implementation project(':capacitor-android')
55
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
56
+ testImplementation "junit:junit:$junitVersion"
57
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
58
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
59
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,158 @@
1
+ package com.telkomsel.ssowebview;
2
+
3
+ import android.app.Activity;
4
+ import android.app.Dialog;
5
+ import android.graphics.Color;
6
+ import android.net.Uri;
7
+ import android.view.Gravity;
8
+ import android.view.ViewGroup;
9
+ import android.webkit.CookieManager;
10
+ import android.webkit.WebResourceRequest;
11
+ import android.webkit.WebSettings;
12
+ import android.webkit.WebView;
13
+ import android.webkit.WebViewClient;
14
+ import android.widget.LinearLayout;
15
+ import android.widget.TextView;
16
+
17
+ public class SSOWebView {
18
+
19
+ public interface Listener {
20
+ void onRedirectIntercepted(String url, String code, String state);
21
+ void onCancelled();
22
+ }
23
+
24
+ private Dialog dialog;
25
+ private WebView webView;
26
+ private boolean alreadyRedirected = false;
27
+
28
+ public void open(
29
+ final Activity activity,
30
+ final String url,
31
+ final String redirectHost,
32
+ final String redirectPathContains,
33
+ final String title,
34
+ final Listener listener
35
+ ) {
36
+ alreadyRedirected = false;
37
+
38
+ dialog = new Dialog(activity, android.R.style.Theme_Light_NoTitleBar_Fullscreen);
39
+
40
+ LinearLayout root = new LinearLayout(activity);
41
+ root.setOrientation(LinearLayout.VERTICAL);
42
+ root.setLayoutParams(new LinearLayout.LayoutParams(
43
+ ViewGroup.LayoutParams.MATCH_PARENT,
44
+ ViewGroup.LayoutParams.MATCH_PARENT
45
+ ));
46
+
47
+ /* ── Header bar ── */
48
+ LinearLayout header = new LinearLayout(activity);
49
+ header.setOrientation(LinearLayout.HORIZONTAL);
50
+ header.setBackgroundColor(Color.parseColor("#1A2B4A"));
51
+ header.setPadding(48, 72, 48, 48);
52
+ header.setGravity(Gravity.CENTER_VERTICAL);
53
+ header.setLayoutParams(new LinearLayout.LayoutParams(
54
+ ViewGroup.LayoutParams.MATCH_PARENT,
55
+ ViewGroup.LayoutParams.WRAP_CONTENT
56
+ ));
57
+
58
+ TextView tvTitle = new TextView(activity);
59
+ tvTitle.setText(title != null ? title : "Login");
60
+ tvTitle.setTextColor(Color.WHITE);
61
+ tvTitle.setTextSize(16);
62
+ tvTitle.setLayoutParams(new LinearLayout.LayoutParams(
63
+ 0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f
64
+ ));
65
+
66
+ TextView btnClose = new TextView(activity);
67
+ btnClose.setText("Tutup");
68
+ btnClose.setTextColor(Color.WHITE);
69
+ btnClose.setTextSize(14);
70
+ btnClose.setPadding(32, 16, 16, 16);
71
+ btnClose.setOnClickListener(v -> {
72
+ boolean wasRedirected = alreadyRedirected;
73
+ dismiss();
74
+ if (!wasRedirected && listener != null) listener.onCancelled();
75
+ });
76
+
77
+ header.addView(tvTitle);
78
+ header.addView(btnClose);
79
+
80
+ /* ── WebView ── */
81
+ webView = new WebView(activity);
82
+ WebSettings s = webView.getSettings();
83
+ s.setJavaScriptEnabled(true);
84
+ s.setDomStorageEnabled(true);
85
+ s.setDatabaseEnabled(true);
86
+ s.setUseWideViewPort(true);
87
+ s.setLoadWithOverviewMode(true);
88
+ s.setSupportZoom(false);
89
+ s.setJavaScriptCanOpenWindowsAutomatically(true);
90
+
91
+ CookieManager cm = CookieManager.getInstance();
92
+ cm.setAcceptCookie(true);
93
+ cm.setAcceptThirdPartyCookies(webView, true);
94
+
95
+ webView.setWebViewClient(new WebViewClient() {
96
+ @Override
97
+ public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
98
+ Uri u = request.getUrl();
99
+ String host = u.getHost();
100
+ String path = u.getPath();
101
+
102
+ boolean isRedirect =
103
+ host != null && host.equalsIgnoreCase(redirectHost) &&
104
+ path != null && path.toLowerCase()
105
+ .contains(redirectPathContains.toLowerCase());
106
+
107
+ if (isRedirect && !alreadyRedirected) {
108
+ alreadyRedirected = true;
109
+
110
+ String code = u.getQueryParameter("code");
111
+ String state = u.getQueryParameter("state");
112
+
113
+ dismiss();
114
+
115
+ if (listener != null) {
116
+ listener.onRedirectIntercepted(
117
+ u.toString(),
118
+ code != null ? code : "",
119
+ state != null ? state : ""
120
+ );
121
+ }
122
+ return true; /* BLOKIR navigasi — code TIDAK dikonsumsi */
123
+ }
124
+ return false;
125
+ }
126
+ });
127
+
128
+ webView.setLayoutParams(new LinearLayout.LayoutParams(
129
+ ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f
130
+ ));
131
+
132
+ root.addView(header);
133
+ root.addView(webView);
134
+
135
+ dialog.setContentView(root);
136
+ dialog.setCancelable(true);
137
+ dialog.setOnCancelListener(d -> {
138
+ if (!alreadyRedirected && listener != null) listener.onCancelled();
139
+ });
140
+ dialog.show();
141
+
142
+ webView.loadUrl(url);
143
+ }
144
+
145
+ public void dismiss() {
146
+ try {
147
+ if (webView != null) {
148
+ webView.stopLoading();
149
+ webView.destroy();
150
+ webView = null;
151
+ }
152
+ if (dialog != null && dialog.isShowing()) {
153
+ dialog.dismiss();
154
+ }
155
+ dialog = null;
156
+ } catch (Exception ignored) {}
157
+ }
158
+ }
@@ -0,0 +1,62 @@
1
+ package com.telkomsel.ssowebview;
2
+
3
+ import com.getcapacitor.JSObject;
4
+ import com.getcapacitor.Plugin;
5
+ import com.getcapacitor.PluginCall;
6
+ import com.getcapacitor.PluginMethod;
7
+ import com.getcapacitor.annotation.CapacitorPlugin;
8
+
9
+ @CapacitorPlugin(name = "SSOWebView")
10
+ public class SSOWebViewPlugin extends Plugin {
11
+
12
+ private final SSOWebView implementation = new SSOWebView();
13
+
14
+ @PluginMethod
15
+ public void openSSO(PluginCall call) {
16
+ final String url = call.getString("url");
17
+ final String redirectHost = call.getString("redirectHost", "tdirectmbl.telkomsel.co.id");
18
+ final String redirectPath = call.getString("redirectPathContains", "mobilelogon");
19
+ final String title = call.getString("title", "Login");
20
+
21
+ if (url == null || url.isEmpty()) {
22
+ call.reject("url is required");
23
+ return;
24
+ }
25
+
26
+ getActivity().runOnUiThread(() -> {
27
+ try {
28
+ implementation.open(
29
+ getActivity(),
30
+ url,
31
+ redirectHost,
32
+ redirectPath,
33
+ title,
34
+ new SSOWebView.Listener() {
35
+ @Override
36
+ public void onRedirectIntercepted(String u, String code, String state) {
37
+ JSObject ret = new JSObject();
38
+ ret.put("url", u);
39
+ ret.put("code", code);
40
+ ret.put("state", state);
41
+ notifyListeners("ssoRedirect", ret);
42
+ }
43
+
44
+ @Override
45
+ public void onCancelled() {
46
+ notifyListeners("ssoCancelled", new JSObject());
47
+ }
48
+ }
49
+ );
50
+ call.resolve();
51
+ } catch (Exception e) {
52
+ call.reject("Failed to open SSO WebView: " + e.getMessage());
53
+ }
54
+ });
55
+ }
56
+
57
+ @PluginMethod
58
+ public void close(PluginCall call) {
59
+ getActivity().runOnUiThread(implementation::dismiss);
60
+ call.resolve();
61
+ }
62
+ }
File without changes
package/dist/docs.json ADDED
@@ -0,0 +1,184 @@
1
+ {
2
+ "api": {
3
+ "name": "SSOWebViewPlugin",
4
+ "slug": "ssowebviewplugin",
5
+ "docs": "",
6
+ "tags": [],
7
+ "methods": [
8
+ {
9
+ "name": "openSSO",
10
+ "signature": "(options: SSOOpenOptions) => Promise<void>",
11
+ "parameters": [
12
+ {
13
+ "name": "options",
14
+ "docs": "",
15
+ "type": "SSOOpenOptions"
16
+ }
17
+ ],
18
+ "returns": "Promise<void>",
19
+ "tags": [],
20
+ "docs": "Buka WebView SSO. Navigasi ke redirectHost akan di-intercept\ndan DIBATALKAN sehingga authorization code tidak dikonsumsi.",
21
+ "complexTypes": [
22
+ "SSOOpenOptions"
23
+ ],
24
+ "slug": "opensso"
25
+ },
26
+ {
27
+ "name": "close",
28
+ "signature": "() => Promise<void>",
29
+ "parameters": [],
30
+ "returns": "Promise<void>",
31
+ "tags": [],
32
+ "docs": "Tutup WebView secara manual",
33
+ "complexTypes": [],
34
+ "slug": "close"
35
+ },
36
+ {
37
+ "name": "addListener",
38
+ "signature": "(eventName: 'ssoRedirect', listenerFunc: (event: SSORedirectEvent) => void) => Promise<PluginListenerHandle>",
39
+ "parameters": [
40
+ {
41
+ "name": "eventName",
42
+ "docs": "",
43
+ "type": "'ssoRedirect'"
44
+ },
45
+ {
46
+ "name": "listenerFunc",
47
+ "docs": "",
48
+ "type": "(event: SSORedirectEvent) => void"
49
+ }
50
+ ],
51
+ "returns": "Promise<PluginListenerHandle>",
52
+ "tags": [],
53
+ "docs": "Fired saat redirect ter-intercept — code masih valid",
54
+ "complexTypes": [
55
+ "PluginListenerHandle",
56
+ "SSORedirectEvent"
57
+ ],
58
+ "slug": "addlistenerssoredirect-"
59
+ },
60
+ {
61
+ "name": "addListener",
62
+ "signature": "(eventName: 'ssoCancelled', listenerFunc: () => void) => Promise<PluginListenerHandle>",
63
+ "parameters": [
64
+ {
65
+ "name": "eventName",
66
+ "docs": "",
67
+ "type": "'ssoCancelled'"
68
+ },
69
+ {
70
+ "name": "listenerFunc",
71
+ "docs": "",
72
+ "type": "() => void"
73
+ }
74
+ ],
75
+ "returns": "Promise<PluginListenerHandle>",
76
+ "tags": [],
77
+ "docs": "Fired saat user menutup WebView tanpa menyelesaikan login",
78
+ "complexTypes": [
79
+ "PluginListenerHandle"
80
+ ],
81
+ "slug": "addlistenerssocancelled-"
82
+ },
83
+ {
84
+ "name": "removeAllListeners",
85
+ "signature": "() => Promise<void>",
86
+ "parameters": [],
87
+ "returns": "Promise<void>",
88
+ "tags": [],
89
+ "docs": "",
90
+ "complexTypes": [],
91
+ "slug": "removealllisteners"
92
+ }
93
+ ],
94
+ "properties": []
95
+ },
96
+ "interfaces": [
97
+ {
98
+ "name": "SSOOpenOptions",
99
+ "slug": "ssoopenoptions",
100
+ "docs": "",
101
+ "tags": [],
102
+ "methods": [],
103
+ "properties": [
104
+ {
105
+ "name": "url",
106
+ "tags": [],
107
+ "docs": "URL SSO authorize endpoint",
108
+ "complexTypes": [],
109
+ "type": "string"
110
+ },
111
+ {
112
+ "name": "redirectHost",
113
+ "tags": [],
114
+ "docs": "Host yang menandakan redirect, mis. \"tdirectmbl.telkomsel.co.id\"",
115
+ "complexTypes": [],
116
+ "type": "string"
117
+ },
118
+ {
119
+ "name": "redirectPathContains",
120
+ "tags": [],
121
+ "docs": "Substring path redirect, mis. \"mobilelogon\"",
122
+ "complexTypes": [],
123
+ "type": "string"
124
+ },
125
+ {
126
+ "name": "title",
127
+ "tags": [],
128
+ "docs": "Judul header WebView",
129
+ "complexTypes": [],
130
+ "type": "string | undefined"
131
+ }
132
+ ]
133
+ },
134
+ {
135
+ "name": "PluginListenerHandle",
136
+ "slug": "pluginlistenerhandle",
137
+ "docs": "",
138
+ "tags": [],
139
+ "methods": [],
140
+ "properties": [
141
+ {
142
+ "name": "remove",
143
+ "tags": [],
144
+ "docs": "",
145
+ "complexTypes": [],
146
+ "type": "() => Promise<void>"
147
+ }
148
+ ]
149
+ },
150
+ {
151
+ "name": "SSORedirectEvent",
152
+ "slug": "ssoredirectevent",
153
+ "docs": "",
154
+ "tags": [],
155
+ "methods": [],
156
+ "properties": [
157
+ {
158
+ "name": "url",
159
+ "tags": [],
160
+ "docs": "Full redirect URL yang ter-intercept",
161
+ "complexTypes": [],
162
+ "type": "string"
163
+ },
164
+ {
165
+ "name": "code",
166
+ "tags": [],
167
+ "docs": "Authorization code (belum dikonsumsi)",
168
+ "complexTypes": [],
169
+ "type": "string"
170
+ },
171
+ {
172
+ "name": "state",
173
+ "tags": [],
174
+ "docs": "State parameter dari OAuth flow",
175
+ "complexTypes": [],
176
+ "type": "string"
177
+ }
178
+ ]
179
+ }
180
+ ],
181
+ "enums": [],
182
+ "typeAliases": [],
183
+ "pluginConfigs": []
184
+ }
@@ -0,0 +1,33 @@
1
+ import type { PluginListenerHandle } from '@capacitor/core';
2
+ export interface SSORedirectEvent {
3
+ /** Full redirect URL yang ter-intercept */
4
+ url: string;
5
+ /** Authorization code (belum dikonsumsi) */
6
+ code: string;
7
+ /** State parameter dari OAuth flow */
8
+ state: string;
9
+ }
10
+ export interface SSOOpenOptions {
11
+ /** URL SSO authorize endpoint */
12
+ url: string;
13
+ /** Host yang menandakan redirect, mis. "tdirectmbl.telkomsel.co.id" */
14
+ redirectHost: string;
15
+ /** Substring path redirect, mis. "mobilelogon" */
16
+ redirectPathContains: string;
17
+ /** Judul header WebView */
18
+ title?: string;
19
+ }
20
+ export interface SSOWebViewPlugin {
21
+ /**
22
+ * Buka WebView SSO. Navigasi ke redirectHost akan di-intercept
23
+ * dan DIBATALKAN sehingga authorization code tidak dikonsumsi.
24
+ */
25
+ openSSO(options: SSOOpenOptions): Promise<void>;
26
+ /** Tutup WebView secara manual */
27
+ close(): Promise<void>;
28
+ /** Fired saat redirect ter-intercept — code masih valid */
29
+ addListener(eventName: 'ssoRedirect', listenerFunc: (event: SSORedirectEvent) => void): Promise<PluginListenerHandle>;
30
+ /** Fired saat user menutup WebView tanpa menyelesaikan login */
31
+ addListener(eventName: 'ssoCancelled', listenerFunc: () => void): Promise<PluginListenerHandle>;
32
+ removeAllListeners(): Promise<void>;
33
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=definitions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PluginListenerHandle } from '@capacitor/core';\n\nexport interface SSORedirectEvent {\n /** Full redirect URL yang ter-intercept */\n url: string;\n /** Authorization code (belum dikonsumsi) */\n code: string;\n /** State parameter dari OAuth flow */\n state: string;\n}\n\nexport interface SSOOpenOptions {\n /** URL SSO authorize endpoint */\n url: string;\n /** Host yang menandakan redirect, mis. \"tdirectmbl.telkomsel.co.id\" */\n redirectHost: string;\n /** Substring path redirect, mis. \"mobilelogon\" */\n redirectPathContains: string;\n /** Judul header WebView */\n title?: string;\n}\n\nexport interface SSOWebViewPlugin {\n /**\n * Buka WebView SSO. Navigasi ke redirectHost akan di-intercept\n * dan DIBATALKAN sehingga authorization code tidak dikonsumsi.\n */\n openSSO(options: SSOOpenOptions): Promise<void>;\n\n /** Tutup WebView secara manual */\n close(): Promise<void>;\n\n /** Fired saat redirect ter-intercept — code masih valid */\n addListener(eventName: 'ssoRedirect', listenerFunc: (event: SSORedirectEvent) => void): Promise<PluginListenerHandle>;\n\n /** Fired saat user menutup WebView tanpa menyelesaikan login */\n addListener(eventName: 'ssoCancelled', listenerFunc: () => void): Promise<PluginListenerHandle>;\n\n removeAllListeners(): Promise<void>;\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { SSOWebViewPlugin } from './definitions';
2
+ declare const SSOWebView: SSOWebViewPlugin;
3
+ export * from './definitions';
4
+ export { SSOWebView };
@@ -0,0 +1,7 @@
1
+ import { registerPlugin } from '@capacitor/core';
2
+ const SSOWebView = registerPlugin('SSOWebView', {
3
+ web: () => import('./web').then((m) => new m.SSOWebViewWeb()),
4
+ });
5
+ export * from './definitions';
6
+ export { SSOWebView };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,UAAU,GAAG,cAAc,CAAmB,YAAY,EAAE;IAChE,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,CAAC;CAC9D,CAAC,CAAC;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,CAAC","sourcesContent":["import { registerPlugin } from '@capacitor/core';\n\nimport type { SSOWebViewPlugin } from './definitions';\n\nconst SSOWebView = registerPlugin<SSOWebViewPlugin>('SSOWebView', {\n web: () => import('./web').then((m) => new m.SSOWebViewWeb()),\n});\n\nexport * from './definitions';\nexport { SSOWebView };\n"]}
@@ -0,0 +1,8 @@
1
+ import { WebPlugin } from '@capacitor/core';
2
+ import type { SSOOpenOptions, SSOWebViewPlugin } from './definitions';
3
+ export declare class SSOWebViewWeb extends WebPlugin implements SSOWebViewPlugin {
4
+ private popup;
5
+ private pollTimer;
6
+ openSSO(options: SSOOpenOptions): Promise<void>;
7
+ close(): Promise<void>;
8
+ }
@@ -0,0 +1,47 @@
1
+ import { WebPlugin } from '@capacitor/core';
2
+ export class SSOWebViewWeb extends WebPlugin {
3
+ constructor() {
4
+ super(...arguments);
5
+ this.popup = null;
6
+ this.pollTimer = null;
7
+ }
8
+ async openSSO(options) {
9
+ this.popup = window.open(options.url, 'ssoLogin', 'width=480,height=680,toolbar=1,location=1,scrollbars=1,resizable=1');
10
+ if (!this.popup) {
11
+ throw new Error('Popup blocked by browser');
12
+ }
13
+ // Web: polling terbatas oleh CORS, hanya untuk same-origin redirect
14
+ this.pollTimer = setInterval(() => {
15
+ var _a, _b;
16
+ try {
17
+ if (!this.popup || this.popup.closed) {
18
+ clearInterval(this.pollTimer);
19
+ this.notifyListeners('ssoCancelled', {});
20
+ return;
21
+ }
22
+ const href = this.popup.location.href;
23
+ if (href.includes(options.redirectPathContains)) {
24
+ clearInterval(this.pollTimer);
25
+ const u = new URL(href);
26
+ this.notifyListeners('ssoRedirect', {
27
+ url: href,
28
+ code: (_a = u.searchParams.get('code')) !== null && _a !== void 0 ? _a : '',
29
+ state: (_b = u.searchParams.get('state')) !== null && _b !== void 0 ? _b : '',
30
+ });
31
+ this.popup.close();
32
+ }
33
+ }
34
+ catch (_c) {
35
+ // cross-origin — normal selama masih di domain SSO
36
+ }
37
+ }, 300);
38
+ }
39
+ async close() {
40
+ if (this.pollTimer)
41
+ clearInterval(this.pollTimer);
42
+ if (this.popup && !this.popup.closed)
43
+ this.popup.close();
44
+ this.popup = null;
45
+ }
46
+ }
47
+ //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C,MAAM,OAAO,aAAc,SAAQ,SAAS;IAA5C;;QACU,UAAK,GAAkB,IAAI,CAAC;QAC5B,cAAS,GAAQ,IAAI,CAAC;IA2ChC,CAAC;IAzCC,KAAK,CAAC,OAAO,CAAC,OAAuB;QACnC,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,IAAI,CACtB,OAAO,CAAC,GAAG,EACX,UAAU,EACV,oEAAoE,CACrE,CAAC;QAEF,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC9C,CAAC;QAED,oEAAoE;QACpE,IAAI,CAAC,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE;;YAChC,IAAI,CAAC;gBACH,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;oBACrC,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;oBAC9B,IAAI,CAAC,eAAe,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;oBACzC,OAAO;gBACT,CAAC;gBACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;gBACtC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,oBAAoB,CAAC,EAAE,CAAC;oBAChD,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;oBAC9B,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;oBACxB,IAAI,CAAC,eAAe,CAAC,aAAa,EAAE;wBAClC,GAAG,EAAE,IAAI;wBACT,IAAI,EAAE,MAAA,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,mCAAI,EAAE;wBACtC,KAAK,EAAE,MAAA,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,mCAAI,EAAE;qBACzC,CAAC,CAAC;oBACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;gBACrB,CAAC;YACH,CAAC;YAAC,WAAM,CAAC;gBACP,mDAAmD;YACrD,CAAC;QACH,CAAC,EAAE,GAAG,CAAC,CAAC;IACV,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,SAAS;YAAE,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAClD,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM;YAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACzD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;CACF","sourcesContent":["import { WebPlugin } from '@capacitor/core';\n\nimport type { SSOOpenOptions, SSOWebViewPlugin } from './definitions';\n\nexport class SSOWebViewWeb extends WebPlugin implements SSOWebViewPlugin {\n private popup: Window | null = null;\n private pollTimer: any = null;\n\n async openSSO(options: SSOOpenOptions): Promise<void> {\n this.popup = window.open(\n options.url,\n 'ssoLogin',\n 'width=480,height=680,toolbar=1,location=1,scrollbars=1,resizable=1',\n );\n\n if (!this.popup) {\n throw new Error('Popup blocked by browser');\n }\n\n // Web: polling terbatas oleh CORS, hanya untuk same-origin redirect\n this.pollTimer = setInterval(() => {\n try {\n if (!this.popup || this.popup.closed) {\n clearInterval(this.pollTimer);\n this.notifyListeners('ssoCancelled', {});\n return;\n }\n const href = this.popup.location.href;\n if (href.includes(options.redirectPathContains)) {\n clearInterval(this.pollTimer);\n const u = new URL(href);\n this.notifyListeners('ssoRedirect', {\n url: href,\n code: u.searchParams.get('code') ?? '',\n state: u.searchParams.get('state') ?? '',\n });\n this.popup.close();\n }\n } catch {\n // cross-origin — normal selama masih di domain SSO\n }\n }, 300);\n }\n\n async close(): Promise<void> {\n if (this.pollTimer) clearInterval(this.pollTimer);\n if (this.popup && !this.popup.closed) this.popup.close();\n this.popup = null;\n }\n}\n"]}
@@ -0,0 +1,61 @@
1
+ 'use strict';
2
+
3
+ var core = require('@capacitor/core');
4
+
5
+ const SSOWebView = core.registerPlugin('SSOWebView', {
6
+ web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.SSOWebViewWeb()),
7
+ });
8
+
9
+ class SSOWebViewWeb extends core.WebPlugin {
10
+ constructor() {
11
+ super(...arguments);
12
+ this.popup = null;
13
+ this.pollTimer = null;
14
+ }
15
+ async openSSO(options) {
16
+ this.popup = window.open(options.url, 'ssoLogin', 'width=480,height=680,toolbar=1,location=1,scrollbars=1,resizable=1');
17
+ if (!this.popup) {
18
+ throw new Error('Popup blocked by browser');
19
+ }
20
+ // Web: polling terbatas oleh CORS, hanya untuk same-origin redirect
21
+ this.pollTimer = setInterval(() => {
22
+ var _a, _b;
23
+ try {
24
+ if (!this.popup || this.popup.closed) {
25
+ clearInterval(this.pollTimer);
26
+ this.notifyListeners('ssoCancelled', {});
27
+ return;
28
+ }
29
+ const href = this.popup.location.href;
30
+ if (href.includes(options.redirectPathContains)) {
31
+ clearInterval(this.pollTimer);
32
+ const u = new URL(href);
33
+ this.notifyListeners('ssoRedirect', {
34
+ url: href,
35
+ code: (_a = u.searchParams.get('code')) !== null && _a !== void 0 ? _a : '',
36
+ state: (_b = u.searchParams.get('state')) !== null && _b !== void 0 ? _b : '',
37
+ });
38
+ this.popup.close();
39
+ }
40
+ }
41
+ catch (_c) {
42
+ // cross-origin — normal selama masih di domain SSO
43
+ }
44
+ }, 300);
45
+ }
46
+ async close() {
47
+ if (this.pollTimer)
48
+ clearInterval(this.pollTimer);
49
+ if (this.popup && !this.popup.closed)
50
+ this.popup.close();
51
+ this.popup = null;
52
+ }
53
+ }
54
+
55
+ var web = /*#__PURE__*/Object.freeze({
56
+ __proto__: null,
57
+ SSOWebViewWeb: SSOWebViewWeb
58
+ });
59
+
60
+ exports.SSOWebView = SSOWebView;
61
+ //# sourceMappingURL=plugin.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst SSOWebView = registerPlugin('SSOWebView', {\n web: () => import('./web').then((m) => new m.SSOWebViewWeb()),\n});\nexport * from './definitions';\nexport { SSOWebView };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class SSOWebViewWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.popup = null;\n this.pollTimer = null;\n }\n async openSSO(options) {\n this.popup = window.open(options.url, 'ssoLogin', 'width=480,height=680,toolbar=1,location=1,scrollbars=1,resizable=1');\n if (!this.popup) {\n throw new Error('Popup blocked by browser');\n }\n // Web: polling terbatas oleh CORS, hanya untuk same-origin redirect\n this.pollTimer = setInterval(() => {\n var _a, _b;\n try {\n if (!this.popup || this.popup.closed) {\n clearInterval(this.pollTimer);\n this.notifyListeners('ssoCancelled', {});\n return;\n }\n const href = this.popup.location.href;\n if (href.includes(options.redirectPathContains)) {\n clearInterval(this.pollTimer);\n const u = new URL(href);\n this.notifyListeners('ssoRedirect', {\n url: href,\n code: (_a = u.searchParams.get('code')) !== null && _a !== void 0 ? _a : '',\n state: (_b = u.searchParams.get('state')) !== null && _b !== void 0 ? _b : '',\n });\n this.popup.close();\n }\n }\n catch (_c) {\n // cross-origin — normal selama masih di domain SSO\n }\n }, 300);\n }\n async close() {\n if (this.pollTimer)\n clearInterval(this.pollTimer);\n if (this.popup && !this.popup.closed)\n this.popup.close();\n this.popup = null;\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;AACK,MAAC,UAAU,GAAGA,mBAAc,CAAC,YAAY,EAAE;AAChD,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,aAAa,EAAE,CAAC;AACjE,CAAC;;ACFM,MAAM,aAAa,SAASC,cAAS,CAAC;AAC7C,IAAI,WAAW,GAAG;AAClB,QAAQ,KAAK,CAAC,GAAG,SAAS,CAAC;AAC3B,QAAQ,IAAI,CAAC,KAAK,GAAG,IAAI;AACzB,QAAQ,IAAI,CAAC,SAAS,GAAG,IAAI;AAC7B,IAAI;AACJ,IAAI,MAAM,OAAO,CAAC,OAAO,EAAE;AAC3B,QAAQ,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,oEAAoE,CAAC;AAC/H,QAAQ,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;AACzB,YAAY,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC;AACvD,QAAQ;AACR;AACA,QAAQ,IAAI,CAAC,SAAS,GAAG,WAAW,CAAC,MAAM;AAC3C,YAAY,IAAI,EAAE,EAAE,EAAE;AACtB,YAAY,IAAI;AAChB,gBAAgB,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;AACtD,oBAAoB,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC;AACjD,oBAAoB,IAAI,CAAC,eAAe,CAAC,cAAc,EAAE,EAAE,CAAC;AAC5D,oBAAoB;AACpB,gBAAgB;AAChB,gBAAgB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI;AACrD,gBAAgB,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,oBAAoB,CAAC,EAAE;AACjE,oBAAoB,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC;AACjD,oBAAoB,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC;AAC3C,oBAAoB,IAAI,CAAC,eAAe,CAAC,aAAa,EAAE;AACxD,wBAAwB,GAAG,EAAE,IAAI;AACjC,wBAAwB,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,IAAI,EAAE,KAAK,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE;AACnG,wBAAwB,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,EAAE,KAAK,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE;AACrG,qBAAqB,CAAC;AACtB,oBAAoB,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE;AACtC,gBAAgB;AAChB,YAAY;AACZ,YAAY,OAAO,EAAE,EAAE;AACvB;AACA,YAAY;AACZ,QAAQ,CAAC,EAAE,GAAG,CAAC;AACf,IAAI;AACJ,IAAI,MAAM,KAAK,GAAG;AAClB,QAAQ,IAAI,IAAI,CAAC,SAAS;AAC1B,YAAY,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC;AACzC,QAAQ,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM;AAC5C,YAAY,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE;AAC9B,QAAQ,IAAI,CAAC,KAAK,GAAG,IAAI;AACzB,IAAI;AACJ;;;;;;;"}
package/dist/plugin.js ADDED
@@ -0,0 +1,64 @@
1
+ var capacitorSSOWebView = (function (exports, core) {
2
+ 'use strict';
3
+
4
+ const SSOWebView = core.registerPlugin('SSOWebView', {
5
+ web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.SSOWebViewWeb()),
6
+ });
7
+
8
+ class SSOWebViewWeb extends core.WebPlugin {
9
+ constructor() {
10
+ super(...arguments);
11
+ this.popup = null;
12
+ this.pollTimer = null;
13
+ }
14
+ async openSSO(options) {
15
+ this.popup = window.open(options.url, 'ssoLogin', 'width=480,height=680,toolbar=1,location=1,scrollbars=1,resizable=1');
16
+ if (!this.popup) {
17
+ throw new Error('Popup blocked by browser');
18
+ }
19
+ // Web: polling terbatas oleh CORS, hanya untuk same-origin redirect
20
+ this.pollTimer = setInterval(() => {
21
+ var _a, _b;
22
+ try {
23
+ if (!this.popup || this.popup.closed) {
24
+ clearInterval(this.pollTimer);
25
+ this.notifyListeners('ssoCancelled', {});
26
+ return;
27
+ }
28
+ const href = this.popup.location.href;
29
+ if (href.includes(options.redirectPathContains)) {
30
+ clearInterval(this.pollTimer);
31
+ const u = new URL(href);
32
+ this.notifyListeners('ssoRedirect', {
33
+ url: href,
34
+ code: (_a = u.searchParams.get('code')) !== null && _a !== void 0 ? _a : '',
35
+ state: (_b = u.searchParams.get('state')) !== null && _b !== void 0 ? _b : '',
36
+ });
37
+ this.popup.close();
38
+ }
39
+ }
40
+ catch (_c) {
41
+ // cross-origin — normal selama masih di domain SSO
42
+ }
43
+ }, 300);
44
+ }
45
+ async close() {
46
+ if (this.pollTimer)
47
+ clearInterval(this.pollTimer);
48
+ if (this.popup && !this.popup.closed)
49
+ this.popup.close();
50
+ this.popup = null;
51
+ }
52
+ }
53
+
54
+ var web = /*#__PURE__*/Object.freeze({
55
+ __proto__: null,
56
+ SSOWebViewWeb: SSOWebViewWeb
57
+ });
58
+
59
+ exports.SSOWebView = SSOWebView;
60
+
61
+ return exports;
62
+
63
+ })({}, capacitorExports);
64
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst SSOWebView = registerPlugin('SSOWebView', {\n web: () => import('./web').then((m) => new m.SSOWebViewWeb()),\n});\nexport * from './definitions';\nexport { SSOWebView };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class SSOWebViewWeb extends WebPlugin {\n constructor() {\n super(...arguments);\n this.popup = null;\n this.pollTimer = null;\n }\n async openSSO(options) {\n this.popup = window.open(options.url, 'ssoLogin', 'width=480,height=680,toolbar=1,location=1,scrollbars=1,resizable=1');\n if (!this.popup) {\n throw new Error('Popup blocked by browser');\n }\n // Web: polling terbatas oleh CORS, hanya untuk same-origin redirect\n this.pollTimer = setInterval(() => {\n var _a, _b;\n try {\n if (!this.popup || this.popup.closed) {\n clearInterval(this.pollTimer);\n this.notifyListeners('ssoCancelled', {});\n return;\n }\n const href = this.popup.location.href;\n if (href.includes(options.redirectPathContains)) {\n clearInterval(this.pollTimer);\n const u = new URL(href);\n this.notifyListeners('ssoRedirect', {\n url: href,\n code: (_a = u.searchParams.get('code')) !== null && _a !== void 0 ? _a : '',\n state: (_b = u.searchParams.get('state')) !== null && _b !== void 0 ? _b : '',\n });\n this.popup.close();\n }\n }\n catch (_c) {\n // cross-origin — normal selama masih di domain SSO\n }\n }, 300);\n }\n async close() {\n if (this.pollTimer)\n clearInterval(this.pollTimer);\n if (this.popup && !this.popup.closed)\n this.popup.close();\n this.popup = null;\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,UAAU,GAAGA,mBAAc,CAAC,YAAY,EAAE;IAChD,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,aAAa,EAAE,CAAC;IACjE,CAAC;;ICFM,MAAM,aAAa,SAASC,cAAS,CAAC;IAC7C,IAAI,WAAW,GAAG;IAClB,QAAQ,KAAK,CAAC,GAAG,SAAS,CAAC;IAC3B,QAAQ,IAAI,CAAC,KAAK,GAAG,IAAI;IACzB,QAAQ,IAAI,CAAC,SAAS,GAAG,IAAI;IAC7B,IAAI;IACJ,IAAI,MAAM,OAAO,CAAC,OAAO,EAAE;IAC3B,QAAQ,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,oEAAoE,CAAC;IAC/H,QAAQ,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;IACzB,YAAY,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC;IACvD,QAAQ;IACR;IACA,QAAQ,IAAI,CAAC,SAAS,GAAG,WAAW,CAAC,MAAM;IAC3C,YAAY,IAAI,EAAE,EAAE,EAAE;IACtB,YAAY,IAAI;IAChB,gBAAgB,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;IACtD,oBAAoB,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC;IACjD,oBAAoB,IAAI,CAAC,eAAe,CAAC,cAAc,EAAE,EAAE,CAAC;IAC5D,oBAAoB;IACpB,gBAAgB;IAChB,gBAAgB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI;IACrD,gBAAgB,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,oBAAoB,CAAC,EAAE;IACjE,oBAAoB,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC;IACjD,oBAAoB,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC;IAC3C,oBAAoB,IAAI,CAAC,eAAe,CAAC,aAAa,EAAE;IACxD,wBAAwB,GAAG,EAAE,IAAI;IACjC,wBAAwB,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,IAAI,EAAE,KAAK,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE;IACnG,wBAAwB,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,EAAE,KAAK,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE;IACrG,qBAAqB,CAAC;IACtB,oBAAoB,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE;IACtC,gBAAgB;IAChB,YAAY;IACZ,YAAY,OAAO,EAAE,EAAE;IACvB;IACA,YAAY;IACZ,QAAQ,CAAC,EAAE,GAAG,CAAC;IACf,IAAI;IACJ,IAAI,MAAM,KAAK,GAAG;IAClB,QAAQ,IAAI,IAAI,CAAC,SAAS;IAC1B,YAAY,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC;IACzC,QAAQ,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM;IAC5C,YAAY,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE;IAC9B,QAAQ,IAAI,CAAC,KAAK,GAAG,IAAI;IACzB,IAAI;IACJ;;;;;;;;;;;"}
@@ -0,0 +1,8 @@
1
+ import Foundation
2
+
3
+ @objc public class SSOWebView: NSObject {
4
+ @objc public func echo(_ value: String) -> String {
5
+ print(value)
6
+ return value
7
+ }
8
+ }
@@ -0,0 +1,23 @@
1
+ import Foundation
2
+ import Capacitor
3
+
4
+ /**
5
+ * Please read the Capacitor iOS Plugin Development Guide
6
+ * here: https://capacitorjs.com/docs/plugins/ios
7
+ */
8
+ @objc(SSOWebViewPlugin)
9
+ public class SSOWebViewPlugin: CAPPlugin, CAPBridgedPlugin {
10
+ public let identifier = "SSOWebViewPlugin"
11
+ public let jsName = "SSOWebView"
12
+ public let pluginMethods: [CAPPluginMethod] = [
13
+ CAPPluginMethod(name: "echo", returnType: CAPPluginReturnPromise)
14
+ ]
15
+ private let implementation = SSOWebView()
16
+
17
+ @objc func echo(_ call: CAPPluginCall) {
18
+ let value = call.getString("value") ?? ""
19
+ call.resolve([
20
+ "value": implementation.echo(value)
21
+ ])
22
+ }
23
+ }
@@ -0,0 +1,15 @@
1
+ import XCTest
2
+ @testable import SSOWebViewPlugin
3
+
4
+ class SSOWebViewTests: XCTestCase {
5
+ func testEcho() {
6
+ // This is an example of a functional test case for a plugin.
7
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
8
+
9
+ let implementation = SSOWebView()
10
+ let value = "Hello, World!"
11
+ let result = implementation.echo(value)
12
+
13
+ XCTAssertEqual(value, result)
14
+ }
15
+ }
package/package.json ADDED
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "capacitor-sso-webview",
3
+ "version": "0.0.1",
4
+ "description": "SSO WebView with URL intercept",
5
+ "main": "dist/plugin.cjs.js",
6
+ "module": "dist/esm/index.js",
7
+ "types": "dist/esm/index.d.ts",
8
+ "unpkg": "dist/plugin.js",
9
+ "files": [
10
+ "android/src/main/",
11
+ "android/build.gradle",
12
+ "dist/",
13
+ "ios/Sources",
14
+ "ios/Tests",
15
+ "Package.swift",
16
+ "CapacitorSsoWebview.podspec"
17
+ ],
18
+ "author": "Telkomsel",
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/Telkomsel/capacitor-sso-webview.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/Telkomsel/capacitor-sso-webview/issues"
26
+ },
27
+ "keywords": [
28
+ "capacitor",
29
+ "plugin",
30
+ "native"
31
+ ],
32
+ "scripts": {
33
+ "verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
34
+ "verify:ios": "xcodebuild -scheme CapacitorSsoWebview -destination generic/platform=iOS",
35
+ "verify:android": "cd android && ./gradlew clean build test && cd ..",
36
+ "verify:web": "npm run build",
37
+ "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
38
+ "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
39
+ "eslint": "eslint . --ext ts",
40
+ "prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
41
+ "swiftlint": "node-swiftlint",
42
+ "docgen": "docgen --api SSOWebViewPlugin --output-readme README.md --output-json dist/docs.json",
43
+ "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
44
+ "clean": "rimraf ./dist",
45
+ "watch": "tsc --watch",
46
+ "prepublishOnly": "npm run build"
47
+ },
48
+ "devDependencies": {
49
+ "@capacitor/android": "^8.3.5",
50
+ "@capacitor/core": "^8.3.5",
51
+ "@capacitor/docgen": "^0.3.1",
52
+ "@capacitor/ios": "^8.3.5",
53
+ "@ionic/eslint-config": "^0.4.0",
54
+ "@ionic/prettier-config": "^4.0.0",
55
+ "@ionic/swiftlint-config": "^2.0.0",
56
+ "eslint": "^8.57.1",
57
+ "prettier": "^3.6.2",
58
+ "prettier-plugin-java": "^2.7.7",
59
+ "rimraf": "^6.1.0",
60
+ "rollup": "^4.53.2",
61
+ "swiftlint": "^2.0.0",
62
+ "typescript": "^5.9.3"
63
+ },
64
+ "peerDependencies": {
65
+ "@capacitor/core": ">=6.0.0"
66
+ },
67
+ "prettier": "@ionic/prettier-config",
68
+ "swiftlint": "@ionic/swiftlint-config",
69
+ "eslintConfig": {
70
+ "extends": "@ionic/eslint-config/recommended"
71
+ },
72
+ "capacitor": {
73
+ "ios": {
74
+ "src": "ios"
75
+ },
76
+ "android": {
77
+ "src": "android"
78
+ }
79
+ }
80
+ }