Git Product home page Git Product logo

capacitor-secure-storage-plugin's People

Contributors

alimejbar avatar de-dan avatar dependabot[bot] avatar koen20 avatar martinkasa avatar matthiasgrube avatar mrosinski avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

capacitor-secure-storage-plugin's Issues

feat: add allKeys api

Storage implementations usually offers a way to iterate over all the entries in the storage :

This benefits in being able to only remove the entries you were responsible for (ie. looking for entries with prefixed keys) and not remove the whole storage.

-> For iOS, SwiftKeychainWrapper already implements an .allKeys api (missing in their doc).
-> For Android, I haven't checked yet.

Android 11 issue

We’re in the process of updating our Angular / Ionic 6 app so it can use Okta authentication and to do this we are using the ionic-appauth library. That library uses the capacitor-secure-storage-plugin to store it’s authentication token on the device.

We’ve come across a problem that only seems to occur on our Android 11 test devices - other versions of Android work as do all versions of iOS.

The problem occurs when the application is fully closed by the user i.e when they swipe-to-close it so it it not running in background.

On all devices apart from the Android 11 ones, the app opens up and the user is authenticated because their token is retrieved from storage. On the Android 11 device an error occurs (“Valid token could not be found”).

In other areas of the app we use the non-secure capacitor storage plugin and anything stored there is retrieved ok on startup of the app. So it looks like it’s simply an issue with Android 11 and the secure storage plugin.

Has anyone come across this or would have any idea if there is any solution - did Android 11 have specific security settings that other versions do not have? (Android 12 works fine)

Get a key in ios

Hi, I can't get a key using get method

async doLogin() {
    const key = 'password';
    const value = 'tomato';

    SecureStoragePlugin.set({ key, value }).then(val => {
        console.log(val);
    });
}
private async getSecure(key) {
    await SecureStoragePlugin.get({ key }).then(value => {
       // Do things with value
    }).catch(error => {
       console.log('Item with specified key does not exist.');
    });
}

Don't know why but I always get the message Item with specified key does not exist. although the method set returns me TRUE.

Can you help me?
Rafael

Update this plugin for Capacitor 3

Capacitor 3 is being built and is already in beta. In this new version the native plugins have been removed from the core and there are some changes that are necessary in the existing plugins.

Example: Plugin Imports
The Plugins object is deprecated, but will continue to work in Capacitor 3. Capacitor plugins should be updated to use the new plugin registration APIs (see the Upgrade Guide for plugins), which will allow them to be imported directly from the plugin’s package.

Going forward, the Plugins object from @capacitor/core should not be used.

// OLD
import { Plugins } from '@capacitor/core';
const { AnyPlugin } = Plugins;
Importing the plugin directly from the plugin’s package is preferred, but the plugin must be updated to work with Capacitor 3 for this to be possible.
// NEW
import { AnyPlugin } from 'any-plugin';

References:

Updating Capacitor to 3.0 in your plugin

Updating Capacitor to 3.0 in your app

(credits to @mklipe for issue explanation)

keys() returns key names with "cap_sec_" prefix on web platform

Consider below code:

await SecureStoragePlugin.set({ key: 'key', value: 'value' })

const { value: keys } = await SecureStoragePlugin.keys()
// [ 'cap_sec_key' ]

await SecureStoragePlugin.get({ key: keys[0] })
// 'Item with given key does not exist'

It fails, because .keys() function returns keys with cap_sec_ prefix and .get() function adds the prefix again, so it looks for cap_sec_cap_sec_key instead of cap_sec_key.

This affects web platform only. The above code works fine on Android and iOS platforms.

How to update value of key?

I'm trying to update value of key(which already exist in storage). I tried,
SecureStoragePlugin.set({key,newvalue}).then(success=>{console.log('looks like we did it')});
but it keeps on showing error. I don't want to first delete the key and then store it with updated value which i believe will increase the processing time. Is there any way of updating value of key without deleting it?

Storage.set() throws an error on 0.6.4 and 0.7.0 on iOS

Summary

Calling Storage.set() throws an error if called on iPhones (checked on iphone 12 and iphone XR). Note that it works well on the web version.

Steps to reproduce

  1. Start an hello world ionic app.
  2. Add the [email protected] plugin (maybe this step is not required, in our case the bug occurred after an upgrade).
  3. Call SecureStoragePlugin.set( { key: 'credentials', value: JSON.stringify( { token: '123' } ) } );
  4. Upgrade to [email protected]
  5. Call SecureStoragePlugin.set( { key: 'credentials', value: JSON.stringify( { token: '123' } ) } );
  6. Bug: an error is thrown.

Environment

Device: iPhone XR, iOS 15.4.1

Plugin version: 0.6.4 and 0.7.0 (0.6.2 works well)

Ionic:

   Ionic CLI                     : 6.18.0 (/home/***/.config/yarn/global/node_modules/@ionic/cli)
   Ionic Framework               : @ionic/angular 6.1.6
   @angular-devkit/build-angular : 13.2.6
   @angular-devkit/schematics    : 13.2.6
   @angular/cli                  : 13.2.6
   @ionic/angular-toolkit        : 6.1.0

Capacitor:

   Capacitor CLI      : 3.5.1
   @capacitor/android : not installed
   @capacitor/core    : 3.5.1
   @capacitor/ios     : 3.5.1

Utility:

   cordova-res : not installed globally
   native-run  : 1.6.0

System:

   NodeJS : v16.13.0 (/home/***/.nvm/versions/node/v16.13.0/bin/node)
   npm    : 8.1.0

Keys and values are being stored as undefined?

I am trying to store my refresh token on ios and android in the secure storage. For some reason, despite the storage reporting success on setting the value, it was not able to get the value on reloading.

On further investigation, it seems that the key and value are being set as undefined, so when i try to get the key 'refresh_token', it indeed does not exist.

Capacitor Doctor:

Latest Dependencies:

  @capacitor/cli: 2.2.1

  @capacitor/core: 2.2.1

  @capacitor/android: 2.2.1

  @capacitor/electron: 2.2.1

  @capacitor/ios: 2.2.1

Installed Dependencies:

  @capacitor/electron not installed


  @capacitor/cli 2.1.2

  @capacitor/core 2.1.2

  @capacitor/android 2.1.2

  @capacitor/ios 2.1.2

[success] Android looking great! 👌
  Found 6 Capacitor plugins for ios:
    @byteowls/capacitor-oauth2 (2.0.0)
    capacitor-secure-storage-plugin (0.4.0)
    cordova-plugin-androidx-adapter (1.1.1)
    cordova-plugin-camera (4.1.0)
    cordova-plugin-inappbrowser (4.0.1-dev)
    cordova-plugin-video-editor (1.1.3)

My code used to set the key value:

private useAccessToken(response) {
        // tslint:disable-next-line:no-string-literal
        this.setupUser(response['access_token']);

        // store ref token and save it to local secure storage.
        this.refreshToken = response['refresh_token'];
        const refToken = this.refreshToken;
        const refTokenKey = 'refresh_token';
        SecureStoragePlugin.set({ refTokenKey, refToken }).then(
            success => console.log('Refresh Token Stored: ', success)
        )
            .catch(error => console.log('Error: ', error));

        this.profileInfo = response['profile_info'];
    }

Why would be key be being set as undefined?

Question: new key generation

Hi, I like this plugin so much. Thanks for your great work!

I have a question:
It would be nice to be able to create new symmetric and asymmetric keys directly with this plugin.
I have not found a plugin to do that jet, and this one seems to be the proper one.
It would be possibile to have this feature implemented?
For Android and iOS should be quite simple, not sure about the web compatibility.

Thanks in avance!

Hardware-backed / Secure Enclave Crypto

Good morning,

I would like to understand if this plugin rely on Hardware / Secure Enclave / HSM generated keys when data are going to be ciphered to be stored in the Keychain/Keystore storage.

My understanding is that :

  • iOS: the Keychain mecanism rely automatically on the use of cryptographic keys generated and stored in the secure enclave since iPhone 5S and later
  • Android: it is unclear if the keys used to cipher data for storage in Keystore are generated and stored in hardware-backed crypto services (I read https://source.android.com/security/keystore but it remains unclear if the Key Attestation specifying harware backed crypto are set in this plugin ?)

Could you provide this information ?

Best regards

Android app saves data in localStorage

When I use this plugin on an Android device/app it seems to be using the web implementation instead of the Android implementation.
Can't see any error in the logs, all seems to compile alright.

Using Capacitor 1.3.0.

Capacitor V4 compatibility

Hey, thanks for your lovely work!

Unfortunately this lib is not (yet) compatible with the newest capacitor release:

npm ERR! Found: @capacitor/[email protected]
npm ERR! node_modules/@capacitor/core
npm ERR!   optional @capacitor/core@"^4.0.0" from the root project
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer @capacitor/core@"^3.0.0" from [email protected]
npm ERR! node_modules/capacitor-secure-storage-plugin
npm ERR!   optional capacitor-secure-storage-plugin@"^0.7.1" from the root project

config object was deprecated in v3

I still get the warning Capacitor WebPlugin "SecureStoragePlugin" config object was deprecated in v3 and will be removed in v4., even though I use version "^0.6.2" with Capacitor v3

Getting a non-existing key on Android and in the browser

When I try to get an non-existing or removed key on iOS the promise correctly gets rejected but on Android and in the browser it resolves with a weird looking value:

{"value":"\u009eée"}

I can implement a check in the resolve callback as a workaround but this might be something worth looking into.

IOS: storage does not get cleared on app uninstall

See title. On testing the app on IOS 11 out of Testflight the storage does not get cleared after an app uninstall and all data still is there if you install again. I suppose that's an IOS thing ... is there a way to force this? Works fine on Android devices though. The data in the store is just one entry as a big(gish) JSON string.

Is this similar to Capacitor Storage?

I'm migrating a Cordova project that uses cordova-plugin-secure-storage to Capacitor, and I was wondering if this is different (more secure) than the built-in Capacitor Storage?

I'm not a Swift / Java developer but upon casual inspection of Capacitor Storage's source, it seems that on iOS it's not using Keychain and on Android it's not encrypting the values in SharedPreferences like your plugin seems to be doing.

Please add the ability to specify the serviceName

I have an existing cordova app that we are migrating to capacitor.

The previously cordova plugin we were using allowed the serviceName to be specified when reading/writing data.

However this plugin looks like the serviceName is hardcoded to cap_sec here, which means we cannot migrate to this plugin.

The ideal outcome would be to allow the serviceName optionally be passed into the methods, and to fallback to cap_sec if its not supplied (to be backwards compatible).

Linting error during build for Android

I've installed the plugin on a new Ionic/Capacitor project, removed the android platform and re-created it with npx cap add android.
When I launch ./gradlew build inside the android folder, I get an error for the task ":capacitor-secure-storage-plugin:lint".
Here is the log file of the build: build.log

This is the software I use:

  • Node v14.17.0
  • npm v6.14.13
  • Gradle 7.0

This is the package.json of the project:
{ "name": "openalpr", "version": "0.0.1", "author": "Ionic Framework", "homepage": "https://ionicframework.com/", "scripts": { "ng": "ng", "start": "ng serve", "build": "ng build", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e" }, "private": true, "dependencies": { "@angular/common": "~13.0.0", "@angular/core": "~13.0.0", "@angular/forms": "~13.0.0", "@angular/platform-browser": "~13.0.0", "@angular/platform-browser-dynamic": "~13.0.0", "@angular/router": "~13.0.0", "@awesome-cordova-plugins/core": "^5.39.0", "@awesome-cordova-plugins/openalpr": "^5.39.0", "@capacitor/android": "^3.4.0", "@capacitor/camera": "^1.2.4", "@capacitor/core": "^3.4.0", "@ionic/angular": "^6.0.0", "capacitor-secure-storage-plugin": "^0.6.2", "cordova-plugin-camera-preview": "^0.12.3", "cordova-plugin-openalpr": "^2.1.0", "rxjs": "~6.6.0", "tslib": "^2.2.0", "zone.js": "~0.11.4" }, "devDependencies": { "@angular-devkit/build-angular": "~13.0.1", "@angular-eslint/builder": "~13.0.1", "@angular-eslint/eslint-plugin": "~13.0.1", "@angular-eslint/eslint-plugin-template": "~13.0.1", "@angular-eslint/template-parser": "~13.0.1", "@angular/cli": "~13.0.1", "@angular/compiler": "~13.0.0", "@angular/compiler-cli": "~13.0.0", "@angular/language-service": "~13.0.0", "@capacitor/cli": "^3.4.0", "@ionic/angular-toolkit": "^5.0.0", "@types/jasmine": "~3.6.0", "@types/jasminewd2": "~2.0.3", "@types/node": "^12.11.1", "@typescript-eslint/eslint-plugin": "5.3.0", "@typescript-eslint/parser": "5.3.0", "eslint": "^7.6.0", "eslint-plugin-import": "2.22.1", "eslint-plugin-jsdoc": "30.7.6", "eslint-plugin-prefer-arrow": "1.2.2", "jasmine-core": "~3.8.0", "jasmine-spec-reporter": "~5.0.0", "karma": "~6.3.2", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.0.3", "karma-coverage-istanbul-reporter": "~3.0.2", "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "^1.5.0", "protractor": "~7.0.0", "serve": "^13.0.2", "ts-node": "~8.3.0", "typescript": "~4.4.4" }, "description": "An Ionic project" }

Android SecureStorage.set can not handle null values

On Android the plugin hard crashes when calling await SecureStoragePlugin.set({'someKey', undefined}). This is because of an unhandled NPE that happens when calling value.getBytes:

this.passwordStorageHelper.setData(key, value.getBytes(Charset.forName("UTF-8")));

Funny enough the iOS version already handles possible null values correctly:

let value = call.getString("value") ?? ""

plugin uses local storage instead of keychain on ios

I'm running a ionic project on an ios device (emulator or real) but the plugin only saves values into local storage, to be lost on app removal.

Here visible in the emulator using safari debug tools:

The same happens on a real device, nothing is saved to keychain.

You mentioned in another ticket that this might be related to imports - might it be a problem that I'm also using DeviceInfo import?

import { DeviceInfo } from '@capacitor/core';

[...]

import 'capacitor-secure-storage-plugin';
import { Plugins } from '@capacitor/core';
const { SecureStoragePlugin } = Plugins;

Storage is 'empty' after updating from 0.5.1 to 0.6.0

Hi!

Currently we use this plugin so that app user can log in via a digit code. This digit code is set up by the app user during the onboarding in our app.

We have migrated from Capacitor 2 to Capacitor 4 which made us update the version we use of this plugin. When we were running on Capacitor 2 we used version 0.4.0 of this package and after updating to Capacitor 4 we use the most recent (0.8.0) version.

After that version bump all of our app user (iOS & Android, except Web) suddenly lost there digit code which needed them to onboard again.

Investigation showed us that te problem disappeared when we downgraden the version from 0.8.0 to 0.4.0: the digit code was present again! Especially, the problem is caused in the changes made between 0.5.1 and 0.6.0 (see diff)

Reproduction steps:

  1. Run an app with at least the following dependencies
"dependencies": {
    "@capacitor/android": "4.1.0",
    "@capacitor/core": "4.1.0",
    "@capacitor/ios": "4.1.0",
    "capacitor-secure-storage-plugin": "0.5.1"
}
  1. Save some value to the secure storage and get it
await SecureStoragePlugin.set({ key: 'digitCode', value: '1234' });
SecureStoragePlugin.get({ key: 'digitCode' })
    .then(console.log) // '1234'
    .catch(console.error) // not called
  1. Upgrade capacitor-secure-storage-plugin to 0.6.0
  2. Executing the getter results in no value
SecureStoragePlugin.get({ key: 'digitCode' })
    .then(console.log) // not called
    .catch(console.error) // results in 'Item with given key does not exist'

Expected behaviour
Step 4 must result in logging the digit code which was set before updating to version 0.6.0

register plugin on Android

In the documentation it states to include the plugin in the mainActivity as such:

@Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Initializes the Bridge
    this.init(savedInstanceState, new ArrayList<Class<? extends Plugin>>() {{
      // Additional plugins you've installed go here
      // Ex: add(TotallyAwesomePlugin.class);
      add(SecureStoragePlugin.class);
    }});
  }

However in the upgrade guide of capacitor 3 it states we no longer need to register plugins that way.

Is it still required to register this plugin in capacitor 3 or not?

import com.whitestein.securestorage.SecureStoragePlugin; not found on android Studio

I add the plugin in my ionic 4 capacitor app and add the plugin in the main activity but the import fails because the package is not found:

import com.whitestein.securestorage.SecureStoragePlugin;

The app builds and install correctly but i had this error in the logcat:

Capacitor/Plugin: Attempt to invoke virtual method 'byte[] java.lang.String.getBytes(java.nio.charset.Charset)' on a null object reference
java.lang.NullPointerException: Attempt to invoke virtual method 'byte[] java.lang.String.getBytes(java.nio.charset.Charset)' on a null object reference
at com.whitestein.securestorage.SecureStoragePlugin._set(SecureStoragePlugin.java:73)
at com.whitestein.securestorage.SecureStoragePlugin.set(SecureStoragePlugin.java:32)
at java.lang.reflect.Method.invoke(Native Method)
at com.getcapacitor.PluginHandle.invoke(PluginHandle.java:99)
at com.getcapacitor.Bridge$1.run(Bridge.java:520)
at android.os.Handler.handleCallback(Handler.java:789)
at android.os.Handler.dispatchMessage(Handler.java:98)
at android.os.Looper.loop(Looper.java:164)
at android.os.HandlerThread.run(HandlerThread.java:65)

CocoaPods Install Error - Is ARM64/M1 Supported

Hello,

Through process of elimination, removing each pod line and running the pod install, I encounter the below issue when CapacitorSecureStoragePlugin is included in the Podfile.

Is arm64 (M1 Mac) not supported?
Or have I not got something installed.

Command

/usr/local/bin/pod install

Stack

CocoaPods : 1.11.2
Ruby : ruby 2.6.8p205 (2021-07-07 revision 67951) [universal.x86_64-darwin21]
RubyGems : 3.0.3.1
Host : macOS 12.1 (21C52)
Xcode : 13.2.1 (13C100)
Git : git version 2.36.0
Ruby lib dir : /System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib
Repositories : trunk - CDN - https://cdn.cocoapods.org/

Plugins

cocoapods-deintegrate : 1.0.5
cocoapods-plugins     : 1.0.0
cocoapods-search      : 1.0.1
cocoapods-trunk       : 1.6.0
cocoapods-try         : 1.2.0

Podfile

platform :ios, '12.0'
use_frameworks!

# workaround to avoid Xcode caching of Pods that requires
# Product -> Clean Build Folder after new Cordova plugins installed
# Requires CocoaPods 1.6 or newer
install! 'cocoapods', :disable_input_output_paths => true

post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['EXPANDED_CODE_SIGN_IDENTITY'] = ""
config.build_settings['CODE_SIGNING_REQUIRED'] = "NO"
config.build_settings['CODE_SIGNING_ALLOWED'] = "NO"
end
end
end

def capacitor_pods
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
pod 'ByteowlsCapacitorOauth2', :path => '../../node_modules/@byteowls/capacitor-oauth2'
pod 'CapacitorCommunityAppcenterAnalytics', :path => '../../node_modules/@capacitor-community/appcenter-analytics'
pod 'CapacitorCommunityBarcodeScanner', :path => '../../node_modules/@capacitor-community/barcode-scanner'
pod 'CapacitorCommunityHttp', :path => '../../node_modules/@capacitor-community/http'
pod 'CapacitorActionSheet', :path => '../../node_modules/@capacitor/action-sheet'
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
pod 'CapacitorCamera', :path => '../../node_modules/@capacitor/camera'
pod 'CapacitorDialog', :path => '../../node_modules/@capacitor/dialog'
pod 'CapacitorGeolocation', :path => '../../node_modules/@capacitor/geolocation'
pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics'
pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard'
pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen'
pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
pod 'CapacitorToast', :path => '../../node_modules/@capacitor/toast'
pod 'CapacitorSecureStoragePlugin', :path => '../../node_modules/capacitor-secure-storage-plugin'
end

target 'App' do
capacitor_pods
# Add your Pods here
end

Error

LoadError - dlopen(/Users/azureagent/.gem/ruby/2.6.0/gems/ffi-1.15.5/lib/ffi_c.bundle, 0x0009): tried: '/Users/azureagent/.gem/ruby/2.6.0/gems/ffi-1.15.5/lib/ffi_c.bundle' (mach-o file, but is an incompatible architecture (have 'arm64', need 'x86_64')), '/usr/lib/ffi_c.bundle' (no such file) - /Users/azureagent/.gem/ruby/2.6.0/gems/ffi-1.15.5/lib/ffi_c.bundle
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/Users/azureagent/.gem/ruby/2.6.0/gems/ffi-1.15.5/lib/ffi.rb:5:in `rescue in <top (required)>'
/Users/azureagent/.gem/ruby/2.6.0/gems/ffi-1.15.5/lib/ffi.rb:2:in `<top (required)>'
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/Library/Ruby/Gems/2.6.0/gems/ethon-0.15.0/lib/ethon.rb:3:in `<top (required)>'
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/Library/Ruby/Gems/2.6.0/gems/typhoeus-1.4.0/lib/typhoeus.rb:2:in `<top (required)>'
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-core-1.11.2/lib/cocoapods-core/cdn_source.rb:440:in `download_typhoeus_impl_async'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-core-1.11.2/lib/cocoapods-core/cdn_source.rb:372:in `download_and_save_with_retries_async'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-core-1.11.2/lib/cocoapods-core/cdn_source.rb:365:in `download_file_async'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-core-1.11.2/lib/cocoapods-core/cdn_source.rb:338:in `download_file'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-core-1.11.2/lib/cocoapods-core/cdn_source.rb:284:in `ensure_versions_file_loaded'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-core-1.11.2/lib/cocoapods-core/cdn_source.rb:208:in `search'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-core-1.11.2/lib/cocoapods-core/source/aggregate.rb:83:in `block in search'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-core-1.11.2/lib/cocoapods-core/source/aggregate.rb:83:in `select'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-core-1.11.2/lib/cocoapods-core/source/aggregate.rb:83:in `search'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/resolver.rb:416:in `create_set_from_sources'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/resolver.rb:385:in `find_cached_set'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/resolver.rb:360:in `specifications_for_dependency'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/resolver.rb:165:in `search_for'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/resolver.rb:274:in `block in sort_dependencies'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/resolver.rb:267:in `each'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/resolver.rb:267:in `sort_by'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/resolver.rb:267:in `sort_by!'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/resolver.rb:267:in `sort_dependencies'
/Library/Ruby/Gems/2.6.0/gems/molinillo-0.8.0/lib/molinillo/delegates/specification_provider.rb:60:in `block in sort_dependencies'
/Library/Ruby/Gems/2.6.0/gems/molinillo-0.8.0/lib/molinillo/delegates/specification_provider.rb:77:in `with_no_such_dependency_error_handling'
/Library/Ruby/Gems/2.6.0/gems/molinillo-0.8.0/lib/molinillo/delegates/specification_provider.rb:59:in `sort_dependencies'
/Library/Ruby/Gems/2.6.0/gems/molinillo-0.8.0/lib/molinillo/resolution.rb:754:in `push_state_for_requirements'
/Library/Ruby/Gems/2.6.0/gems/molinillo-0.8.0/lib/molinillo/resolution.rb:744:in `require_nested_dependencies_for'
/Library/Ruby/Gems/2.6.0/gems/molinillo-0.8.0/lib/molinillo/resolution.rb:727:in `activate_new_spec'
/Library/Ruby/Gems/2.6.0/gems/molinillo-0.8.0/lib/molinillo/resolution.rb:684:in `attempt_to_activate'
/Library/Ruby/Gems/2.6.0/gems/molinillo-0.8.0/lib/molinillo/resolution.rb:254:in `process_topmost_state'
/Library/Ruby/Gems/2.6.0/gems/molinillo-0.8.0/lib/molinillo/resolution.rb:182:in `resolve'
/Library/Ruby/Gems/2.6.0/gems/molinillo-0.8.0/lib/molinillo/resolver.rb:43:in `resolve'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/resolver.rb:94:in `resolve'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/installer/analyzer.rb:1078:in `block in resolve_dependencies'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/user_interface.rb:64:in `section'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/installer/analyzer.rb:1076:in `resolve_dependencies'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/installer/analyzer.rb:124:in `analyze'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/installer.rb:416:in `analyze'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/installer.rb:241:in `block in resolve_dependencies'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/user_interface.rb:64:in `section'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/installer.rb:240:in `resolve_dependencies'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/installer.rb:161:in `install!'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/command/install.rb:52:in `run'
/Library/Ruby/Gems/2.6.0/gems/claide-1.1.0/lib/claide/command.rb:334:in `run'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/lib/cocoapods/command.rb:52:in `run'
/Library/Ruby/Gems/2.6.0/gems/cocoapods-1.11.2/bin/pod:55:in `<top (required)>'
/usr/local/bin/pod:23:in `load'
/usr/local/bin/pod:23:in `<main>'

Item with given key does not exist

Previously I created this issue for the following reason.

This plugin is getting the following error when minifyEnabled is set to true in Android.

This error is actually coming always whether or not minifyEnabled is true.

E/Capacitor: Unable to read file at path public/plugins
E/Capacitor/Plugin: Item with given key does not exist
    java.lang.Exception: Item with given key does not exist
        at com.whitestein.securestorage.SecureStoragePlugin.Z(:95)
        at com.whitestein.securestorage.SecureStoragePlugin.get(:45)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.getcapacitor.t0.h(:121)
        at com.getcapacitor.a0.D(:584)
        at com.getcapacitor.a0.E(Unknown Source:0)
        at com.getcapacitor.a.run(Unknown Source:8)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:246)
        at android.os.HandlerThread.run(HandlerThread.java:67)

Could you please share the ProGuard rule for this plugin which is needed for proguard-rules.pro file?

[BUG] When opening the app it crashes after installing the plugin.

Reproduction:

  • ionic start testApp
  • ionic build
  • ioinic cap add android
  • npm install capacitor-secure-storage-plugin
  • Add plugin lines to MainActivity
  • ionic cap sync
  • ionic cap open android
  • Build app in android-studio
  • Deploy on OnePlus 5

Error:

2020-04-10 11:16:35.673 4524-4524/? I/o.ionic.starte: Late-enabling -Xcheck:jni
2020-04-10 11:16:35.793 4524-4556/io.ionic.starter I/o.ionic.starte: The ClassLoaderContext is a special shared library.
2020-04-10 11:16:36.008 4524-4524/io.ionic.starter I/Perf: Connecting to perf service.
2020-04-10 11:16:36.022 4524-4524/io.ionic.starter W/FirebaseApp: Default FirebaseApp failed to initialize because no default options were found. This usually means that com.google.gms:google-services was not applied to your gradle project.
2020-04-10 11:16:36.022 4524-4524/io.ionic.starter I/FirebaseInitProvider: FirebaseApp initialization unsuccessful
2020-04-10 11:16:36.097 4524-4524/io.ionic.starter W/o.ionic.starte: Accessing hidden method Landroid/graphics/drawable/Drawable;->getOpticalInsets()Landroid/graphics/Insets; (light greylist, linking)
2020-04-10 11:16:36.097 4524-4524/io.ionic.starter W/o.ionic.starte: Accessing hidden field Landroid/graphics/Insets;->left:I (light greylist, linking)
2020-04-10 11:16:36.097 4524-4524/io.ionic.starter W/o.ionic.starte: Accessing hidden field Landroid/graphics/Insets;->right:I (light greylist, linking)
2020-04-10 11:16:36.097 4524-4524/io.ionic.starter W/o.ionic.starte: Accessing hidden field Landroid/graphics/Insets;->top:I (light greylist, linking)
2020-04-10 11:16:36.097 4524-4524/io.ionic.starter W/o.ionic.starte: Accessing hidden field Landroid/graphics/Insets;->bottom:I (light greylist, linking)
2020-04-10 11:16:36.105 4524-4524/io.ionic.starter E/o.ionic.starte: Invalid ID 0x00000000.
2020-04-10 11:16:36.158 4524-4524/io.ionic.starter W/o.ionic.starte: Accessing hidden method Landroid/view/View;->getAccessibilityDelegate()Landroid/view/View$AccessibilityDelegate; (light greylist, linking)
2020-04-10 11:16:36.166 4524-4524/io.ionic.starter W/o.ionic.starte: Accessing hidden method Landroid/view/View;->computeFitSystemWindows(Landroid/graphics/Rect;Landroid/graphics/Rect;)Z (light greylist, reflection)
2020-04-10 11:16:36.167 4524-4524/io.ionic.starter W/o.ionic.starte: Accessing hidden method Landroid/view/ViewGroup;->makeOptionalFitsSystemWindows()V (light greylist, reflection)
2020-04-10 11:16:36.171 4524-4524/io.ionic.starter D/AndroidRuntime: Shutting down VM
2020-04-10 11:16:36.176 4524-4524/io.ionic.starter E/AndroidRuntime: FATAL EXCEPTION: main
    Process: io.ionic.starter, PID: 4524
    java.lang.RuntimeException: Unable to start activity ComponentInfo{io.ionic.starter/io.ionic.starter.MainActivity}: android.view.InflateException: Binary XML file line #2: Binary XML file line #2: Error inflating class android.support.design.widget.CoordinatorLayout
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3047)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3182)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1916)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6898)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
     Caused by: android.view.InflateException: Binary XML file line #2: Binary XML file line #2: Error inflating class android.support.design.widget.CoordinatorLayout
     Caused by: android.view.InflateException: Binary XML file line #2: Error inflating class android.support.design.widget.CoordinatorLayout
     Caused by: java.lang.ClassNotFoundException: Didn't find class "android.support.design.widget.CoordinatorLayout" on path: DexPathList[[zip file "/data/app/io.ionic.starter-HL1m187oOUhYQheeuueT3A==/base.apk"],nativeLibraryDirectories=[/data/app/io.ionic.starter-HL1m187oOUhYQheeuueT3A==/lib/arm64, /system/lib64]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:134)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        at android.view.LayoutInflater.createView(LayoutInflater.java:606)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:790)
        at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:730)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:492)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:423)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:374)
        at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:555)
        at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:161)
        at com.getcapacitor.BridgeActivity.init(BridgeActivity.java:56)
        at io.ionic.starter.MainActivity.onCreate(MainActivity.java:18)
        at android.app.Activity.performCreate(Activity.java:7149)
        at android.app.Activity.performCreate(Activity.java:7140)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1288)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3027)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3182)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1916)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6898)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
2020-04-10 11:16:36.201 4524-4612/? D/OSTracker: OS Event: crash
2020-04-10 11:16:36.222 4524-4612/? D/AbstractTracker: Event success
2020-04-10 11:16:36.223 4524-4524/? I/Process: Sending signal. PID: 4524 SIG: 9

ITMS-90809: UIWebView API Deprecation

Hello,
i'm using your plugin and get the UIWebView API Deprecation when submitting to App Store Connect. I searched my code for the string "UIWebView" and found out that it is referenced in the following file: ios/Pods/CapacitorCordova/ios/CapacitorCordova/CapacitorCordova/Classes/Public/CDVUserAgentUtil.m

Is it possible, that the Warning from Apple occours because of that file?

I also saw that Capacitor made a fix for it, see ionic-team/capacitor@3034885.

Best regards
Jakob

Why is this plugin using asymmetric encryption?

Based on the use cases provided by this plugin it might be easier & faster (in my opinion) to use symmetric AES 256-bit encryption.

That's why I was wondering why we are using an asymmetric encryption? As illustrated here: https://github.com/martinkasa/capacitor-secure-storage-plugin/blob/master/android/src/main/java/com/whitestein/securestorage/PasswordStorageHelper.java#L167 & here: https://github.com/martinkasa/capacitor-secure-storage-plugin/blob/master/android/src/main/java/com/whitestein/securestorage/PasswordStorageHelper.java#L197

iOS SecureStoragePlugin.keys() returns JSON serializable error

Keys method return JSON Serializable Error.

Reason: Return type for KeychainWrapper.allKeys() is Set<String>.

QuickFix: File Name: Plugin/Plugin.swift

Updated Method: (Convert Set to Array)

@objc func keys(_ call: CAPPluginCall) {
        let keys = keychainwrapper.allKeys();
        call.success(["values": Array(keys)])
    }

Project Config

capacitor-secure-storage-plugin (0.5.0)
@capacitor/core : 2.4.5

Error Screenshot

image

Android SecureStoragePlugin.keys() returns "[Ljava.lang.String;" instead of an array of strings.

The value being returned by SecureStoragePlugin.keys() is the string literal "[Ljava.lang.String;" instead of an array of strings. This is broken for both android and iOS. I have forked the the repo to add a simple fix where I just call Arrays.toString(keys) to get the values. This is not ideal however because this is still not a proper stringified JSON object.

Am I doing something wrong? I am on "capacitor-secure-storage-plugin": "^0.5.0", @capacitor/android": "^2.4.4", "@capacitor/core": "^2.4.4", and "@capacitor/ios": "^2.4.4".

below is a hack I am using to get the actual string values.

public JSObject _keys() {
  String[] keys = this.passwordStorageHelper.keys();
  JSObject ret = new JSObject();
  ret.put("value", Arrays.toString(keys));
  return ret;
}

Issues getting stored key on Android?

Hi,

I am experiencing an error on Android. Somehow I does not allow me to access the stored key. It works fine on IOS.

Saving the key works fine (i get true on success). However, when I am trying to get the stored key I get the following error:

E/Capacitor/Plugin: error java.lang.NullPointerException: Attempt to get length of null array at java.lang.StringFactory.newStringFromBytes(StringFactory.java:249) at com.whitestein.securestorage.SecureStoragePlugin.get(SecureStoragePlugin.java:42) at java.lang.reflect.Method.invoke(Native Method) at com.getcapacitor.PluginHandle.invoke(PluginHandle.java:99) at com.getcapacitor.Bridge$2.run(Bridge.java:537) at android.os.Handler.handleCallback(Handler.java:883) at android.os.Handler.dispatchMessage(Handler.java:100) at android.os.Looper.loop(Looper.java:214) at android.os.HandlerThread.run(HandlerThread.java:67)

I am trying to save an application access_token (that is valid forever) in the secure storage.
My set javascript method is as follows:

`async setAccessToken(access_token:string){

const key = 'access_token';
const value = access_token;

try {
 
  let success = await SecureStoragePlugin.set({ key, value })
  console.log('success saving token '+JSON.stringify(success))
  
} catch (error) {
  return error
}

}`

my get method:

`
async getAccessToken(){

  try {

    const key = 'access_token';
   
    let object = await SecureStoragePlugin.get({ key });
    console.log('Getting access token '+object)

    return object.value
    
  } catch (error) {

    return error

  }    

}
`

I am doing the test in the Android emulator in Android Studio and the device has a lockScreen PIN set. Any ideas what the problem might be?

Get method does not throw error on missing value

Using capacitor 1.4.0 I copied the code examples from the readme:

import 'capacitor-secure-storage-plugin';
import { Plugins } from '@capacitor/core';
const { SecureStoragePlugin } = Plugins;

[...]

    const key = 'moots_token_secure';
    SecureStoragePlugin.get({ key })
    .then((value: string) => {
      console.log(value);
    })
    .catch((error: any) => {
      console.log('Item with specified key does not exist.');
    });

But there is never any error thrown. If the value does not exist, a weird {value: "�ée"} value is returned:

Silently crash when storing string with more than 256 bytes

Trying to store (encryptedly) a long string (for instance a JWT token of 2075 chars).

When debugging the process, the issue lies in the "encrypt" function of PasswordStorageHelper_SDK18 class.

javax.crypto.IllegalBlockSizeException: input must be under 256 bytes
        at com.android.org.conscrypt.OpenSSLCipherRSA.engineDoFinal(OpenSSLCipherRSA.java:299)
        at javax.crypto.Cipher.doFinal(Cipher.java:2055)
        at com.whitestein.securestorage.PasswordStorageHelper$PasswordStorageHelper_SDK18.encrypt(PasswordStorageHelper.java:313)
        at com.whitestein.securestorage.PasswordStorageHelper$PasswordStorageHelper_SDK18.setData(PasswordStorageHelper.java:254)
        at com.whitestein.securestorage.PasswordStorageHelper.setData(PasswordStorageHelper.java:70)
        at com.whitestein.securestorage.SecureStoragePlugin.set(SecureStoragePlugin.java:27)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.getcapacitor.PluginHandle.invoke(PluginHandle.java:99)
        at com.getcapacitor.Bridge$2.run(Bridge.java:526)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.os.HandlerThread.run(HandlerThread.java:65)

A work around from the web side, could be splitting the long string into smaller one (245bytes in fact) but this should not be necessary!

Version 0.3.2 wrong capacitor dependency

Version 0.3.2 is targeted for capacitor < 2.0.0, but the package.json mentions the following dependency:

"@capacitor/core": "latest"

Shouldn't this point to 1.5.x?

What is the package name for Android Studio?

Hey Martinkasa,
which package import do I need to reference SecureStoragePlugin.class? I can't find the package name.

In your provided example the import is missing, and I can't figure out what package import I need..

public class MainActivity extends BridgeActivity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Initializes the Bridge
    this.init(savedInstanceState, new ArrayList<Class<? extends Plugin>>() {{
      // Additional plugins you've installed go here
      // Ex: add(TotallyAwesomePlugin.class);
      add(SecureStoragePlugin.class);
    }});
  }
}

Any way to verify that Screen lock options PIN or Password is enabled ?

Hello,

Plugin cordova secure storage permit to verify that Screen lock options PIN or Password is enabled and throw "Mobile is not secure" error if it's not. In capacitor plugin I don't see any init method. Is there a way to verify that the mobile is secure thanks to this plugin ?

link :
https://support.google.com/android/answer/9079129?hl=en
https://www.npmjs.com/package/cordova-plugin-secure-storage-echo

var ss = new cordova.plugins.SecureStorage(
  function() {
    console.log("Success");
  },
  function(error) {
    console.log("Error " + error);
  },
  "my_app"
);

sample of implementation :

public init(): Promise<any> {
       return new Promise((resolve, reject) => {
           this.secureStorage = new cordova.plugins.SecureStorage(
               () => {
                   console.log('--> Secure Storage init success');
                   resolve();
               },
               (error) => {
                   console.log('error on init secureStorage ', error);
                   navigator.notification.alert(
                       'Please enable the screen lock on your device. This app cannot operate securely without it.',
                       () => {
                           console.log('dans l alert');
                       },
                       'Screen lock is disabled'
                   );
                   reject(error);
               },
               'MY_APP');
       });
   }

Thanks in advance for your return,

`remove` method throwing error in ios when key not found

I am using capacitor 2 and plugin v0.5.1, the documentation says
set, remove and clear return true in case of success and false in case of error
On my ios device, it does not return false, instead it throws an error if key is not found. Please update the documentation

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.