GithubHelp home page GithubHelp logo

pantheon-systems / wp-redis Goto Github PK

View Code? Open in Web Editor NEW
225.0 55.0 68.0 1.1 MB

WordPress Object Cache using Redis.

Home Page: https://wordpress.org/plugins/wp-redis/

License: GNU General Public License v2.0

Shell 4.63% PHP 94.13% Gherkin 1.24%
caching redis wordpress wordpress-plugin

wp-redis's Introduction

WP Redis

Actively Maintained

Contributors: getpantheon, danielbachhuber, mboynes, Outlandish Josh jspellman jazzs3quence
Tags: cache, plugin, redis
Requires at least: 3.0.1
Tested up to: 6.4.1
Stable tag: 1.4.5-dev
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html

Back your WP Object Cache with Redis, a high-performance in-memory storage backend.

Description

CircleCI

For sites concerned with high traffic, speed for logged-in users, or dynamic pageloads, a high-speed and persistent object cache is a must. You also need something that can scale across multiple instances of your application, so using local file caches or APC are out.

Redis is a great answer, and one we bundle on the Pantheon platform. This is our plugin for integrating with the cache, but you can use it on any self-hosted WordPress site if you have Redis. Install from WordPress.org or Github.

It's important to note that a persistent object cache isn't a panacea - a page load with 2,000 Redis calls can be 2 full seconds of object cache transactions. Make sure you use the object cache wisely: keep to a sensible number of keys, don't store a huge amount of data on each key, and avoid stampeding frontend writes and deletes.

Go forth and make awesome! And, once you've built something great, send us feature requests (or bug reports). Take a look at the wiki for useful code snippets and other tips.

Installation

This assumes you have a PHP environment with the required PhpRedis extension and a working Redis server (e.g. Pantheon). WP Redis also works with Predis via humanmade/wp-redis-predis-client.

  1. Install object-cache.php to wp-content/object-cache.php with a symlink or by copying the file.

  2. If you're not running on Pantheon, edit wp-config.php to add your cache credentials, e.g.:

     $redis_server = array(
         'host'     => '127.0.0.1',
         'port'     => 6379,
         'auth'     => '12345',
         'database' => 0, // Optionally use a specific numeric Redis database. Default is 0.
     );
    
  3. If your Redis server is listening through a sockt file instead, set its path on host parameter and change the port to null:

     $redis_server = array(
         'host'     => '/path/of/redis/socket-file.sock',
         'port'     => null,
         'auth'     => '12345',
         'database' => 0, // Optionally use a specific numeric Redis database. Default is 0.
     );
    
  4. Engage thrusters: you are now backing WP's Object Cache with Redis.

  5. (Optional) To use the wp redis WP-CLI commands, activate the WP Redis plugin. No activation is necessary if you're solely using the object cache drop-in.

  6. (Optional) To use the same Redis server with multiple, discreet WordPress installs, you can use the WP_CACHE_KEY_SALT constant to define a unique salt for each install.

  7. (Optional) To use true cache groups, with the ability to delete all keys for a given group, register groups with wp_cache_add_redis_hash_groups(), or define the WP_REDIS_USE_CACHE_GROUPS constant to true to enable with all groups. However, when enabled, the expiration value is not respected because expiration on group keys isn't a feature supported by Redis.

  8. (Optional) On an existing site previously using WordPress' transient cache, use WP-CLI to delete all (%_transient_%) transients from the options table: wp transient delete-all. WP Redis assumes responsibility for the transient cache.

  9. (Optional) To use Relay instead of PhpRedis as the client define the WP_REDIS_USE_RELAY constant to true. For support requests, please use Relay's GitHub discussions.

WP-CLI Commands

This plugin implements a variety of WP-CLI commands. All commands are grouped into the wp redis namespace.

$ wp help redis

NAME

  wp redis

SYNOPSIS

  wp redis <command>

SUBCOMMANDS

  cli         Launch redis-cli using Redis configuration for WordPress
  debug       Debug object cache hit / miss ratio for any page URL.
  enable      Enable WP Redis by creating the symlink for object-cache.php
  info        Provide details on the Redis connection.

Use wp help redis <command> to learn more about each command.

Contributing

See CONTRIBUTING.md for information on contributing.

Security Policy

Reporting Security Bugs

Please report security bugs found in the WP Redis plugin's source code through the Patchstack Vulnerability Disclosure Program. The Patchstack team will assist you with verification, CVE assignment, and notify the developers of this plugin.

Frequently Asked Questions

Why would I want to use this plugin?

If you are concerned with the speed of your site, backing it with a high-performance, persistent object cache can have a huge impact. It takes load off your database, and is faster for loading all the data objects WordPress needs to run.

How does this work with other caching plugins?

This plugin is for the internal application object cache. It doesn't have anything to do with page caches. On Pantheon you do not need additional page caching, but if you are self-hosted you can use your favorite page cache plugins in conjunction with WP Redis.

How do I disable the persistent object cache for a bad actor?

A page load with 2,000 Redis calls can be 2 full seconds of object cache transactions. If a plugin you're using is erroneously creating a huge number of cache keys, you might be able to mitigate the problem by disabling cache persistency for the plugin's group:

wp_cache_add_non_persistent_groups( array( 'bad-actor' ) );

This declaration means use of wp_cache_set( 'foo', 'bar', 'bad-actor' ); and wp_cache_get( 'foo', 'bad-actor' ); will not use Redis, and instead fall back to WordPress' default runtime object cache.

Why does the object cache sometimes get out of sync with the database?

There's a known issue with WordPress alloptions cache design. Specifically, a race condition between two requests can cause the object cache to have stale values. If you think you might be impacted by this, review this GitHub issue for links to more context, including a workaround.

Changelog

1.4.5-dev

1.4.4 (November 27, 2023)

  • Updates Pantheon WP Coding Standards to 2.0 [#445]
  • Handle duplicate keys in get_multiple function [#448] (props @Souptik2001)

1.4.3 (June 26, 2023)

  • Bug fix: Fixes assumption that CACHE_PORT & CACHE_PASSWORD are Set. [428] (props @timnolte)
  • Adds WP.org validation GitHub action [#435]
  • Bug fix: Fixes incorrect order of array_replace_recursive and other issues [434] (props @timnolte)
  • Bug fix: Replace use of wp_strip_all_tags in object-cache.php [434] (props @timnolte)
  • Bug fix: Don't strip tags from the cache password. [434] (props @timnolte)

1.4.2 (May 15, 2023)

  • Bug fix: Removes exception loop caused by esc_html in _exception_handler() [421]

1.4.1 (May 11, 2023)

  • Bug fix: wp_cache_flush_runtime should only clear the local cache [413]

1.4.0 (May 9, 2023)

  • Add support for flush_runtime and flush_group functions [#405]
  • Add pantheon-wp-coding-standards [#400]
  • Update CONTRIBUTING.MD [#406]
  • Update Composer dependencies [#401]

1.3.5 (April 6, 2023)

  • Bump tested up to version to 6.2
  • Update Composer dependencies [#394]

1.3.4 (March 7, 2023)

  • Set missing_redis_message if Redis service is not connected [#391]

1.3.3 (February 28, 2023)

  • Add PHP 8.2 support [#388]
  • Remove Grunt, add valid license to Composer file [#387]
  • Update Composer dependencies [#384] [#385]

1.3.2 (December 5, 2022)

  • Fix broken wp_cache_supports function [#382].

1.3.1 (December 2, 2022)

  • Declare wp_cache_supports function and support features. [#378]
  • Make dependabot target develop branch for PRs. [#376]
  • Declare wp_cache_supports function and support features. [#378]

1.3.0 (November 29, 2022)

  • Added CONTRIBUTING.MD and GitHub action to automate deployments to wp.org. [#368]

1.2.0 (February 17, 2022)

  • Adds support for Relay via WP_REDIS_USE_RELAY constant [#344].

1.1.4 (October 21, 2021)

  • Fixes some faulty logic in WP_REDIS_IGNORE_GLOBAL_GROUPS check [#333].

1.1.3 (October 21, 2021)

  • Supports a WP_REDIS_IGNORE_GLOBAL_GROUPS constant to prevent groups from being added to global caching group [#331].

1.1.2 (March 24, 2021)

  • Applies logic used elsewhere to fall back to $_SERVER in wp_redis_get_info() [#316].

1.1.1 (August 17, 2020)

  • Returns cache data in correct order when using wp_cache_get_multiple() and internal cache is already primed [#292].

1.1.0 (July 13, 2020)

  • Implements wp_cache_get_multiple() for WordPress 5.5 [#287].
  • Bails early when connecting to Redis throws an Exception to avoid fatal error [285].

1.0.1 (April 14, 2020)

  • Adds support for specifying Redis database number from environment/server variables [#273].

1.0.0 (March 2, 2020)

  • Plugin is stable.

0.8.3 (February 24, 2020)

  • Fixes wp redis cli by using proc_open() directly, instead of WP_CLI::launch() [#268].

0.8.2 (January 15, 2020)

  • Catches exceptions when trying to connect to Redis [#265].

0.8.1 (January 10, 2020)

  • Adds WP_REDIS_DEFAULT_EXPIRE_SECONDS constant to set default cache expire value [#264].

0.8.0 (January 6, 2020)

  • Uses flushdb instead of flushAll to avoid flushing the entire Redis instance [#259].

0.7.1 (December 14, 2018)

  • Better support in wp_cache_init() for drop-ins like LudicrousDB [#231].
  • Cleans up PHPCS issues.

0.7.0 (August 22, 2017)

0.6.2 (June 5, 2017)

  • Bug fix: Preserves null values in internal cache.
  • Bug fix: Converts numeric values to their true type when getting.

0.6.1 (February 23, 2017)

  • Bug fix: correctly passes an empty password to redis-cli.
  • Variety of improvements to the test suite.

0.6.0 (September 21, 2016)

  • Introduces three new WP-CLI commands: wp redis debug to display cache hit/miss ratio for any URL; wp redis info to display high-level Redis statistics; wp redis enable to create the object-cache.php symlink.
  • Permits a Redis database to be defined with $redis_server['database'].
  • Introduces wp_cache_add_redis_hash_groups(), which permits registering specific groups to use Redis hashes, and is more precise than our existing WP_REDIS_USE_CACHE_GROUPS constant.

0.5.0 (April 27, 2016)

  • Performance boost! Removes redundant exists call from wp_cache_get(), which easily halves the number of Redis calls.
  • Uses add_action() and $wpdb in a safer manner for compatibility with Batcache, which loads the object cache before aforementioned APIs are available.
  • For debugging purposes, tracks number of calls to Redis, and includes breakdown of call types.
  • Adds a slew of more explicit test coverage against existing features.
  • For consistency with the actual Redis call, calls del instead of delete.
  • Bug fix: If a group isn't persistent, don't ever make an exists call against Redis.

0.4.0 (March 23, 2016)

  • Introduces wp redis-cli, a WP-CLI command to launch redis-cli with WordPress' Redis credentials.
  • Bug fix: Ensures fail back mechanism works as expected on multisite, by writing to sitemeta table instead of the active site's options table.
  • Bug fix: Uses 'default' as the default cache group, mirroring WordPress core, such that $wp_object_cache->add( 'foo', 'bar' ) === wp_cache_add( 'foo', 'bar' ).

0.3.0 (February 11, 2016)

  • Introduces opt-in support for Redis cache groups. Enable with define( 'WP_REDIS_USE_CACHE_GROUPS', true );. When enabled, WP Redis persists cache groups in a structured manner, instead of hashing the cache key and group together.
  • Uses PHP_CodeSniffer and WordPress Coding Standards sniffs to ensure WP Redis adheres to WordPress coding standards.
  • Bug fix: Permits use of a Unix socket in $redis_server['host'] by ensuring the supplied $port is null.

0.2.2 (November 24, 2015)

  • Bug fix: use INSERT IGNORE INTO instead of INSERT INTO to prevent SQL errors when two concurrent processes attempt to write failback flag at the same time.
  • Bug fix: use E_USER_WARNING with trigger_error().
  • Bug fix: catch Exceptions thrown during authentication to permit failing back to internal object cache.

0.2.1 (November 17, 2015)

  • Bug fix: prevent SQL error when $wpdb->options isn't yet initialized on multisite.

0.2.0 (November 17, 2015)

  • Gracefully fails back to the WordPress object cache when Redis is unavailable or intermittent. Previously, WP Redis would hard fatal.
  • Triggers a PHP error if Redis goes away mid-request, for you to monitor in your logs. Attempts one reconnect based on specific error messages.
  • Forces a flushAll on Redis when Redis comes back after failing. This behavior can be disabled with the WP_REDIS_DISABLE_FAILBACK_FLUSH constant.
  • Show an admin notice when Redis is unavailable but is expected to be.

0.1

  • Initial commit of working code for the benefit of all.

Upgrade Notice

1.4.0 (May 9, 2023)

WP Redis 1.4.0 adds support for the flush_runtime and flush_group functions. If you've copied object-cache.php and made your own changes, be sure to copy these additions over as well.

wp-redis's People

Contributors

danielbachhuber avatar dependabot[bot] avatar gablau avatar janthiel avatar jazzsequence avatar jimvandervoort avatar kporras07 avatar mathieuhays avatar mboynes avatar mikevanwinkle avatar nathanielks avatar partounian avatar paulocoghi avatar pwtyler avatar rwagner00 avatar souptik2001 avatar ssnepenthe avatar stevector avatar szepeviktor avatar tillkruss avatar timnolte avatar tobeycodes avatar westonruter 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  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

wp-redis's Issues

WP Redis v 4.0 performs better on Fedora 22 than v 5.0 for some plugins

Reference desk case 62005

It appears that when using WP Redis 5.0 on cacheservers running Fedora 22, it is necessary to click the 'Clear Caches' button in the WP Admin under 'Pantheon cache' in order for pages to render updated content in fields created by the Advance Custom Fields PRO plugin.

There may be other plugins that surface this issue, but this is the first report.

Add Multi get / set functions

I know this one might be a long shot, as the core team I still hashing out the implementation (details here) but it doesn't stop us implementing it on drop-in. It is then up to the developer to choose to use them or not.

Other object cache based drop-ins have done similar,

Redis supports multi gets and multi sets. It could ever be prefixed, something like this
wp_redis_cache_get_multi to stop conflicts with future core implementations.

Add a conditional for when Redis is not available

The conditional prevents the site from breaking if Redis is not available locally or on some environment. An admin message to inform would be good to have as well.

Would probably work by wrapping the whole object-cache.php file in a class_exists( 'Redis' ) conditional.

Additional CLI commands

  • install - could be used to link or copy the object-cache.php into the wp-content directory
  • check - could be used to check everything is installed and working, servers added to wp-config, servers are online and can be authenticated to, etc

If you like the idea of these and had ideas for other commands I can prepare a PR

AWS Elastic Cache cluster or master/replica

Hi everyone!

According to the installation readme, we need to add a "host" to use this plugin out of Pantheon. Since I am on AWS and use their Elastic Cache service for caching, I am forced to either chose a master/replica or cluster environment for Redis. How can I handle such configuration with wp-redis?

With the new failback solution, we're seeing the following errors in various environments

[13-Nov-2015 20:04:11 UTC] WordPress database error You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'WHERE option_name='wp_redis_do_redis_failback_flush'' at line 1 for query SELECT option_value FROM  WHERE option_name='wp_redis_do_redis_failback_flush' made by require_once('D:\home\site\wwwroot\wp-admin\admin.php'), require_once('D:\home\site\wwwroot\wp-load.php'), require_once('D:\home\site\wwwroot\wp-config.php'), require_once('D:\home\site\wwwroot\wp-settings.php'), wp_start_object_cache, wp_cache_init, WP_Object_Cache->__construct

WordPress database error Duplicate entry 'wp_redis_do_redis_failback_flush' for key 'option_name'

[18-Nov-2015 10:36:45 UTC] WordPress database error Duplicate entry 'wp_redis_do_redis_failback_flush' for key 'option_name' for query INSERT INTO `wp_options` (`option_name`, `option_value`) VALUES ('wp_redis_do_redis_failback_flush', '1') made by require('D:\home\site\wwwroot\wp-blog-header.php'), require_once('D:\home\site\wwwroot\wp-includes\template-loader.php'), do_feed, do_action('do_feed_rss2'), call_user_func_array, do_feed_rss2, load_template, require_once('D:\home\site\wwwroot\wp-includes\feed-rss2.php'), rss_enclosure, get_post_custom, get_post_meta, get_metadata, wp_cache_get, WP_Object_Cache->get, WP_Object_Cache->_exists, WP_Object_Cache->_call_redis

Fail gracefully if config is missing

Currently this dies pretty hard if the expected configuration isn't there. It should have a developer/user friendly exception handler so that people are guided to configure the plugin.

Better support cache keys without groups in native cache group mode

WordPress' default behavior is to assign wp_cache_set( 'foo', 'bar' ) to a group called default. However, when group=default in Redis, we may want to store these as top-level cache entries, instead of in the default group hash, because this will let us set expiry on the individual values.

WP-Redis snippets

Could we have a Wiki page or a subdirectory with useful snippets?

Like #47 (comment)
and

if ( function_exists( 'wp_cache_flush' ) ) {
    add_action( 'admin_bar_menu', 'o1_flush_cache_button', 100 );
}

function o1_flush_cache_button( $wp_admin_bar ) {

    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }

    if ( 'flush' === $_GET['flush-cache-button']
        && wp_verify_nonce( $_GET['_wpnonce'], 'flush-cache-button' )
    ) {
        wp_cache_flush();
        add_action( 'admin_notices', function () {
            echo '<div class="notice notice-success is-dismissible"><p>Object Cache flushed.</p></div>';
        } );
    }

    $dashboard_url = admin_url( add_query_arg( 'flush-cache-button', 'flush', 'index.php' ) );
    $args = array(
        'id'    => 'flush_cache_button',
        'title' => 'Flush Object Cache',
        'href'  => wp_nonce_url( $dashboard_url, 'flush-cache-button' ),
        'meta'  => array( 'class' => 'flush-cache-button' )
    );
    $wp_admin_bar->add_node( $args );
}

Unknown URI scheme "unix"

Having this error in HHVM at every connection:

[Mon Apr 18 20:29:39 2016] [hphp] [24355:7fe0ab7ff700:2:000001] [] \nWarning: Unknown URI scheme "unix" in /wp-content/object-cache.php on line 948

Fixed it by commenting out the IF statement and leaving only the $port = null;

Fall back to non-persistent object caching if Redis is unavailable

[27-Jun-2015 23:12:44 UTC] PHP Fatal error: Uncaught exception 'RedisException' with message 'Redis server went away' in PATH/object-cache.php:713
[28-Jun-2015 03:10:20 UTC] PHP Fatal error: Uncaught exception 'RedisException' with message 'read error on connection' in PATH/object-cache.php:652

There should be some handling in place for these errors. Ideally, we would be catching these exceptions and dumping them to the error log so that we know when these errors are occurring as well as what the errors are, but the site should simply fall back to connecting to the DB for its info, rather than fatal erring.

Add a status page in wp-admin

It would be nice to have a status page in the wp-admin area that could tell if the cache is being used. When demoing I often forget to create the object-cache.php file, and a status page could tell me this. It could also present the data from Redis' info command.

PHP notice on Socket connection

Hi,
when wp-redis is in socket configuration and you do not specify port parameter reise a php notice.

example config:
$redis_server = array( 'host' => '/tmp/redis.sock');

php Notice:
Notice: Undefined index: port in /var/www/vhosts/testsite.com/httpdocs/wp-content/object-cache.php on line 709

Allow only some group to delete whole group deletes

When WP_REDIS_USE_CACHE_GROUPS is set to true, then a hash set is used to store keys. However this means that the expires field is then ignored, a ttls are ignored. However there are valid reasons to have ttl on caches and not invalidating them can break many things. There are examples in core where caches should invalidate themselves. Good example is here.

A new method be added (similar to wp_cache_add_non_persistent_groups), that allows a developers to define which groups should be allowed to be delete and which group still respect the expire time.

For example, I would want the cache group of "posts" to allow to deleted, by the terms or batcache group not.

Ensure default cache group is always 'default'

The signature for wp_cache_add():

function wp_cache_add( $key, $data, $group = '', $expire = 0 ) {`

The signature for WP_Object_Cache->add():

public function add( $key, $data, $group = 'default', $expire = 0 ) {

WordPress core has the following within its methods so that $group is never empty:

if ( empty( $group ) )
    $group = 'default';

We, however, do not.

Related #68

WP Pantheon Cache on Multisite does not clear Redis

Clearing cache via Pantheon Cache does not appear to do anything within a WP multisite site.

Steps to reproduce:

Create a super admin WP user for a multisite in Live:
ex: terminus wp "user create csecse2 [email protected] --user_pass=manticore --role=administrator" --site=candela --env=live

terminus wp "super-admin add csecse2" --env=live --site=candela

Connect to redis-cli for the multisite in Live, run:

ex: KEYS "49:*"

substitute the number in KEYS for the site multisite with actual keys

clear cache on the page in the site via Pantheon Cache

then run KEYS "49:*" again

there are no cleared keys.

104.239.201.36:11983> dbsize
(integer) 3406

Clearing the caches through the dashboard resulted in an actual clear:

[2:56] 
104.239.201.36:11982> dbsize
(integer) 0

Meta headers

WP reads meta headers from object-cache.php
Please consider adding some of these lines.

/*
Plugin Name: 
Version: 
Description: 
Plugin URI: 
Author: 
*/

Can we remove `exists` calls to Redis?

On WordPress sites heavily utilizing the object cache, the exists calls essentially double the number of network requests on a given page load. Can we remove these calls to reduce the number of network requests?

Retry on 'Redis server went away'

There are cases where the Redis server has a idle client timeout (on Pantheon it is 5 minutes) and a super-long-running process could loose the connection as a result.

The code should attempt to re-open the connection when this exception is hit. If that cannot be done โ€” e.g. because the server actually died โ€” then it should follow the behavior of #4 and fall back to the DB.

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.