@capgo/capacitor-autofill-save-password 6.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/CapgoCapacitorAutofillSavePassword.podspec +17 -0
- package/Package.swift +28 -0
- package/README.md +117 -0
- package/android/build.gradle +59 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/ee/forgr/autofill_password/SavePasswordPlugin.java +111 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +88 -0
- package/dist/esm/definitions.d.ts +31 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +5 -0
- package/dist/esm/web.js +7 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +21 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +24 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/SavePasswordPlugin/SavePasswordPlugin.swift +62 -0
- package/ios/Tests/SavePasswordPluginTests/SavePasswordPluginTests.swift +15 -0
- package/package.json +85 -0
|
@@ -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 = 'CapgoCapacitorAutofillSavePassword'
|
|
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 = '13.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: "CapgoCapacitorAutofillSavePassword",
|
|
6
|
+
platforms: [.iOS(.v13)],
|
|
7
|
+
products: [
|
|
8
|
+
.library(
|
|
9
|
+
name: "CapgoCapacitorAutofillSavePassword",
|
|
10
|
+
targets: ["SavePasswordPlugin"])
|
|
11
|
+
],
|
|
12
|
+
dependencies: [
|
|
13
|
+
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", branch: "main")
|
|
14
|
+
],
|
|
15
|
+
targets: [
|
|
16
|
+
.target(
|
|
17
|
+
name: "SavePasswordPlugin",
|
|
18
|
+
dependencies: [
|
|
19
|
+
.product(name: "Capacitor", package: "capacitor-swift-pm"),
|
|
20
|
+
.product(name: "Cordova", package: "capacitor-swift-pm")
|
|
21
|
+
],
|
|
22
|
+
path: "ios/Sources/SavePasswordPlugin"),
|
|
23
|
+
.testTarget(
|
|
24
|
+
name: "SavePasswordPluginTests",
|
|
25
|
+
dependencies: ["SavePasswordPlugin"],
|
|
26
|
+
path: "ios/Tests/SavePasswordPluginTests")
|
|
27
|
+
]
|
|
28
|
+
)
|
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# @capgo/capacitor-autofill-save-password
|
|
2
|
+
|
|
3
|
+
Prompt to display dialog for saving password to keychain from webview app
|
|
4
|
+
|
|
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
|
+
|
|
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>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
Fork of original plugin to work with Capacitor 7
|
|
13
|
+
|
|
14
|
+
IOS work for old versions and 18.3
|
|
15
|
+
|
|
16
|
+
Android still WIP
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @capgo/capacitor-autofill-save-password
|
|
22
|
+
npx cap sync
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Prerequisite
|
|
26
|
+
You must set up your app’s associated domains. To learn how to set up your app’s associated domains, see [Supporting Associated Domains](https://developer.apple.com/documentation/safariservices/supporting_associated_domains) in Apple Developer document.
|
|
27
|
+
|
|
28
|
+
Then add in your `App.entitlements`
|
|
29
|
+
|
|
30
|
+
```xml
|
|
31
|
+
<key>com.apple.developer.associated-domains</key>
|
|
32
|
+
<array>
|
|
33
|
+
<string>webcredentials:YOURDOMAIN</string>
|
|
34
|
+
</array>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
To associate your domain to your app.
|
|
38
|
+
|
|
39
|
+
## How to use
|
|
40
|
+
```ts
|
|
41
|
+
import { Capacitor } from '@capacitor/core';
|
|
42
|
+
import { SavePassword } from '@capgo/capacitor-autofill-save-password';
|
|
43
|
+
|
|
44
|
+
login(username: string, password: string) {
|
|
45
|
+
// your login logic here
|
|
46
|
+
|
|
47
|
+
SavePassword.promptDialog({
|
|
48
|
+
username: username,
|
|
49
|
+
password: password,
|
|
50
|
+
})
|
|
51
|
+
.then(() => console.log('promptDialog success'))
|
|
52
|
+
.catch((err) => console.error('promptDialog failure', err));
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
### Android
|
|
58
|
+
|
|
59
|
+
Add `apply plugin: 'com.google.gms.google-services'` beneath `apply plugin: 'com.android.application'` in `android/app/build.gradle`
|
|
60
|
+
|
|
61
|
+
this will allow the plugin to import the proper lib.
|
|
62
|
+
|
|
63
|
+
Then you need to make sure you did set properly your domain and did add google-services.json.
|
|
64
|
+
|
|
65
|
+
guide here https://developer.android.com/identity/sign-in/credential-manager
|
|
66
|
+
|
|
67
|
+
You need to have the file at this path `android/google-services.json` set, if you dont use firebase add empty json
|
|
68
|
+
|
|
69
|
+
then add your domain in `example-app/android/app/src/main/res/values/strings.xml`
|
|
70
|
+
|
|
71
|
+
with
|
|
72
|
+
```xml
|
|
73
|
+
<string name="asset_statements" translatable="false">
|
|
74
|
+
[{
|
|
75
|
+
\"include\": \"https://YOURDOMAIN/.well-known/assetlinks.json\"
|
|
76
|
+
}]
|
|
77
|
+
</string>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## API
|
|
81
|
+
|
|
82
|
+
<docgen-index>
|
|
83
|
+
|
|
84
|
+
* [`promptDialog(...)`](#promptdialog)
|
|
85
|
+
* [Interfaces](#interfaces)
|
|
86
|
+
|
|
87
|
+
</docgen-index>
|
|
88
|
+
|
|
89
|
+
<docgen-api>
|
|
90
|
+
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
|
|
91
|
+
|
|
92
|
+
### promptDialog(...)
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
promptDialog(options: Options) => Promise<void>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Save a password to the keychain.
|
|
99
|
+
|
|
100
|
+
| Param | Type | Description |
|
|
101
|
+
| ------------- | ------------------------------------------- | ------------------------------- |
|
|
102
|
+
| **`options`** | <code><a href="#options">Options</a></code> | - The options for the password. |
|
|
103
|
+
|
|
104
|
+
--------------------
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
### Interfaces
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
#### Options
|
|
111
|
+
|
|
112
|
+
| Prop | Type | Description |
|
|
113
|
+
| -------------- | ------------------- | --------------------- |
|
|
114
|
+
| **`username`** | <code>string</code> | The username to save. |
|
|
115
|
+
| **`password`** | <code>string</code> | The password to save. |
|
|
116
|
+
|
|
117
|
+
</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.6.1'
|
|
4
|
+
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.1.5'
|
|
5
|
+
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.5.1'
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
buildscript {
|
|
9
|
+
repositories {
|
|
10
|
+
google()
|
|
11
|
+
mavenCentral()
|
|
12
|
+
}
|
|
13
|
+
dependencies {
|
|
14
|
+
classpath 'com.android.tools.build:gradle:8.2.1'
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
apply plugin: 'com.android.library'
|
|
19
|
+
|
|
20
|
+
android {
|
|
21
|
+
namespace "ee.forgr.autofill_password"
|
|
22
|
+
compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 34
|
|
23
|
+
defaultConfig {
|
|
24
|
+
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 22
|
|
25
|
+
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 34
|
|
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.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
|
+
repositories {
|
|
46
|
+
google()
|
|
47
|
+
mavenCentral()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
dependencies {
|
|
51
|
+
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
52
|
+
implementation project(':capacitor-android')
|
|
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"
|
|
56
|
+
testImplementation "junit:junit:$junitVersion"
|
|
57
|
+
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
58
|
+
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
59
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
package ee.forgr.autofill_password;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.os.Build;
|
|
5
|
+
import android.util.Log;
|
|
6
|
+
|
|
7
|
+
import androidx.credentials.CredentialManager;
|
|
8
|
+
import androidx.credentials.CreatePasswordRequest;
|
|
9
|
+
import androidx.credentials.CreateCredentialResponse;
|
|
10
|
+
import androidx.credentials.PendingGetCredentialRequest;
|
|
11
|
+
import androidx.credentials.CredentialManagerCallback;
|
|
12
|
+
import androidx.credentials.exceptions.CreateCredentialException;
|
|
13
|
+
|
|
14
|
+
import com.getcapacitor.Plugin;
|
|
15
|
+
import com.getcapacitor.PluginCall;
|
|
16
|
+
import com.getcapacitor.PluginMethod;
|
|
17
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
18
|
+
|
|
19
|
+
import java.util.HashMap;
|
|
20
|
+
import java.util.Map;
|
|
21
|
+
|
|
22
|
+
import androidx.core.content.ContextCompat;
|
|
23
|
+
|
|
24
|
+
@CapacitorPlugin(name = "SavePassword")
|
|
25
|
+
public class SavePasswordPlugin extends Plugin {
|
|
26
|
+
private static final String TAG = "CredentialManager";
|
|
27
|
+
private CredentialManager credentialManager;
|
|
28
|
+
private Map<String, PendingGetCredentialRequest> pendingRequestsByElementId = new HashMap<>();
|
|
29
|
+
|
|
30
|
+
@Override
|
|
31
|
+
public void load() {
|
|
32
|
+
super.load();
|
|
33
|
+
try {
|
|
34
|
+
credentialManager = CredentialManager.create(getContext());
|
|
35
|
+
} catch (Exception e) {
|
|
36
|
+
Log.e(TAG, "Error initializing CredentialManager", e);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@PluginMethod
|
|
41
|
+
public void promptDialog(final PluginCall call) {
|
|
42
|
+
if (!isCredentialManagerAvailable(call)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
String username = call.getString("username");
|
|
47
|
+
String password = call.getString("password");
|
|
48
|
+
|
|
49
|
+
if (username == null || username.isEmpty()) {
|
|
50
|
+
call.reject("Username is required");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (password == null || password.isEmpty()) {
|
|
55
|
+
call.reject("Password is required");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
// Build request directly with username & password (API 1.5.0 signature)
|
|
61
|
+
CreatePasswordRequest request = new CreatePasswordRequest(username, password);
|
|
62
|
+
|
|
63
|
+
// Execute on main thread
|
|
64
|
+
bridge.executeOnMainThread(() -> {
|
|
65
|
+
Activity activity = getActivity();
|
|
66
|
+
if (activity == null) {
|
|
67
|
+
call.reject("Activity not available");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
credentialManager.createCredentialAsync(
|
|
73
|
+
activity,
|
|
74
|
+
request,
|
|
75
|
+
null,
|
|
76
|
+
ContextCompat.getMainExecutor(getContext()),
|
|
77
|
+
new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() {
|
|
78
|
+
@Override
|
|
79
|
+
public void onResult(CreateCredentialResponse response) {
|
|
80
|
+
call.resolve();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
@Override
|
|
84
|
+
public void onError(CreateCredentialException e) {
|
|
85
|
+
call.reject("Error saving credential: " + e.getMessage(), e);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
} catch (Exception e) {
|
|
90
|
+
call.reject("Error saving credential", e);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
} catch (Exception e) {
|
|
94
|
+
call.reject("Error building save credential request", e);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private boolean isCredentialManagerAvailable(PluginCall call) {
|
|
99
|
+
if (credentialManager == null) {
|
|
100
|
+
call.reject("Credential Manager not available on this device");
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
|
105
|
+
call.reject("Credential Manager requires Android API level 28 or higher");
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
File without changes
|
package/dist/docs.json
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"api": {
|
|
3
|
+
"name": "SavePasswordPlugin",
|
|
4
|
+
"slug": "savepasswordplugin",
|
|
5
|
+
"docs": "",
|
|
6
|
+
"tags": [
|
|
7
|
+
{
|
|
8
|
+
"text": "SavePasswordPlugin",
|
|
9
|
+
"name": "interface"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"text": "Capacitor plugin for saving passwords to the keychain.",
|
|
13
|
+
"name": "description"
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"methods": [
|
|
17
|
+
{
|
|
18
|
+
"name": "promptDialog",
|
|
19
|
+
"signature": "(options: Options) => Promise<void>",
|
|
20
|
+
"parameters": [
|
|
21
|
+
{
|
|
22
|
+
"name": "options",
|
|
23
|
+
"docs": "- The options for the password.",
|
|
24
|
+
"type": "Options"
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"returns": "Promise<void>",
|
|
28
|
+
"tags": [
|
|
29
|
+
{
|
|
30
|
+
"name": "param",
|
|
31
|
+
"text": "options - The options for the password."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "returns",
|
|
35
|
+
"text": "Success status"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "example",
|
|
39
|
+
"text": "await SavePassword.promptDialog({\n username: 'your-username',\n password: 'your-password'\n});"
|
|
40
|
+
}
|
|
41
|
+
],
|
|
42
|
+
"docs": "Save a password to the keychain.",
|
|
43
|
+
"complexTypes": [
|
|
44
|
+
"Options"
|
|
45
|
+
],
|
|
46
|
+
"slug": "promptdialog"
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"properties": []
|
|
50
|
+
},
|
|
51
|
+
"interfaces": [
|
|
52
|
+
{
|
|
53
|
+
"name": "Options",
|
|
54
|
+
"slug": "options",
|
|
55
|
+
"docs": "",
|
|
56
|
+
"tags": [
|
|
57
|
+
{
|
|
58
|
+
"text": "Options",
|
|
59
|
+
"name": "interface"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"text": "The options for the prompt.",
|
|
63
|
+
"name": "description"
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"methods": [],
|
|
67
|
+
"properties": [
|
|
68
|
+
{
|
|
69
|
+
"name": "username",
|
|
70
|
+
"tags": [],
|
|
71
|
+
"docs": "The username to save.",
|
|
72
|
+
"complexTypes": [],
|
|
73
|
+
"type": "string"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "password",
|
|
77
|
+
"tags": [],
|
|
78
|
+
"docs": "The password to save.",
|
|
79
|
+
"complexTypes": [],
|
|
80
|
+
"type": "string"
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
"enums": [],
|
|
86
|
+
"typeAliases": [],
|
|
87
|
+
"pluginConfigs": []
|
|
88
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @interface Options
|
|
3
|
+
* @description The options for the prompt.
|
|
4
|
+
*/
|
|
5
|
+
export interface Options {
|
|
6
|
+
/**
|
|
7
|
+
* The username to save.
|
|
8
|
+
*/
|
|
9
|
+
username: string;
|
|
10
|
+
/**
|
|
11
|
+
* The password to save.
|
|
12
|
+
*/
|
|
13
|
+
password: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* @interface SavePasswordPlugin
|
|
17
|
+
* @description Capacitor plugin for saving passwords to the keychain.
|
|
18
|
+
*/
|
|
19
|
+
export interface SavePasswordPlugin {
|
|
20
|
+
/**
|
|
21
|
+
* Save a password to the keychain.
|
|
22
|
+
* @param {Options} options - The options for the password.
|
|
23
|
+
* @returns {Promise<void>} Success status
|
|
24
|
+
* @example
|
|
25
|
+
* await SavePassword.promptDialog({
|
|
26
|
+
* username: 'your-username',
|
|
27
|
+
* password: 'your-password'
|
|
28
|
+
* });
|
|
29
|
+
*/
|
|
30
|
+
promptDialog(options: Options): Promise<void>;
|
|
31
|
+
}
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { registerPlugin } from '@capacitor/core';
|
|
2
|
+
const SavePassword = registerPlugin('SavePassword', {
|
|
3
|
+
web: () => import('./web').then((m) => new m.SavePasswordWeb()),
|
|
4
|
+
});
|
|
5
|
+
export * from './definitions';
|
|
6
|
+
export { SavePassword };
|
|
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,YAAY,GAAG,cAAc,CAAqB,cAAc,EAAE;IACtE,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,eAAe,EAAE,CAAC;CAChE,CAAC,CAAC;AAEH,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,CAAC","sourcesContent":["import { registerPlugin } from '@capacitor/core';\n\nimport type { SavePasswordPlugin } from './definitions';\n\nconst SavePassword = registerPlugin<SavePasswordPlugin>('SavePassword', {\n web: () => import('./web').then((m) => new m.SavePasswordWeb()),\n});\n\nexport * from './definitions';\nexport { SavePassword };\n"]}
|
package/dist/esm/web.js
ADDED
|
@@ -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,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"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@capacitor/core');
|
|
4
|
+
|
|
5
|
+
const SavePassword = core.registerPlugin('SavePassword', {
|
|
6
|
+
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.SavePasswordWeb()),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
class SavePasswordWeb extends core.WebPlugin {
|
|
10
|
+
async promptDialog(options) {
|
|
11
|
+
throw new Error("Not implemented on web" + JSON.stringify(options));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
16
|
+
__proto__: null,
|
|
17
|
+
SavePasswordWeb: SavePasswordWeb
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
exports.SavePassword = SavePassword;
|
|
21
|
+
//# 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 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;;;;;;;;;"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
var capacitorSavePassword = (function (exports, core) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const SavePassword = core.registerPlugin('SavePassword', {
|
|
5
|
+
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.SavePasswordWeb()),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
class SavePasswordWeb extends core.WebPlugin {
|
|
9
|
+
async promptDialog(options) {
|
|
10
|
+
throw new Error("Not implemented on web" + JSON.stringify(options));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
15
|
+
__proto__: null,
|
|
16
|
+
SavePasswordWeb: SavePasswordWeb
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
exports.SavePassword = SavePassword;
|
|
20
|
+
|
|
21
|
+
return exports;
|
|
22
|
+
|
|
23
|
+
})({}, capacitorExports);
|
|
24
|
+
//# 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 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;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,62 @@
|
|
|
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(SavePasswordPlugin)
|
|
9
|
+
public class SavePasswordPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
10
|
+
public let identifier = "SavePasswordPlugin"
|
|
11
|
+
|
|
12
|
+
public let jsName = "SavePassword"
|
|
13
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
14
|
+
CAPPluginMethod(name: "promptDialog", returnType: CAPPluginReturnPromise)
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
@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
|
+
|
|
24
|
+
// Defer removal so the system registers the fields before they disappear
|
|
25
|
+
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()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
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()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
@testable import SavePasswordPlugin
|
|
3
|
+
|
|
4
|
+
class SavePasswordTests: 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 = SavePassword()
|
|
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,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@capgo/capacitor-autofill-save-password",
|
|
3
|
+
"version": "6.0.0",
|
|
4
|
+
"description": "Prompt to display dialog for saving password to keychain from webview app",
|
|
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
|
+
"CapgoCapacitorAutofillSavePassword.podspec"
|
|
17
|
+
],
|
|
18
|
+
"author": "Martin Donadieu <martin@capgo.app>",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/Cap-go/capacitor-autofill-save-password.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/Cap-go/capacitor-autofill-save-password/issues"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"capacitor",
|
|
29
|
+
"plugin",
|
|
30
|
+
"native",
|
|
31
|
+
"autofill",
|
|
32
|
+
"save",
|
|
33
|
+
"password",
|
|
34
|
+
"ios",
|
|
35
|
+
"android"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
39
|
+
"verify:ios": "xcodebuild -scheme CapgoCapacitorAutofillSavePassword -destination generic/platform=iOS",
|
|
40
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
41
|
+
"verify:web": "npm run build",
|
|
42
|
+
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
43
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
|
|
44
|
+
"eslint": "eslint . --ext ts",
|
|
45
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java",
|
|
46
|
+
"swiftlint": "node-swiftlint",
|
|
47
|
+
"docgen": "docgen --api SavePasswordPlugin --output-readme README.md --output-json dist/docs.json",
|
|
48
|
+
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
49
|
+
"clean": "rimraf ./dist",
|
|
50
|
+
"watch": "tsc --watch",
|
|
51
|
+
"prepublishOnly": "npm run build"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@capacitor/android": "^6.0.0",
|
|
55
|
+
"@capacitor/core": "^6.0.0",
|
|
56
|
+
"@capacitor/docgen": "^0.3.0",
|
|
57
|
+
"@capacitor/ios": "^6.0.0",
|
|
58
|
+
"@ionic/eslint-config": "^0.4.0",
|
|
59
|
+
"@ionic/prettier-config": "^4.0.0",
|
|
60
|
+
"@ionic/swiftlint-config": "^2.0.0",
|
|
61
|
+
"eslint": "^8.57.0",
|
|
62
|
+
"prettier": "^3.4.2",
|
|
63
|
+
"prettier-plugin-java": "^2.6.6",
|
|
64
|
+
"rimraf": "^6.0.1",
|
|
65
|
+
"rollup": "^4.30.1",
|
|
66
|
+
"swiftlint": "^2.0.0",
|
|
67
|
+
"typescript": "~4.1.5"
|
|
68
|
+
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"@capacitor/core": ">=6.0.0"
|
|
71
|
+
},
|
|
72
|
+
"prettier": "@ionic/prettier-config",
|
|
73
|
+
"swiftlint": "@ionic/swiftlint-config",
|
|
74
|
+
"eslintConfig": {
|
|
75
|
+
"extends": "@ionic/eslint-config/recommended"
|
|
76
|
+
},
|
|
77
|
+
"capacitor": {
|
|
78
|
+
"ios": {
|
|
79
|
+
"src": "ios"
|
|
80
|
+
},
|
|
81
|
+
"android": {
|
|
82
|
+
"src": "android"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|