@capawesome/capacitor-password-autofill 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.
- package/CapawesomeCapacitorPasswordAutofill.podspec +17 -0
- package/LICENSE +21 -0
- package/Package.swift +28 -0
- package/README.md +193 -0
- package/android/build.gradle +61 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passwordautofill/PasswordAutofill.java +49 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passwordautofill/PasswordAutofillPlugin.java +64 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passwordautofill/classes/CustomException.java +20 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passwordautofill/classes/CustomExceptions.java +15 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passwordautofill/classes/options/SavePasswordOptions.java +47 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passwordautofill/interfaces/Callback.java +5 -0
- package/android/src/main/java/io/capawesome/capacitorjs/plugins/passwordautofill/interfaces/EmptyCallback.java +5 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +97 -0
- package/dist/esm/definitions.d.ts +66 -0
- package/dist/esm/definitions.js +19 -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 +40 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +43 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/Classes/Options/SavePasswordOptions.swift +35 -0
- package/ios/Plugin/Enums/CustomError.swift +32 -0
- package/ios/Plugin/Info.plist +24 -0
- package/ios/Plugin/PasswordAutofill.swift +23 -0
- package/ios/Plugin/PasswordAutofillPlugin.swift +44 -0
- package/ios/Plugin/Protocols/Result.swift +5 -0
- package/package.json +91 -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 = 'CapawesomeCapacitorPasswordAutofill'
|
|
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/Plugin/**/*.{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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Robin Genz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/Package.swift
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// swift-tools-version: 5.9
|
|
2
|
+
import PackageDescription
|
|
3
|
+
|
|
4
|
+
let package = Package(
|
|
5
|
+
name: "CapawesomeCapacitorPasswordAutofill",
|
|
6
|
+
platforms: [.iOS(.v15)],
|
|
7
|
+
products: [
|
|
8
|
+
.library(
|
|
9
|
+
name: "CapawesomeCapacitorPasswordAutofill",
|
|
10
|
+
targets: ["PasswordAutofillPlugin"])
|
|
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: "PasswordAutofillPlugin",
|
|
18
|
+
dependencies: [
|
|
19
|
+
.product(name: "Capacitor", package: "capacitor-swift-pm"),
|
|
20
|
+
.product(name: "Cordova", package: "capacitor-swift-pm")
|
|
21
|
+
],
|
|
22
|
+
path: "ios/Plugin"),
|
|
23
|
+
.testTarget(
|
|
24
|
+
name: "PasswordAutofillPluginTests",
|
|
25
|
+
dependencies: ["PasswordAutofillPlugin"],
|
|
26
|
+
path: "ios/PluginTests")
|
|
27
|
+
]
|
|
28
|
+
)
|
package/README.md
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# @capawesome/capacitor-password-autofill
|
|
2
|
+
|
|
3
|
+
Capacitor plugin for saving passwords to the platform credential store.
|
|
4
|
+
|
|
5
|
+
This plugin lets you **save into** the platform autofill system (iCloud Keychain on iOS, Google Password Manager on Android) after a successful in-app login. It does **not** fill forms — it solves the well-known problem that WebView-based apps never trigger the native "Save password?" prompt.
|
|
6
|
+
|
|
7
|
+
<div class="capawesome-z29o10a">
|
|
8
|
+
<a href="https://cloud.capawesome.io/" target="_blank">
|
|
9
|
+
<img alt="Deliver Live Updates to your Capacitor app with Capawesome Cloud" src="https://cloud.capawesome.io/assets/banners/cloud-build-and-deploy-capacitor-apps.png?t=1" />
|
|
10
|
+
</a>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
We are proud to offer one of the most complete and feature-rich Capacitor plugins for password autofill. Here are some of the key features:
|
|
16
|
+
|
|
17
|
+
- 🖥️ **Cross-platform**: Supports Android and iOS.
|
|
18
|
+
- 🔐 **Credential saving**: Save a username and password to the platform credential store.
|
|
19
|
+
- 🎯 **Deterministic**: Trigger the save prompt explicitly after a successful login, even for non-form flows.
|
|
20
|
+
- 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS.
|
|
21
|
+
- 🔁 **Up-to-date**: Always supports the latest Capacitor version.
|
|
22
|
+
|
|
23
|
+
Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look!
|
|
24
|
+
|
|
25
|
+
## Newsletter
|
|
26
|
+
|
|
27
|
+
Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/).
|
|
28
|
+
|
|
29
|
+
## Compatibility
|
|
30
|
+
|
|
31
|
+
| Plugin Version | Capacitor Version | Status |
|
|
32
|
+
| -------------- | ----------------- | -------------- |
|
|
33
|
+
| 0.x.x | >=8.x.x | Active support |
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
You can use our **AI-Assisted Setup** to install the plugin.
|
|
38
|
+
Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npx skills add capawesome-team/skills --skill capacitor-plugins
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Then use the following prompt:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-password-autofill` plugin in my project.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install @capawesome/capacitor-password-autofill
|
|
54
|
+
npx cap sync
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This plugin is only available on **Android** and **iOS**. On Web, all methods reject as unimplemented.
|
|
58
|
+
|
|
59
|
+
### Android
|
|
60
|
+
|
|
61
|
+
#### Variables
|
|
62
|
+
|
|
63
|
+
This plugin will use the following project variables (defined in your app's `variables.gradle` file):
|
|
64
|
+
|
|
65
|
+
- `$androidxCredentials` version of `androidx.credentials:credentials` and `androidx.credentials:credentials-play-services-auth` (default: `1.5.0`)
|
|
66
|
+
|
|
67
|
+
No further setup is required. On Android, saving a credential presents the system "Save password?" prompt provided by the active credential provider (e.g. Google Password Manager).
|
|
68
|
+
|
|
69
|
+
### iOS
|
|
70
|
+
|
|
71
|
+
The Shared Web Credentials API requires the **Associated Domains** capability and a matching `apple-app-site-association` (AASA) file. Without this setup, `savePassword(...)` cannot save the credential.
|
|
72
|
+
|
|
73
|
+
#### Associated Domains
|
|
74
|
+
|
|
75
|
+
Add the Associated Domains entitlement to your app and include a `webcredentials:` entry for each domain you want to save credentials for:
|
|
76
|
+
|
|
77
|
+
```xml
|
|
78
|
+
<key>com.apple.developer.associated-domains</key>
|
|
79
|
+
<array>
|
|
80
|
+
<string>webcredentials:example.com</string>
|
|
81
|
+
</array>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The `domain` you pass to `savePassword(...)` must match one of these entries (without the `webcredentials:` prefix).
|
|
85
|
+
|
|
86
|
+
#### Apple App Site Association
|
|
87
|
+
|
|
88
|
+
Host an `apple-app-site-association` file at `https://example.com/.well-known/apple-app-site-association` that lists your app under the `webcredentials` service:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"webcredentials": {
|
|
93
|
+
"apps": ["TEAMID.com.example.app"]
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Replace `TEAMID` with your Apple Developer Team ID and `com.example.app` with your app's bundle identifier. See the [Apple documentation](https://developer.apple.com/documentation/xcode/supporting-associated-domains) for more information.
|
|
99
|
+
|
|
100
|
+
## Configuration
|
|
101
|
+
|
|
102
|
+
No configuration required for this plugin.
|
|
103
|
+
|
|
104
|
+
## Usage
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { PasswordAutofill } from '@capawesome/capacitor-password-autofill';
|
|
108
|
+
|
|
109
|
+
const savePassword = async () => {
|
|
110
|
+
await PasswordAutofill.savePassword({
|
|
111
|
+
domain: 'example.com',
|
|
112
|
+
username: 'jane.doe@example.com',
|
|
113
|
+
password: 'super-secret',
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## API
|
|
119
|
+
|
|
120
|
+
<docgen-index>
|
|
121
|
+
|
|
122
|
+
* [`savePassword(...)`](#savepassword)
|
|
123
|
+
* [Interfaces](#interfaces)
|
|
124
|
+
|
|
125
|
+
</docgen-index>
|
|
126
|
+
|
|
127
|
+
<docgen-api>
|
|
128
|
+
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->
|
|
129
|
+
|
|
130
|
+
### savePassword(...)
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
savePassword(options: SavePasswordOptions) => Promise<void>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Save a username and password to the platform credential store.
|
|
137
|
+
|
|
138
|
+
On iOS, the credential is saved to the iCloud Keychain via the
|
|
139
|
+
Shared Web Credentials API. This requires the Associated Domains
|
|
140
|
+
entitlement with a `webcredentials:` entry and a matching
|
|
141
|
+
`apple-app-site-association` file hosted on your domain.
|
|
142
|
+
|
|
143
|
+
On Android, the credential is saved to the Google Password Manager
|
|
144
|
+
(or another provider) via the Credential Manager API. This presents
|
|
145
|
+
the system "Save password?" prompt to the user.
|
|
146
|
+
|
|
147
|
+
Only available on Android and iOS.
|
|
148
|
+
|
|
149
|
+
| Param | Type |
|
|
150
|
+
| ------------- | ------------------------------------------------------------------- |
|
|
151
|
+
| **`options`** | <code><a href="#savepasswordoptions">SavePasswordOptions</a></code> |
|
|
152
|
+
|
|
153
|
+
**Since:** 0.1.0
|
|
154
|
+
|
|
155
|
+
--------------------
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
### Interfaces
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
#### SavePasswordOptions
|
|
162
|
+
|
|
163
|
+
| Prop | Type | Description | Since |
|
|
164
|
+
| -------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- |
|
|
165
|
+
| **`domain`** | <code>string</code> | The domain to associate the credential with. This must match one of the domains in your app's Associated Domains entitlement (without the `webcredentials:` prefix). Only available on iOS (required). | 0.1.0 |
|
|
166
|
+
| **`password`** | <code>string</code> | The password to save. | 0.1.0 |
|
|
167
|
+
| **`username`** | <code>string</code> | The username to save. | 0.1.0 |
|
|
168
|
+
|
|
169
|
+
</docgen-api>
|
|
170
|
+
|
|
171
|
+
## When to use this plugin vs. the official guide
|
|
172
|
+
|
|
173
|
+
The [official Capacitor autofill guide](https://capacitorjs.com/docs/guides/autofill-credentials) saves credentials by relying on the operating system's form heuristics. This requires changing `server.hostname` to your real domain, which:
|
|
174
|
+
|
|
175
|
+
- changes the WebView origin and therefore wipes existing users' `localStorage`, `IndexedDB`, and cookies, and
|
|
176
|
+
- can break OAuth redirect allowlists.
|
|
177
|
+
|
|
178
|
+
It also depends on iOS detecting a form submission followed by a navigation — something typical single-page app logins (`fetch()` + `preventDefault()`) never trigger.
|
|
179
|
+
|
|
180
|
+
This plugin takes a different approach: **deterministic credential saving without changing the WebView origin**. Your app keeps `localhost` as its origin, and you call `savePassword(...)` explicitly after a successful login. This also covers non-form flows such as social signup with generated passwords.
|
|
181
|
+
|
|
182
|
+
What this plugin does **not** replace:
|
|
183
|
+
|
|
184
|
+
- The Associated Domains / `apple-app-site-association` setup is required in both approaches.
|
|
185
|
+
- Autofill (reading credentials back into a form) still benefits from the `autocomplete` attributes described in the official guide.
|
|
186
|
+
|
|
187
|
+
## Changelog
|
|
188
|
+
|
|
189
|
+
See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/password-autofill/CHANGELOG.md).
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/password-autofill/LICENSE).
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
androidxCredentialsVersion = project.hasProperty('androidxCredentialsVersion') ? rootProject.ext.androidxCredentialsVersion : '1.5.0'
|
|
5
|
+
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
|
|
6
|
+
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
buildscript {
|
|
10
|
+
repositories {
|
|
11
|
+
google()
|
|
12
|
+
mavenCentral()
|
|
13
|
+
}
|
|
14
|
+
dependencies {
|
|
15
|
+
classpath 'com.android.tools.build:gradle:8.13.0'
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
apply plugin: 'com.android.library'
|
|
20
|
+
|
|
21
|
+
android {
|
|
22
|
+
namespace = "io.capawesome.capacitorjs.plugins.passwordautofill"
|
|
23
|
+
compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
|
|
24
|
+
defaultConfig {
|
|
25
|
+
minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 24
|
|
26
|
+
targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
|
|
27
|
+
versionCode 1
|
|
28
|
+
versionName "1.0"
|
|
29
|
+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
30
|
+
}
|
|
31
|
+
buildTypes {
|
|
32
|
+
release {
|
|
33
|
+
minifyEnabled false
|
|
34
|
+
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
lintOptions {
|
|
38
|
+
abortOnError = false
|
|
39
|
+
}
|
|
40
|
+
compileOptions {
|
|
41
|
+
sourceCompatibility JavaVersion.VERSION_21
|
|
42
|
+
targetCompatibility JavaVersion.VERSION_21
|
|
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
|
+
implementation "androidx.credentials:credentials:$androidxCredentialsVersion"
|
|
57
|
+
implementation "androidx.credentials:credentials-play-services-auth:$androidxCredentialsVersion"
|
|
58
|
+
testImplementation "junit:junit:$junitVersion"
|
|
59
|
+
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
|
60
|
+
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
|
61
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
package io.capawesome.capacitorjs.plugins.passwordautofill;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import androidx.credentials.CreateCredentialResponse;
|
|
5
|
+
import androidx.credentials.CreatePasswordRequest;
|
|
6
|
+
import androidx.credentials.CredentialManager;
|
|
7
|
+
import androidx.credentials.CredentialManagerCallback;
|
|
8
|
+
import androidx.credentials.exceptions.CreateCredentialCancellationException;
|
|
9
|
+
import androidx.credentials.exceptions.CreateCredentialException;
|
|
10
|
+
import io.capawesome.capacitorjs.plugins.passwordautofill.classes.CustomExceptions;
|
|
11
|
+
import io.capawesome.capacitorjs.plugins.passwordautofill.classes.options.SavePasswordOptions;
|
|
12
|
+
import io.capawesome.capacitorjs.plugins.passwordautofill.interfaces.EmptyCallback;
|
|
13
|
+
|
|
14
|
+
public class PasswordAutofill {
|
|
15
|
+
|
|
16
|
+
@NonNull
|
|
17
|
+
private final PasswordAutofillPlugin plugin;
|
|
18
|
+
|
|
19
|
+
public PasswordAutofill(@NonNull PasswordAutofillPlugin plugin) {
|
|
20
|
+
this.plugin = plugin;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public void savePassword(@NonNull SavePasswordOptions options, @NonNull EmptyCallback callback) {
|
|
24
|
+
CreatePasswordRequest request = new CreatePasswordRequest(options.getUsername(), options.getPassword());
|
|
25
|
+
CredentialManager credentialManager = CredentialManager.create(plugin.getContext());
|
|
26
|
+
credentialManager.createCredentialAsync(
|
|
27
|
+
plugin.getActivity(),
|
|
28
|
+
request,
|
|
29
|
+
null,
|
|
30
|
+
Runnable::run,
|
|
31
|
+
new CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException>() {
|
|
32
|
+
@Override
|
|
33
|
+
public void onResult(@NonNull CreateCredentialResponse response) {
|
|
34
|
+
callback.success();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@Override
|
|
38
|
+
public void onError(@NonNull CreateCredentialException exception) {
|
|
39
|
+
if (exception instanceof CreateCredentialCancellationException) {
|
|
40
|
+
callback.error(CustomExceptions.CANCELED);
|
|
41
|
+
} else {
|
|
42
|
+
String message = exception.getMessage();
|
|
43
|
+
callback.error(CustomExceptions.saveFailed(message == null ? "The credential could not be saved." : message));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
package io.capawesome.capacitorjs.plugins.passwordautofill;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import com.getcapacitor.Logger;
|
|
5
|
+
import com.getcapacitor.Plugin;
|
|
6
|
+
import com.getcapacitor.PluginCall;
|
|
7
|
+
import com.getcapacitor.PluginMethod;
|
|
8
|
+
import com.getcapacitor.annotation.CapacitorPlugin;
|
|
9
|
+
import io.capawesome.capacitorjs.plugins.passwordautofill.classes.CustomException;
|
|
10
|
+
import io.capawesome.capacitorjs.plugins.passwordautofill.classes.options.SavePasswordOptions;
|
|
11
|
+
import io.capawesome.capacitorjs.plugins.passwordautofill.interfaces.EmptyCallback;
|
|
12
|
+
|
|
13
|
+
@CapacitorPlugin(name = "PasswordAutofill")
|
|
14
|
+
public class PasswordAutofillPlugin extends Plugin {
|
|
15
|
+
|
|
16
|
+
public static final String ERROR_UNKNOWN_ERROR = "An unknown error has occurred.";
|
|
17
|
+
public static final String TAG = "PasswordAutofillPlugin";
|
|
18
|
+
|
|
19
|
+
private PasswordAutofill implementation;
|
|
20
|
+
|
|
21
|
+
@Override
|
|
22
|
+
public void load() {
|
|
23
|
+
super.load();
|
|
24
|
+
this.implementation = new PasswordAutofill(this);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@PluginMethod
|
|
28
|
+
public void savePassword(PluginCall call) {
|
|
29
|
+
try {
|
|
30
|
+
SavePasswordOptions options = new SavePasswordOptions(call);
|
|
31
|
+
EmptyCallback callback = new EmptyCallback() {
|
|
32
|
+
@Override
|
|
33
|
+
public void success() {
|
|
34
|
+
resolveCall(call);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@Override
|
|
38
|
+
public void error(Exception exception) {
|
|
39
|
+
rejectCall(call, exception);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
implementation.savePassword(options, callback);
|
|
43
|
+
} catch (Exception exception) {
|
|
44
|
+
rejectCall(call, exception);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private void rejectCall(@NonNull PluginCall call, @NonNull Exception exception) {
|
|
49
|
+
String message = exception.getMessage();
|
|
50
|
+
if (message == null) {
|
|
51
|
+
message = ERROR_UNKNOWN_ERROR;
|
|
52
|
+
}
|
|
53
|
+
String code = null;
|
|
54
|
+
if (exception instanceof CustomException) {
|
|
55
|
+
code = ((CustomException) exception).getCode();
|
|
56
|
+
}
|
|
57
|
+
Logger.error(TAG, message, exception);
|
|
58
|
+
call.reject(message, code);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private void resolveCall(@NonNull PluginCall call) {
|
|
62
|
+
call.resolve();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package io.capawesome.capacitorjs.plugins.passwordautofill.classes;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import androidx.annotation.Nullable;
|
|
5
|
+
|
|
6
|
+
public class CustomException extends Exception {
|
|
7
|
+
|
|
8
|
+
@Nullable
|
|
9
|
+
private final String code;
|
|
10
|
+
|
|
11
|
+
public CustomException(@Nullable String code, @NonNull String message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.code = code;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Nullable
|
|
17
|
+
public String getCode() {
|
|
18
|
+
return code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
package io.capawesome.capacitorjs.plugins.passwordautofill.classes;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
|
|
5
|
+
public class CustomExceptions {
|
|
6
|
+
|
|
7
|
+
public static final CustomException CANCELED = new CustomException("CANCELED", "The user canceled the save operation.");
|
|
8
|
+
public static final CustomException PASSWORD_MISSING = new CustomException(null, "password must be provided.");
|
|
9
|
+
public static final CustomException USERNAME_MISSING = new CustomException(null, "username must be provided.");
|
|
10
|
+
|
|
11
|
+
@NonNull
|
|
12
|
+
public static CustomException saveFailed(@NonNull String message) {
|
|
13
|
+
return new CustomException("SAVE_FAILED", message);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
package io.capawesome.capacitorjs.plugins.passwordautofill.classes.options;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import com.getcapacitor.PluginCall;
|
|
5
|
+
import io.capawesome.capacitorjs.plugins.passwordautofill.classes.CustomExceptions;
|
|
6
|
+
|
|
7
|
+
public class SavePasswordOptions {
|
|
8
|
+
|
|
9
|
+
@NonNull
|
|
10
|
+
private final String password;
|
|
11
|
+
|
|
12
|
+
@NonNull
|
|
13
|
+
private final String username;
|
|
14
|
+
|
|
15
|
+
public SavePasswordOptions(@NonNull PluginCall call) throws Exception {
|
|
16
|
+
this.password = getPasswordFromCall(call);
|
|
17
|
+
this.username = getUsernameFromCall(call);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@NonNull
|
|
21
|
+
public String getPassword() {
|
|
22
|
+
return password;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@NonNull
|
|
26
|
+
public String getUsername() {
|
|
27
|
+
return username;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@NonNull
|
|
31
|
+
private static String getPasswordFromCall(@NonNull PluginCall call) throws Exception {
|
|
32
|
+
String password = call.getString("password");
|
|
33
|
+
if (password == null) {
|
|
34
|
+
throw CustomExceptions.PASSWORD_MISSING;
|
|
35
|
+
}
|
|
36
|
+
return password;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@NonNull
|
|
40
|
+
private static String getUsernameFromCall(@NonNull PluginCall call) throws Exception {
|
|
41
|
+
String username = call.getString("username");
|
|
42
|
+
if (username == null) {
|
|
43
|
+
throw CustomExceptions.USERNAME_MISSING;
|
|
44
|
+
}
|
|
45
|
+
return username;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
File without changes
|
package/dist/docs.json
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"api": {
|
|
3
|
+
"name": "PasswordAutofillPlugin",
|
|
4
|
+
"slug": "passwordautofillplugin",
|
|
5
|
+
"docs": "",
|
|
6
|
+
"tags": [],
|
|
7
|
+
"methods": [
|
|
8
|
+
{
|
|
9
|
+
"name": "savePassword",
|
|
10
|
+
"signature": "(options: SavePasswordOptions) => Promise<void>",
|
|
11
|
+
"parameters": [
|
|
12
|
+
{
|
|
13
|
+
"name": "options",
|
|
14
|
+
"docs": "",
|
|
15
|
+
"type": "SavePasswordOptions"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"returns": "Promise<void>",
|
|
19
|
+
"tags": [
|
|
20
|
+
{
|
|
21
|
+
"name": "since",
|
|
22
|
+
"text": "0.1.0"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"docs": "Save a username and password to the platform credential store.\n\nOn iOS, the credential is saved to the iCloud Keychain via the\nShared Web Credentials API. This requires the Associated Domains\nentitlement with a `webcredentials:` entry and a matching\n`apple-app-site-association` file hosted on your domain.\n\nOn Android, the credential is saved to the Google Password Manager\n(or another provider) via the Credential Manager API. This presents\nthe system \"Save password?\" prompt to the user.\n\nOnly available on Android and iOS.",
|
|
26
|
+
"complexTypes": [
|
|
27
|
+
"SavePasswordOptions"
|
|
28
|
+
],
|
|
29
|
+
"slug": "savepassword"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"properties": []
|
|
33
|
+
},
|
|
34
|
+
"interfaces": [
|
|
35
|
+
{
|
|
36
|
+
"name": "SavePasswordOptions",
|
|
37
|
+
"slug": "savepasswordoptions",
|
|
38
|
+
"docs": "",
|
|
39
|
+
"tags": [
|
|
40
|
+
{
|
|
41
|
+
"text": "0.1.0",
|
|
42
|
+
"name": "since"
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"methods": [],
|
|
46
|
+
"properties": [
|
|
47
|
+
{
|
|
48
|
+
"name": "domain",
|
|
49
|
+
"tags": [
|
|
50
|
+
{
|
|
51
|
+
"text": "'example.com'",
|
|
52
|
+
"name": "example"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"text": "0.1.0",
|
|
56
|
+
"name": "since"
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"docs": "The domain to associate the credential with.\n\nThis must match one of the domains in your app's Associated Domains\nentitlement (without the `webcredentials:` prefix).\n\nOnly available on iOS (required).",
|
|
60
|
+
"complexTypes": [],
|
|
61
|
+
"type": "string | undefined"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "password",
|
|
65
|
+
"tags": [
|
|
66
|
+
{
|
|
67
|
+
"text": "0.1.0",
|
|
68
|
+
"name": "since"
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"docs": "The password to save.",
|
|
72
|
+
"complexTypes": [],
|
|
73
|
+
"type": "string"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "username",
|
|
77
|
+
"tags": [
|
|
78
|
+
{
|
|
79
|
+
"text": "'jane.doe@example.com'",
|
|
80
|
+
"name": "example"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"text": "0.1.0",
|
|
84
|
+
"name": "since"
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"docs": "The username to save.",
|
|
88
|
+
"complexTypes": [],
|
|
89
|
+
"type": "string"
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
"enums": [],
|
|
95
|
+
"typeAliases": [],
|
|
96
|
+
"pluginConfigs": []
|
|
97
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export interface PasswordAutofillPlugin {
|
|
2
|
+
/**
|
|
3
|
+
* Save a username and password to the platform credential store.
|
|
4
|
+
*
|
|
5
|
+
* On iOS, the credential is saved to the iCloud Keychain via the
|
|
6
|
+
* Shared Web Credentials API. This requires the Associated Domains
|
|
7
|
+
* entitlement with a `webcredentials:` entry and a matching
|
|
8
|
+
* `apple-app-site-association` file hosted on your domain.
|
|
9
|
+
*
|
|
10
|
+
* On Android, the credential is saved to the Google Password Manager
|
|
11
|
+
* (or another provider) via the Credential Manager API. This presents
|
|
12
|
+
* the system "Save password?" prompt to the user.
|
|
13
|
+
*
|
|
14
|
+
* Only available on Android and iOS.
|
|
15
|
+
*
|
|
16
|
+
* @since 0.1.0
|
|
17
|
+
*/
|
|
18
|
+
savePassword(options: SavePasswordOptions): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* @since 0.1.0
|
|
22
|
+
*/
|
|
23
|
+
export interface SavePasswordOptions {
|
|
24
|
+
/**
|
|
25
|
+
* The domain to associate the credential with.
|
|
26
|
+
*
|
|
27
|
+
* This must match one of the domains in your app's Associated Domains
|
|
28
|
+
* entitlement (without the `webcredentials:` prefix).
|
|
29
|
+
*
|
|
30
|
+
* Only available on iOS (required).
|
|
31
|
+
*
|
|
32
|
+
* @example 'example.com'
|
|
33
|
+
* @since 0.1.0
|
|
34
|
+
*/
|
|
35
|
+
domain?: string;
|
|
36
|
+
/**
|
|
37
|
+
* The password to save.
|
|
38
|
+
*
|
|
39
|
+
* @since 0.1.0
|
|
40
|
+
*/
|
|
41
|
+
password: string;
|
|
42
|
+
/**
|
|
43
|
+
* The username to save.
|
|
44
|
+
*
|
|
45
|
+
* @example 'jane.doe@example.com'
|
|
46
|
+
* @since 0.1.0
|
|
47
|
+
*/
|
|
48
|
+
username: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* @since 0.1.0
|
|
52
|
+
*/
|
|
53
|
+
export declare enum ErrorCode {
|
|
54
|
+
/**
|
|
55
|
+
* The user canceled the save operation.
|
|
56
|
+
*
|
|
57
|
+
* @since 0.1.0
|
|
58
|
+
*/
|
|
59
|
+
Canceled = "CANCELED",
|
|
60
|
+
/**
|
|
61
|
+
* The credential could not be saved.
|
|
62
|
+
*
|
|
63
|
+
* @since 0.1.0
|
|
64
|
+
*/
|
|
65
|
+
SaveFailed = "SAVE_FAILED"
|
|
66
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 0.1.0
|
|
3
|
+
*/
|
|
4
|
+
export var ErrorCode;
|
|
5
|
+
(function (ErrorCode) {
|
|
6
|
+
/**
|
|
7
|
+
* The user canceled the save operation.
|
|
8
|
+
*
|
|
9
|
+
* @since 0.1.0
|
|
10
|
+
*/
|
|
11
|
+
ErrorCode["Canceled"] = "CANCELED";
|
|
12
|
+
/**
|
|
13
|
+
* The credential could not be saved.
|
|
14
|
+
*
|
|
15
|
+
* @since 0.1.0
|
|
16
|
+
*/
|
|
17
|
+
ErrorCode["SaveFailed"] = "SAVE_FAILED";
|
|
18
|
+
})(ErrorCode || (ErrorCode = {}));
|
|
19
|
+
//# sourceMappingURL=definitions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAmDA;;GAEG;AACH,MAAM,CAAN,IAAY,SAaX;AAbD,WAAY,SAAS;IACnB;;;;OAIG;IACH,kCAAqB,CAAA;IACrB;;;;OAIG;IACH,uCAA0B,CAAA;AAC5B,CAAC,EAbW,SAAS,KAAT,SAAS,QAapB","sourcesContent":["export interface PasswordAutofillPlugin {\n /**\n * Save a username and password to the platform credential store.\n *\n * On iOS, the credential is saved to the iCloud Keychain via the\n * Shared Web Credentials API. This requires the Associated Domains\n * entitlement with a `webcredentials:` entry and a matching\n * `apple-app-site-association` file hosted on your domain.\n *\n * On Android, the credential is saved to the Google Password Manager\n * (or another provider) via the Credential Manager API. This presents\n * the system \"Save password?\" prompt to the user.\n *\n * Only available on Android and iOS.\n *\n * @since 0.1.0\n */\n savePassword(options: SavePasswordOptions): Promise<void>;\n}\n\n/**\n * @since 0.1.0\n */\nexport interface SavePasswordOptions {\n /**\n * The domain to associate the credential with.\n *\n * This must match one of the domains in your app's Associated Domains\n * entitlement (without the `webcredentials:` prefix).\n *\n * Only available on iOS (required).\n *\n * @example 'example.com'\n * @since 0.1.0\n */\n domain?: string;\n /**\n * The password to save.\n *\n * @since 0.1.0\n */\n password: string;\n /**\n * The username to save.\n *\n * @example 'jane.doe@example.com'\n * @since 0.1.0\n */\n username: string;\n}\n\n/**\n * @since 0.1.0\n */\nexport enum ErrorCode {\n /**\n * The user canceled the save operation.\n *\n * @since 0.1.0\n */\n Canceled = 'CANCELED',\n /**\n * The credential could not be saved.\n *\n * @since 0.1.0\n */\n SaveFailed = 'SAVE_FAILED',\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { registerPlugin } from '@capacitor/core';
|
|
2
|
+
const PasswordAutofill = registerPlugin('PasswordAutofill', {
|
|
3
|
+
web: () => import('./web').then(m => new m.PasswordAutofillWeb()),
|
|
4
|
+
});
|
|
5
|
+
export * from './definitions';
|
|
6
|
+
export { PasswordAutofill };
|
|
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,gBAAgB,GAAG,cAAc,CACrC,kBAAkB,EAClB;IACE,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;CAClE,CACF,CAAC;AAEF,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,CAAC","sourcesContent":["import { registerPlugin } from '@capacitor/core';\n\nimport type { PasswordAutofillPlugin } from './definitions';\n\nconst PasswordAutofill = registerPlugin<PasswordAutofillPlugin>(\n 'PasswordAutofill',\n {\n web: () => import('./web').then(m => new m.PasswordAutofillWeb()),\n },\n);\n\nexport * from './definitions';\nexport { PasswordAutofill };\n"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { WebPlugin } from '@capacitor/core';
|
|
2
|
+
import type { PasswordAutofillPlugin, SavePasswordOptions } from './definitions';
|
|
3
|
+
export declare class PasswordAutofillWeb extends WebPlugin implements PasswordAutofillPlugin {
|
|
4
|
+
savePassword(_options: SavePasswordOptions): Promise<void>;
|
|
5
|
+
}
|
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;AAO5C,MAAM,OAAO,mBACX,SAAQ,SAAS;IAGjB,KAAK,CAAC,YAAY,CAAC,QAA6B;QAC9C,MAAM,IAAI,CAAC,aAAa,CAAC,yBAAyB,CAAC,CAAC;IACtD,CAAC;CACF","sourcesContent":["import { WebPlugin } from '@capacitor/core';\n\nimport type {\n PasswordAutofillPlugin,\n SavePasswordOptions,\n} from './definitions';\n\nexport class PasswordAutofillWeb\n extends WebPlugin\n implements PasswordAutofillPlugin\n{\n async savePassword(_options: SavePasswordOptions): Promise<void> {\n throw this.unimplemented('Not implemented on web.');\n }\n}\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var core = require('@capacitor/core');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @since 0.1.0
|
|
7
|
+
*/
|
|
8
|
+
exports.ErrorCode = void 0;
|
|
9
|
+
(function (ErrorCode) {
|
|
10
|
+
/**
|
|
11
|
+
* The user canceled the save operation.
|
|
12
|
+
*
|
|
13
|
+
* @since 0.1.0
|
|
14
|
+
*/
|
|
15
|
+
ErrorCode["Canceled"] = "CANCELED";
|
|
16
|
+
/**
|
|
17
|
+
* The credential could not be saved.
|
|
18
|
+
*
|
|
19
|
+
* @since 0.1.0
|
|
20
|
+
*/
|
|
21
|
+
ErrorCode["SaveFailed"] = "SAVE_FAILED";
|
|
22
|
+
})(exports.ErrorCode || (exports.ErrorCode = {}));
|
|
23
|
+
|
|
24
|
+
const PasswordAutofill = core.registerPlugin('PasswordAutofill', {
|
|
25
|
+
web: () => Promise.resolve().then(function () { return web; }).then(m => new m.PasswordAutofillWeb()),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
class PasswordAutofillWeb extends core.WebPlugin {
|
|
29
|
+
async savePassword(_options) {
|
|
30
|
+
throw this.unimplemented('Not implemented on web.');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
35
|
+
__proto__: null,
|
|
36
|
+
PasswordAutofillWeb: PasswordAutofillWeb
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
exports.PasswordAutofill = PasswordAutofill;
|
|
40
|
+
//# sourceMappingURL=plugin.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.cjs.js","sources":["esm/definitions.js","esm/index.js","esm/web.js"],"sourcesContent":["/**\n * @since 0.1.0\n */\nexport var ErrorCode;\n(function (ErrorCode) {\n /**\n * The user canceled the save operation.\n *\n * @since 0.1.0\n */\n ErrorCode[\"Canceled\"] = \"CANCELED\";\n /**\n * The credential could not be saved.\n *\n * @since 0.1.0\n */\n ErrorCode[\"SaveFailed\"] = \"SAVE_FAILED\";\n})(ErrorCode || (ErrorCode = {}));\n//# sourceMappingURL=definitions.js.map","import { registerPlugin } from '@capacitor/core';\nconst PasswordAutofill = registerPlugin('PasswordAutofill', {\n web: () => import('./web').then(m => new m.PasswordAutofillWeb()),\n});\nexport * from './definitions';\nexport { PasswordAutofill };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class PasswordAutofillWeb extends WebPlugin {\n async savePassword(_options) {\n throw this.unimplemented('Not implemented on web.');\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["ErrorCode","registerPlugin","WebPlugin"],"mappings":";;;;AAAA;AACA;AACA;AACWA;AACX,CAAC,UAAU,SAAS,EAAE;AACtB;AACA;AACA;AACA;AACA;AACA,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,UAAU;AACtC;AACA;AACA;AACA;AACA;AACA,IAAI,SAAS,CAAC,YAAY,CAAC,GAAG,aAAa;AAC3C,CAAC,EAAEA,iBAAS,KAAKA,iBAAS,GAAG,EAAE,CAAC,CAAC;;AChB5B,MAAC,gBAAgB,GAAGC,mBAAc,CAAC,kBAAkB,EAAE;AAC5D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;AACrE,CAAC;;ACFM,MAAM,mBAAmB,SAASC,cAAS,CAAC;AACnD,IAAI,MAAM,YAAY,CAAC,QAAQ,EAAE;AACjC,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,yBAAyB,CAAC;AAC3D,IAAI;AACJ;;;;;;;;;"}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
var capacitorPasswordAutofill = (function (exports, core) {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @since 0.1.0
|
|
6
|
+
*/
|
|
7
|
+
exports.ErrorCode = void 0;
|
|
8
|
+
(function (ErrorCode) {
|
|
9
|
+
/**
|
|
10
|
+
* The user canceled the save operation.
|
|
11
|
+
*
|
|
12
|
+
* @since 0.1.0
|
|
13
|
+
*/
|
|
14
|
+
ErrorCode["Canceled"] = "CANCELED";
|
|
15
|
+
/**
|
|
16
|
+
* The credential could not be saved.
|
|
17
|
+
*
|
|
18
|
+
* @since 0.1.0
|
|
19
|
+
*/
|
|
20
|
+
ErrorCode["SaveFailed"] = "SAVE_FAILED";
|
|
21
|
+
})(exports.ErrorCode || (exports.ErrorCode = {}));
|
|
22
|
+
|
|
23
|
+
const PasswordAutofill = core.registerPlugin('PasswordAutofill', {
|
|
24
|
+
web: () => Promise.resolve().then(function () { return web; }).then(m => new m.PasswordAutofillWeb()),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
class PasswordAutofillWeb extends core.WebPlugin {
|
|
28
|
+
async savePassword(_options) {
|
|
29
|
+
throw this.unimplemented('Not implemented on web.');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
var web = /*#__PURE__*/Object.freeze({
|
|
34
|
+
__proto__: null,
|
|
35
|
+
PasswordAutofillWeb: PasswordAutofillWeb
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
exports.PasswordAutofill = PasswordAutofill;
|
|
39
|
+
|
|
40
|
+
return exports;
|
|
41
|
+
|
|
42
|
+
})({}, capacitorExports);
|
|
43
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.js","sources":["esm/definitions.js","esm/index.js","esm/web.js"],"sourcesContent":["/**\n * @since 0.1.0\n */\nexport var ErrorCode;\n(function (ErrorCode) {\n /**\n * The user canceled the save operation.\n *\n * @since 0.1.0\n */\n ErrorCode[\"Canceled\"] = \"CANCELED\";\n /**\n * The credential could not be saved.\n *\n * @since 0.1.0\n */\n ErrorCode[\"SaveFailed\"] = \"SAVE_FAILED\";\n})(ErrorCode || (ErrorCode = {}));\n//# sourceMappingURL=definitions.js.map","import { registerPlugin } from '@capacitor/core';\nconst PasswordAutofill = registerPlugin('PasswordAutofill', {\n web: () => import('./web').then(m => new m.PasswordAutofillWeb()),\n});\nexport * from './definitions';\nexport { PasswordAutofill };\n//# sourceMappingURL=index.js.map","import { WebPlugin } from '@capacitor/core';\nexport class PasswordAutofillWeb extends WebPlugin {\n async savePassword(_options) {\n throw this.unimplemented('Not implemented on web.');\n }\n}\n//# sourceMappingURL=web.js.map"],"names":["ErrorCode","registerPlugin","WebPlugin"],"mappings":";;;IAAA;IACA;IACA;AACWA;IACX,CAAC,UAAU,SAAS,EAAE;IACtB;IACA;IACA;IACA;IACA;IACA,IAAI,SAAS,CAAC,UAAU,CAAC,GAAG,UAAU;IACtC;IACA;IACA;IACA;IACA;IACA,IAAI,SAAS,CAAC,YAAY,CAAC,GAAG,aAAa;IAC3C,CAAC,EAAEA,iBAAS,KAAKA,iBAAS,GAAG,EAAE,CAAC,CAAC;;AChB5B,UAAC,gBAAgB,GAAGC,mBAAc,CAAC,kBAAkB,EAAE;IAC5D,IAAI,GAAG,EAAE,MAAM,mDAAe,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,mBAAmB,EAAE,CAAC;IACrE,CAAC;;ICFM,MAAM,mBAAmB,SAASC,cAAS,CAAC;IACnD,IAAI,MAAM,YAAY,CAAC,QAAQ,EAAE;IACjC,QAAQ,MAAM,IAAI,CAAC,aAAa,CAAC,yBAAyB,CAAC;IAC3D,IAAI;IACJ;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
|
|
4
|
+
@objc public class SavePasswordOptions: NSObject {
|
|
5
|
+
let domain: String
|
|
6
|
+
let password: String
|
|
7
|
+
let username: String
|
|
8
|
+
|
|
9
|
+
init(_ call: CAPPluginCall) throws {
|
|
10
|
+
self.domain = try SavePasswordOptions.getDomainFromCall(call)
|
|
11
|
+
self.password = try SavePasswordOptions.getPasswordFromCall(call)
|
|
12
|
+
self.username = try SavePasswordOptions.getUsernameFromCall(call)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private static func getDomainFromCall(_ call: CAPPluginCall) throws -> String {
|
|
16
|
+
guard let domain = call.getString("domain") else {
|
|
17
|
+
throw CustomError.domainMissing
|
|
18
|
+
}
|
|
19
|
+
return domain
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private static func getPasswordFromCall(_ call: CAPPluginCall) throws -> String {
|
|
23
|
+
guard let password = call.getString("password") else {
|
|
24
|
+
throw CustomError.passwordMissing
|
|
25
|
+
}
|
|
26
|
+
return password
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private static func getUsernameFromCall(_ call: CAPPluginCall) throws -> String {
|
|
30
|
+
guard let username = call.getString("username") else {
|
|
31
|
+
throw CustomError.usernameMissing
|
|
32
|
+
}
|
|
33
|
+
return username
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
public enum CustomError: Error {
|
|
4
|
+
case domainMissing
|
|
5
|
+
case passwordMissing
|
|
6
|
+
case saveFailed(message: String)
|
|
7
|
+
case usernameMissing
|
|
8
|
+
|
|
9
|
+
public var code: String? {
|
|
10
|
+
switch self {
|
|
11
|
+
case .saveFailed:
|
|
12
|
+
return "SAVE_FAILED"
|
|
13
|
+
default:
|
|
14
|
+
return nil
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
extension CustomError: LocalizedError {
|
|
20
|
+
public var errorDescription: String? {
|
|
21
|
+
switch self {
|
|
22
|
+
case .domainMissing:
|
|
23
|
+
return NSLocalizedString("domain must be provided.", comment: "domainMissing")
|
|
24
|
+
case .passwordMissing:
|
|
25
|
+
return NSLocalizedString("password must be provided.", comment: "passwordMissing")
|
|
26
|
+
case .saveFailed(let message):
|
|
27
|
+
return NSLocalizedString(message, comment: "saveFailed")
|
|
28
|
+
case .usernameMissing:
|
|
29
|
+
return NSLocalizedString("username must be provided.", comment: "usernameMissing")
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>CFBundleDevelopmentRegion</key>
|
|
6
|
+
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
7
|
+
<key>CFBundleExecutable</key>
|
|
8
|
+
<string>$(EXECUTABLE_NAME)</string>
|
|
9
|
+
<key>CFBundleIdentifier</key>
|
|
10
|
+
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
11
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
|
12
|
+
<string>6.0</string>
|
|
13
|
+
<key>CFBundleName</key>
|
|
14
|
+
<string>$(PRODUCT_NAME)</string>
|
|
15
|
+
<key>CFBundlePackageType</key>
|
|
16
|
+
<string>FMWK</string>
|
|
17
|
+
<key>CFBundleShortVersionString</key>
|
|
18
|
+
<string>1.0</string>
|
|
19
|
+
<key>CFBundleVersion</key>
|
|
20
|
+
<string>$(CURRENT_PROJECT_VERSION)</string>
|
|
21
|
+
<key>NSPrincipalClass</key>
|
|
22
|
+
<string></string>
|
|
23
|
+
</dict>
|
|
24
|
+
</plist>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Security
|
|
3
|
+
|
|
4
|
+
@objc public class PasswordAutofill: NSObject {
|
|
5
|
+
private let plugin: PasswordAutofillPlugin
|
|
6
|
+
|
|
7
|
+
init(plugin: PasswordAutofillPlugin) {
|
|
8
|
+
self.plugin = plugin
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@objc public func savePassword(_ options: SavePasswordOptions, completion: @escaping (_ error: Error?) -> Void) {
|
|
12
|
+
let domain = options.domain
|
|
13
|
+
let username = options.username
|
|
14
|
+
let password = options.password
|
|
15
|
+
SecAddSharedWebCredential(domain as CFString, username as CFString, password as CFString) { error in
|
|
16
|
+
if let error = error {
|
|
17
|
+
completion(CustomError.saveFailed(message: (error as Error).localizedDescription))
|
|
18
|
+
return
|
|
19
|
+
}
|
|
20
|
+
completion(nil)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import Capacitor
|
|
3
|
+
|
|
4
|
+
@objc(PasswordAutofillPlugin)
|
|
5
|
+
public class PasswordAutofillPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
6
|
+
public let identifier = "PasswordAutofillPlugin"
|
|
7
|
+
public let jsName = "PasswordAutofill"
|
|
8
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
9
|
+
CAPPluginMethod(name: "savePassword", returnType: CAPPluginReturnPromise)
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
public static let tag = "PasswordAutofillPlugin"
|
|
13
|
+
|
|
14
|
+
private var implementation: PasswordAutofill?
|
|
15
|
+
|
|
16
|
+
override public func load() {
|
|
17
|
+
self.implementation = PasswordAutofill(plugin: self)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@objc func savePassword(_ call: CAPPluginCall) {
|
|
21
|
+
do {
|
|
22
|
+
let options = try SavePasswordOptions(call)
|
|
23
|
+
implementation?.savePassword(options) { error in
|
|
24
|
+
if let error = error {
|
|
25
|
+
self.rejectCall(call, error)
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
self.resolveCall(call)
|
|
29
|
+
}
|
|
30
|
+
} catch {
|
|
31
|
+
self.rejectCall(call, error)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private func rejectCall(_ call: CAPPluginCall, _ error: Error) {
|
|
36
|
+
CAPLog.print("[", PasswordAutofillPlugin.tag, "] ", error)
|
|
37
|
+
let code = (error as? CustomError)?.code
|
|
38
|
+
call.reject(error.localizedDescription, code)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private func resolveCall(_ call: CAPPluginCall) {
|
|
42
|
+
call.resolve()
|
|
43
|
+
}
|
|
44
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@capawesome/capacitor-password-autofill",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Capacitor plugin for saving passwords to the platform credential store.",
|
|
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/Plugin/",
|
|
14
|
+
"CapawesomeCapacitorPasswordAutofill.podspec",
|
|
15
|
+
"Package.swift"
|
|
16
|
+
],
|
|
17
|
+
"author": "Robin Genz <mail@robingenz.dev>",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/capawesome-team/capacitor-plugins.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/capawesome-team/capacitor-plugins/issues"
|
|
25
|
+
},
|
|
26
|
+
"funding": [
|
|
27
|
+
{
|
|
28
|
+
"type": "github",
|
|
29
|
+
"url": "https://github.com/sponsors/capawesome-team/"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"type": "opencollective",
|
|
33
|
+
"url": "https://opencollective.com/capawesome"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"homepage": "https://capawesome.io/docs/plugins/password-autofill/",
|
|
37
|
+
"keywords": [
|
|
38
|
+
"capacitor",
|
|
39
|
+
"plugin",
|
|
40
|
+
"native"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
44
|
+
"verify:ios": "cd ios && pod install && xcodebuild -workspace Plugin.xcworkspace -scheme Plugin -destination generic/platform=iOS && cd ..",
|
|
45
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
46
|
+
"verify:web": "npm run build",
|
|
47
|
+
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
48
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
|
|
49
|
+
"eslint": "eslint . --ext ts",
|
|
50
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java}\"",
|
|
51
|
+
"swiftlint": "node-swiftlint",
|
|
52
|
+
"docgen": "docgen --api PasswordAutofillPlugin --output-readme README.md --output-json dist/docs.json",
|
|
53
|
+
"build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs",
|
|
54
|
+
"clean": "rimraf ./dist",
|
|
55
|
+
"watch": "tsc --watch",
|
|
56
|
+
"ios:pod:install": "cd ios && pod install --repo-update && cd ..",
|
|
57
|
+
"ios:spm:install": "cd ios && swift package resolve && cd ..",
|
|
58
|
+
"prepublishOnly": "npm run build"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@capacitor/android": "8.0.0",
|
|
62
|
+
"@capacitor/cli": "8.0.0",
|
|
63
|
+
"@capacitor/core": "8.0.0",
|
|
64
|
+
"@capacitor/docgen": "0.3.1",
|
|
65
|
+
"@capacitor/ios": "8.0.0",
|
|
66
|
+
"@ionic/eslint-config": "0.4.0",
|
|
67
|
+
"eslint": "8.57.0",
|
|
68
|
+
"prettier-plugin-java": "2.6.7",
|
|
69
|
+
"rimraf": "6.1.2",
|
|
70
|
+
"rollup": "4.53.3",
|
|
71
|
+
"swiftlint": "2.0.0",
|
|
72
|
+
"typescript": "5.9.3"
|
|
73
|
+
},
|
|
74
|
+
"peerDependencies": {
|
|
75
|
+
"@capacitor/core": ">=8.0.0"
|
|
76
|
+
},
|
|
77
|
+
"eslintConfig": {
|
|
78
|
+
"extends": "@ionic/eslint-config/recommended"
|
|
79
|
+
},
|
|
80
|
+
"capacitor": {
|
|
81
|
+
"ios": {
|
|
82
|
+
"src": "ios"
|
|
83
|
+
},
|
|
84
|
+
"android": {
|
|
85
|
+
"src": "android"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"publishConfig": {
|
|
89
|
+
"access": "public"
|
|
90
|
+
}
|
|
91
|
+
}
|