@capgo/capacitor-autofill-save-password 6.0.0 → 6.0.2

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 CHANGED
@@ -5,15 +5,17 @@ Prompt to display dialog for saving password to keychain from webview app
5
5
  <a href="https://capgo.app/"><img src='https://raw.githubusercontent.com/Cap-go/capgo/main/assets/capgo_banner.png' alt='Capgo - Instant updates for capacitor'/></a>
6
6
 
7
7
  <div align="center">
8
- <h2><a href="https://capgo.app/?ref=plugin"> ➡️ Get Instant updates for your App with Capgo 🚀</a></h2>
9
- <h2><a href="https://capgo.app/consulting/?ref=plugin"> Fix your annoying bug now, Hire a Capacitor expert 💪</a></h2>
8
+ <h2><a href="https://capgo.app/?ref=plugin_autofill_save_password"> ➡️ Get Instant updates for your App with Capgo</a></h2>
9
+ <h2><a href="https://capgo.app/consulting/?ref=plugin_autofill_save_password"> Missing a feature? We’ll build the plugin for you 💪</a></h2>
10
10
  </div>
11
11
 
12
12
  Fork of original plugin to work with Capacitor 7
13
13
 
14
14
  IOS work for old versions and 18.3
15
15
 
16
- Android still WIP
16
+ ## Documentation
17
+
18
+ The most complete doc is available here: https://capgo.app/docs/plugins/autofill-save-password/
17
19
 
18
20
  ## Install
19
21
 
@@ -82,6 +84,8 @@ with
82
84
  <docgen-index>
83
85
 
84
86
  * [`promptDialog(...)`](#promptdialog)
87
+ * [`readPassword()`](#readpassword)
88
+ * [`getPluginVersion()`](#getpluginversion)
85
89
  * [Interfaces](#interfaces)
86
90
 
87
91
  </docgen-index>
@@ -104,14 +108,51 @@ Save a password to the keychain.
104
108
  --------------------
105
109
 
106
110
 
111
+ ### readPassword()
112
+
113
+ ```typescript
114
+ readPassword() => Promise<ReadPasswordResult>
115
+ ```
116
+
117
+ Read a password from the keychain. Requires the developer to setup associated domain for the app for iOS.
118
+
119
+ **Returns:** <code>Promise&lt;<a href="#readpasswordresult">ReadPasswordResult</a>&gt;</code>
120
+
121
+ --------------------
122
+
123
+
124
+ ### getPluginVersion()
125
+
126
+ ```typescript
127
+ getPluginVersion() => Promise<{ version: string; }>
128
+ ```
129
+
130
+ Get the native Capacitor plugin version.
131
+
132
+ **Returns:** <code>Promise&lt;{ version: string; }&gt;</code>
133
+
134
+ **Since:** 1.0.0
135
+
136
+ --------------------
137
+
138
+
107
139
  ### Interfaces
108
140
 
109
141
 
110
142
  #### Options
111
143
 
112
- | Prop | Type | Description |
113
- | -------------- | ------------------- | --------------------- |
114
- | **`username`** | <code>string</code> | The username to save. |
115
- | **`password`** | <code>string</code> | The password to save. |
144
+ | Prop | Type | Description |
145
+ | -------------- | ------------------- | ------------------------------------------------------------------------------ |
146
+ | **`username`** | <code>string</code> | The username to save. |
147
+ | **`password`** | <code>string</code> | The password to save. |
148
+ | **`url`** | <code>string</code> | The url to save the password for. (For example: "console.capgo.app") iOS only. |
149
+
150
+
151
+ #### ReadPasswordResult
152
+
153
+ | Prop | Type | Description |
154
+ | -------------- | ------------------- | ----------------------------- |
155
+ | **`username`** | <code>string</code> | The username of the password. |
156
+ | **`password`** | <code>string</code> | The password of the password. |
116
157
 
117
158
  </docgen-api>
@@ -51,8 +51,8 @@ dependencies {
51
51
  implementation fileTree(dir: 'libs', include: ['*.jar'])
52
52
  implementation project(':capacitor-android')
53
53
  implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
54
- implementation "androidx.credentials:credentials:1.5.0"
55
- implementation "androidx.credentials:credentials-play-services-auth:1.5.0"
54
+ implementation "androidx.credentials:credentials:1.2.2"
55
+ implementation "androidx.credentials:credentials-play-services-auth:1.2.2"
56
56
  testImplementation "junit:junit:$junitVersion"
57
57
  androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
58
58
  androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
@@ -3,29 +3,30 @@ package ee.forgr.autofill_password;
3
3
  import android.app.Activity;
4
4
  import android.os.Build;
5
5
  import android.util.Log;
6
-
7
- import androidx.credentials.CredentialManager;
8
- import androidx.credentials.CreatePasswordRequest;
6
+ import androidx.core.content.ContextCompat;
9
7
  import androidx.credentials.CreateCredentialResponse;
10
- import androidx.credentials.PendingGetCredentialRequest;
8
+ import androidx.credentials.CreatePasswordRequest;
9
+ import androidx.credentials.CredentialManager;
11
10
  import androidx.credentials.CredentialManagerCallback;
11
+ import androidx.credentials.GetCredentialRequest;
12
+ import androidx.credentials.GetCredentialResponse;
13
+ import androidx.credentials.GetPasswordOption;
14
+ import androidx.credentials.PasswordCredential;
12
15
  import androidx.credentials.exceptions.CreateCredentialException;
13
-
16
+ import androidx.credentials.exceptions.GetCredentialException;
17
+ import com.getcapacitor.JSObject;
14
18
  import com.getcapacitor.Plugin;
15
19
  import com.getcapacitor.PluginCall;
16
20
  import com.getcapacitor.PluginMethod;
17
21
  import com.getcapacitor.annotation.CapacitorPlugin;
18
-
19
- import java.util.HashMap;
20
- import java.util.Map;
21
-
22
- import androidx.core.content.ContextCompat;
22
+ import java.util.List;
23
23
 
24
24
  @CapacitorPlugin(name = "SavePassword")
25
25
  public class SavePasswordPlugin extends Plugin {
26
+
27
+ private final String pluginVersion = "6.0.2";
26
28
  private static final String TAG = "CredentialManager";
27
29
  private CredentialManager credentialManager;
28
- private Map<String, PendingGetCredentialRequest> pendingRequestsByElementId = new HashMap<>();
29
30
 
30
31
  @Override
31
32
  public void load() {
@@ -59,7 +60,7 @@ public class SavePasswordPlugin extends Plugin {
59
60
  try {
60
61
  // Build request directly with username & password (API 1.5.0 signature)
61
62
  CreatePasswordRequest request = new CreatePasswordRequest(username, password);
62
-
63
+
63
64
  // Execute on main thread
64
65
  bridge.executeOnMainThread(() -> {
65
66
  Activity activity = getActivity();
@@ -95,6 +96,57 @@ public class SavePasswordPlugin extends Plugin {
95
96
  }
96
97
  }
97
98
 
99
+ @PluginMethod
100
+ public void readPassword(final PluginCall call) {
101
+ if (!isCredentialManagerAvailable(call)) {
102
+ return;
103
+ }
104
+
105
+ try {
106
+ GetCredentialRequest request = new GetCredentialRequest(List.of(new GetPasswordOption()));
107
+
108
+ bridge.executeOnMainThread(() -> {
109
+ Activity activity = getActivity();
110
+ if (activity == null) {
111
+ call.reject("Activity not available");
112
+ return;
113
+ }
114
+
115
+ try {
116
+ credentialManager.getCredentialAsync(
117
+ activity,
118
+ request,
119
+ null,
120
+ ContextCompat.getMainExecutor(getContext()),
121
+ new CredentialManagerCallback<GetCredentialResponse, GetCredentialException>() {
122
+ @Override
123
+ public void onResult(GetCredentialResponse response) {
124
+ if (response.getCredential() instanceof PasswordCredential) {
125
+ PasswordCredential credential = (PasswordCredential) response.getCredential();
126
+ JSObject result = new JSObject();
127
+ result.put("username", credential.getId());
128
+ result.put("password", credential.getPassword());
129
+ call.resolve(result);
130
+ } else {
131
+ call.reject("No password credential found");
132
+ }
133
+ }
134
+
135
+ @Override
136
+ public void onError(GetCredentialException e) {
137
+ call.reject("Error retrieving credential: " + e.getMessage(), e);
138
+ }
139
+ }
140
+ );
141
+ } catch (Exception e) {
142
+ call.reject("Error retrieving credential", e);
143
+ }
144
+ });
145
+ } catch (Exception e) {
146
+ call.reject("Error building get credential request", e);
147
+ }
148
+ }
149
+
98
150
  private boolean isCredentialManagerAvailable(PluginCall call) {
99
151
  if (credentialManager == null) {
100
152
  call.reject("Credential Manager not available on this device");
@@ -108,4 +160,15 @@ public class SavePasswordPlugin extends Plugin {
108
160
 
109
161
  return true;
110
162
  }
163
+
164
+ @PluginMethod
165
+ public void getPluginVersion(final PluginCall call) {
166
+ try {
167
+ final JSObject ret = new JSObject();
168
+ ret.put("version", this.pluginVersion);
169
+ call.resolve(ret);
170
+ } catch (final Exception e) {
171
+ call.reject("Could not get plugin version", e);
172
+ }
173
+ }
111
174
  }
package/dist/docs.json CHANGED
@@ -44,6 +44,42 @@
44
44
  "Options"
45
45
  ],
46
46
  "slug": "promptdialog"
47
+ },
48
+ {
49
+ "name": "readPassword",
50
+ "signature": "() => Promise<ReadPasswordResult>",
51
+ "parameters": [],
52
+ "returns": "Promise<ReadPasswordResult>",
53
+ "tags": [
54
+ {
55
+ "name": "returns",
56
+ "text": "The retrieved password credentials"
57
+ }
58
+ ],
59
+ "docs": "Read a password from the keychain. Requires the developer to setup associated domain for the app for iOS.",
60
+ "complexTypes": [
61
+ "ReadPasswordResult"
62
+ ],
63
+ "slug": "readpassword"
64
+ },
65
+ {
66
+ "name": "getPluginVersion",
67
+ "signature": "() => Promise<{ version: string; }>",
68
+ "parameters": [],
69
+ "returns": "Promise<{ version: string; }>",
70
+ "tags": [
71
+ {
72
+ "name": "returns",
73
+ "text": "Promise that resolves with the plugin version"
74
+ },
75
+ {
76
+ "name": "since",
77
+ "text": "1.0.0"
78
+ }
79
+ ],
80
+ "docs": "Get the native Capacitor plugin version.",
81
+ "complexTypes": [],
82
+ "slug": "getpluginversion"
47
83
  }
48
84
  ],
49
85
  "properties": []
@@ -78,6 +114,36 @@
78
114
  "docs": "The password to save.",
79
115
  "complexTypes": [],
80
116
  "type": "string"
117
+ },
118
+ {
119
+ "name": "url",
120
+ "tags": [],
121
+ "docs": "The url to save the password for. (For example: \"console.capgo.app\")\niOS only.",
122
+ "complexTypes": [],
123
+ "type": "string | undefined"
124
+ }
125
+ ]
126
+ },
127
+ {
128
+ "name": "ReadPasswordResult",
129
+ "slug": "readpasswordresult",
130
+ "docs": "",
131
+ "tags": [],
132
+ "methods": [],
133
+ "properties": [
134
+ {
135
+ "name": "username",
136
+ "tags": [],
137
+ "docs": "The username of the password.",
138
+ "complexTypes": [],
139
+ "type": "string"
140
+ },
141
+ {
142
+ "name": "password",
143
+ "tags": [],
144
+ "docs": "The password of the password.",
145
+ "complexTypes": [],
146
+ "type": "string"
81
147
  }
82
148
  ]
83
149
  }
@@ -11,6 +11,21 @@ export interface Options {
11
11
  * The password to save.
12
12
  */
13
13
  password: string;
14
+ /**
15
+ * The url to save the password for. (For example: "console.capgo.app")
16
+ * iOS only.
17
+ */
18
+ url?: string;
19
+ }
20
+ export interface ReadPasswordResult {
21
+ /**
22
+ * The username of the password.
23
+ */
24
+ username: string;
25
+ /**
26
+ * The password of the password.
27
+ */
28
+ password: string;
14
29
  }
15
30
  /**
16
31
  * @interface SavePasswordPlugin
@@ -28,4 +43,18 @@ export interface SavePasswordPlugin {
28
43
  * });
29
44
  */
30
45
  promptDialog(options: Options): Promise<void>;
46
+ /**
47
+ * Read a password from the keychain. Requires the developer to setup associated domain for the app for iOS.
48
+ * @returns {Promise<ReadPasswordResult>} The retrieved password credentials
49
+ */
50
+ readPassword(): Promise<ReadPasswordResult>;
51
+ /**
52
+ * Get the native Capacitor plugin version.
53
+ *
54
+ * @returns Promise that resolves with the plugin version
55
+ * @since 1.0.0
56
+ */
57
+ getPluginVersion(): Promise<{
58
+ version: string;
59
+ }>;
31
60
  }
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * @interface Options\n * @description The options for the prompt.\n */\nexport interface Options {\n /**\n * The username to save.\n */\n username: string;\n /**\n * The password to save.\n */\n password: string;\n}\n\n/**\n * @interface SavePasswordPlugin\n * @description Capacitor plugin for saving passwords to the keychain.\n */\nexport interface SavePasswordPlugin {\n /**\n * Save a password to the keychain.\n * @param {Options} options - The options for the password.\n * @returns {Promise<void>} Success status\n * @example\n * await SavePassword.promptDialog({\n * username: 'your-username',\n * password: 'your-password'\n * });\n */\n promptDialog(options: Options): Promise<void>;\n}\n"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * @interface Options\n * @description The options for the prompt.\n */\nexport interface Options {\n /**\n * The username to save.\n */\n username: string;\n /**\n * The password to save.\n */\n password: string;\n /**\n * The url to save the password for. (For example: \"console.capgo.app\")\n * iOS only.\n */\n url?: string;\n}\n\nexport interface ReadPasswordResult {\n /**\n * The username of the password.\n */\n username: string;\n /**\n * The password of the password.\n */\n password: string;\n}\n\n/**\n * @interface SavePasswordPlugin\n * @description Capacitor plugin for saving passwords to the keychain.\n */\nexport interface SavePasswordPlugin {\n /**\n * Save a password to the keychain.\n * @param {Options} options - The options for the password.\n * @returns {Promise<void>} Success status\n * @example\n * await SavePassword.promptDialog({\n * username: 'your-username',\n * password: 'your-password'\n * });\n */\n promptDialog(options: Options): Promise<void>;\n\n /**\n * Read a password from the keychain. Requires the developer to setup associated domain for the app for iOS.\n * @returns {Promise<ReadPasswordResult>} The retrieved password credentials\n */\n readPassword(): Promise<ReadPasswordResult>;\n\n /**\n * Get the native Capacitor plugin version.\n *\n * @returns Promise that resolves with the plugin version\n * @since 1.0.0\n */\n getPluginVersion(): Promise<{ version: string }>;\n}\n"]}
package/dist/esm/web.d.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import { WebPlugin } from '@capacitor/core';
2
- import type { Options, SavePasswordPlugin } from './definitions';
2
+ import type { Options, ReadPasswordResult, SavePasswordPlugin } from './definitions';
3
3
  export declare class SavePasswordWeb extends WebPlugin implements SavePasswordPlugin {
4
+ readPassword(): Promise<ReadPasswordResult>;
4
5
  promptDialog(options: Options): Promise<void>;
6
+ getPluginVersion(): Promise<{
7
+ version: string;
8
+ }>;
5
9
  }
package/dist/esm/web.js CHANGED
@@ -1,7 +1,13 @@
1
1
  import { WebPlugin } from '@capacitor/core';
2
2
  export class SavePasswordWeb extends WebPlugin {
3
+ readPassword() {
4
+ throw new Error('Method not implemented.');
5
+ }
3
6
  async promptDialog(options) {
4
- throw new Error("Not implemented on web" + JSON.stringify(options));
7
+ throw new Error('Not implemented on web' + JSON.stringify(options));
8
+ }
9
+ async getPluginVersion() {
10
+ return { version: 'web' };
5
11
  }
6
12
  }
7
13
  //# sourceMappingURL=web.js.map
@@ -1 +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,eAAgB,SAAQ,SAAS;IAC5C,KAAK,CAAC,YAAY,CAAC,OAAgB;QACjC,MAAM,IAAI,KAAK,CAAC,wBAAwB,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IACtE,CAAC;CACF","sourcesContent":["import { WebPlugin } from '@capacitor/core';\n\nimport type { Options, SavePasswordPlugin } from './definitions';\n\nexport class SavePasswordWeb extends WebPlugin implements SavePasswordPlugin {\n async promptDialog(options: Options): Promise<void> {\n throw new Error(\"Not implemented on web\" + JSON.stringify(options));\n }\n}\n"]}
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAI5C,MAAM,OAAO,eAAgB,SAAQ,SAAS;IAC5C,YAAY;QACV,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;IAC7C,CAAC;IACD,KAAK,CAAC,YAAY,CAAC,OAAgB;QACjC,MAAM,IAAI,KAAK,CAAC,wBAAwB,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,KAAK,CAAC,gBAAgB;QACpB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC5B,CAAC;CACF","sourcesContent":["import { WebPlugin } from '@capacitor/core';\n\nimport type { Options, ReadPasswordResult, SavePasswordPlugin } from './definitions';\n\nexport class SavePasswordWeb extends WebPlugin implements SavePasswordPlugin {\n readPassword(): Promise<ReadPasswordResult> {\n throw new Error('Method not implemented.');\n }\n async promptDialog(options: Options): Promise<void> {\n throw new Error('Not implemented on web' + JSON.stringify(options));\n }\n\n async getPluginVersion(): Promise<{ version: string }> {\n return { version: 'web' };\n }\n}\n"]}
@@ -7,8 +7,14 @@ const SavePassword = core.registerPlugin('SavePassword', {
7
7
  });
8
8
 
9
9
  class SavePasswordWeb extends core.WebPlugin {
10
+ readPassword() {
11
+ throw new Error('Method not implemented.');
12
+ }
10
13
  async promptDialog(options) {
11
- throw new Error("Not implemented on web" + JSON.stringify(options));
14
+ throw new Error('Not implemented on web' + JSON.stringify(options));
15
+ }
16
+ async getPluginVersion() {
17
+ return { version: 'web' };
12
18
  }
13
19
  }
14
20
 
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst SavePassword = registerPlugin('SavePassword', {\n web: () => import('./web').then((m) => new m.SavePasswordWeb()),\n});\nexport * from './definitions';\nexport { SavePassword };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class SavePasswordWeb extends WebPlugin {\n async promptDialog(options) {\n throw new Error(\"Not implemented on web\" + JSON.stringify(options));\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;AACK,MAAC,YAAY,GAAGA,mBAAc,CAAC,cAAc,EAAE;AACpD,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,eAAe,EAAE,CAAC;AACnE,CAAC;;ACFM,MAAM,eAAe,SAASC,cAAS,CAAC;AAC/C,IAAI,MAAM,YAAY,CAAC,OAAO,EAAE;AAChC,QAAQ,MAAM,IAAI,KAAK,CAAC,wBAAwB,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;AAC3E;AACA;;;;;;;;;"}
1
+ {"version":3,"file":"plugin.cjs.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst SavePassword = registerPlugin('SavePassword', {\n web: () => import('./web').then((m) => new m.SavePasswordWeb()),\n});\nexport * from './definitions';\nexport { SavePassword };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class SavePasswordWeb extends WebPlugin {\n readPassword() {\n throw new Error('Method not implemented.');\n }\n async promptDialog(options) {\n throw new Error('Not implemented on web' + JSON.stringify(options));\n }\n async getPluginVersion() {\n return { version: 'web' };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;;AACK,MAAC,YAAY,GAAGA,mBAAc,CAAC,cAAc,EAAE;AACpD,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,eAAe,EAAE,CAAC;AACnE,CAAC;;ACFM,MAAM,eAAe,SAASC,cAAS,CAAC;AAC/C,IAAI,YAAY,GAAG;AACnB,QAAQ,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC;AAClD;AACA,IAAI,MAAM,YAAY,CAAC,OAAO,EAAE;AAChC,QAAQ,MAAM,IAAI,KAAK,CAAC,wBAAwB,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;AAC3E;AACA,IAAI,MAAM,gBAAgB,GAAG;AAC7B,QAAQ,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE;AACjC;AACA;;;;;;;;;"}
package/dist/plugin.js CHANGED
@@ -6,8 +6,14 @@ var capacitorSavePassword = (function (exports, core) {
6
6
  });
7
7
 
8
8
  class SavePasswordWeb extends core.WebPlugin {
9
+ readPassword() {
10
+ throw new Error('Method not implemented.');
11
+ }
9
12
  async promptDialog(options) {
10
- throw new Error("Not implemented on web" + JSON.stringify(options));
13
+ throw new Error('Not implemented on web' + JSON.stringify(options));
14
+ }
15
+ async getPluginVersion() {
16
+ return { version: 'web' };
11
17
  }
12
18
  }
13
19
 
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst SavePassword = registerPlugin('SavePassword', {\n web: () => import('./web').then((m) => new m.SavePasswordWeb()),\n});\nexport * from './definitions';\nexport { SavePassword };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class SavePasswordWeb extends WebPlugin {\n async promptDialog(options) {\n throw new Error(\"Not implemented on web\" + JSON.stringify(options));\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,YAAY,GAAGA,mBAAc,CAAC,cAAc,EAAE;IACpD,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,eAAe,EAAE,CAAC;IACnE,CAAC;;ICFM,MAAM,eAAe,SAASC,cAAS,CAAC;IAC/C,IAAI,MAAM,YAAY,CAAC,OAAO,EAAE;IAChC,QAAQ,MAAM,IAAI,KAAK,CAAC,wBAAwB,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAC3E;IACA;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"plugin.js","sources":["esm/index.js","esm/web.js"],"sourcesContent":["import { registerPlugin } from '@capacitor/core';\nconst SavePassword = registerPlugin('SavePassword', {\n web: () => import('./web').then((m) => new m.SavePasswordWeb()),\n});\nexport * from './definitions';\nexport { SavePassword };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class SavePasswordWeb extends WebPlugin {\n readPassword() {\n throw new Error('Method not implemented.');\n }\n async promptDialog(options) {\n throw new Error('Not implemented on web' + JSON.stringify(options));\n }\n async getPluginVersion() {\n return { version: 'web' };\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["registerPlugin","WebPlugin"],"mappings":";;;AACK,UAAC,YAAY,GAAGA,mBAAc,CAAC,cAAc,EAAE;IACpD,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,eAAe,EAAE,CAAC;IACnE,CAAC;;ICFM,MAAM,eAAe,SAASC,cAAS,CAAC;IAC/C,IAAI,YAAY,GAAG;IACnB,QAAQ,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC;IAClD;IACA,IAAI,MAAM,YAAY,CAAC,OAAO,EAAE;IAChC,QAAQ,MAAM,IAAI,KAAK,CAAC,wBAAwB,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IAC3E;IACA,IAAI,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE;IACjC;IACA;;;;;;;;;;;;;;;"}
@@ -1,62 +1,106 @@
1
1
  import Foundation
2
2
  import Capacitor
3
+ import Security
4
+ import AuthenticationServices
3
5
 
4
6
  /**
5
7
  * Please read the Capacitor iOS Plugin Development Guide
6
8
  * here: https://capacitorjs.com/docs/plugins/ios
7
9
  */
8
10
  @objc(SavePasswordPlugin)
9
- public class SavePasswordPlugin: CAPPlugin, CAPBridgedPlugin {
11
+ public class SavePasswordPlugin: CAPPlugin, CAPBridgedPlugin, ASAuthorizationControllerDelegate, ASAuthorizationControllerPresentationContextProviding {
12
+ private let pluginVersion: String = "6.0.2"
10
13
  public let identifier = "SavePasswordPlugin"
11
14
 
12
15
  public let jsName = "SavePassword"
13
16
  public let pluginMethods: [CAPPluginMethod] = [
14
- CAPPluginMethod(name: "promptDialog", returnType: CAPPluginReturnPromise)
17
+ CAPPluginMethod(name: "promptDialog", returnType: CAPPluginReturnPromise),
18
+ CAPPluginMethod(name: "readPassword", returnType: CAPPluginReturnPromise),
19
+ CAPPluginMethod(name: "getPluginVersion", returnType: CAPPluginReturnPromise)
15
20
  ]
16
21
 
17
22
  @objc func promptDialog(_ call: CAPPluginCall) {
18
- DispatchQueue.main.async {
19
- let loginScreen = LoginScreenViewController()
20
- loginScreen.usernameTextField.text = call.getString("username") ?? ""
21
- loginScreen.passwordTextField.text = call.getString("password") ?? ""
22
- self.bridge?.webView?.addSubview(loginScreen.view)
23
+ guard let username = call.getString("username"),
24
+ let password = call.getString("password") else {
25
+ call.reject("Username and password are required")
26
+ return
27
+ }
28
+ guard let url = call.getString("url") else {
29
+ call.reject("URL is required for iOS shared web credentials")
30
+ return
31
+ }
32
+ let fqdn = url as CFString
33
+ let user = username as CFString
34
+ let pass = password as CFString
35
+ SecAddSharedWebCredential(fqdn, user, pass) { error in
36
+ DispatchQueue.main.async {
37
+ if let error = error {
38
+ let cfError = error as CFError
39
+ let description = CFErrorCopyDescription(cfError) as String? ?? "Unknown error"
40
+ call.reject("Failed to save credential", description)
41
+ } else {
42
+ call.resolve()
43
+ }
44
+ }
45
+ }
46
+ }
23
47
 
24
- // Defer removal so the system registers the fields before they disappear
48
+ @objc func readPassword(_ call: CAPPluginCall) {
49
+ if #available(iOS 12.0, *) {
25
50
  DispatchQueue.main.async {
26
- loginScreen.view.removeFromSuperview()
27
- // Clear fields *after* removal as required by Autofill heuristics
28
- loginScreen.usernameTextField.text = ""
29
- loginScreen.passwordTextField.text = ""
30
- call.resolve()
51
+ let passwordRequest = ASAuthorizationPasswordProvider().createRequest()
52
+ let authController = ASAuthorizationController(authorizationRequests: [passwordRequest])
53
+ self.currentReadCall = call
54
+ authController.delegate = self
55
+ authController.presentationContextProvider = self
56
+ authController.performRequests()
31
57
  }
58
+ } else {
59
+ call.reject("Password autofill not available on this iOS version")
32
60
  }
33
61
  }
34
- }
35
62
 
36
- class LoginScreenViewController: UIViewController {
37
- let usernameTextField: UITextField = {
38
- let textField = UITextField()
39
- textField.frame.size.width = 1
40
- textField.frame.size.height = 1
41
- textField.textContentType = .username
42
- return textField
43
- }()
44
-
45
- let passwordTextField: UITextField = {
46
- let textField = UITextField()
47
- textField.frame.size.width = 1
48
- textField.frame.size.height = 1
49
- textField.textContentType = .newPassword
50
- // Fix for ios 18.3 : from https://stackoverflow.com/questions/76773166/password-autofill-wkwebview-doesnt-present-save-password-alert#comment140186929_76773167
51
- return textField
52
- }()
53
-
54
- override func viewDidLoad() {
55
- super.viewDidLoad()
56
- view.frame = CGRect(x: 0, y: 0, width: 0, height: 0)
57
- view.addSubview(usernameTextField)
58
- view.addSubview(passwordTextField)
59
- // Make password the first responder so the strong-password prompt or save-password alert triggers reliably
60
- passwordTextField.becomeFirstResponder()
63
+ private var currentReadCall: CAPPluginCall?
64
+ private var currentCall: CAPPluginCall?
65
+
66
+ public func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
67
+ if let passwordCredential = authorization.credential as? ASPasswordCredential {
68
+ if let call = currentReadCall {
69
+ call.resolve([
70
+ "username": passwordCredential.user,
71
+ "password": passwordCredential.password
72
+ ])
73
+ currentReadCall = nil
74
+ return
75
+ }
76
+ currentCall?.resolve([
77
+ "username": passwordCredential.user,
78
+ "password": passwordCredential.password
79
+ ])
80
+ } else {
81
+ currentReadCall?.resolve()
82
+ currentReadCall = nil
83
+ currentCall?.resolve()
84
+ currentCall = nil
85
+ }
86
+ }
87
+
88
+ public func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
89
+ if let call = currentReadCall {
90
+ call.reject("Autofill failed", error.localizedDescription)
91
+ currentReadCall = nil
92
+ return
93
+ }
94
+ currentCall?.reject("Autofill failed", error.localizedDescription)
95
+ currentCall = nil
96
+ }
97
+
98
+ // MARK: - ASAuthorizationControllerPresentationContextProviding
99
+ public func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
100
+ return self.bridge?.viewController?.view.window ?? ASPresentationAnchor()
101
+ }
102
+
103
+ @objc func getPluginVersion(_ call: CAPPluginCall) {
104
+ call.resolve(["version": self.pluginVersion])
61
105
  }
62
106
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-autofill-save-password",
3
- "version": "6.0.0",
3
+ "version": "6.0.2",
4
4
  "description": "Prompt to display dialog for saving password to keychain from webview app",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",