Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add support for Single Post Subscription (including fix as per Google Extended Access UX) #2

Open
wants to merge 31 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
edda470
Add `isAccessibleForFree` property in Yoast SEO for Article schema
AnuragVasanwala Mar 29, 2023
7e23eda
Fix `isAccessibleForFree` assignment
AnuragVasanwala Mar 29, 2023
ac6b8cc
✨ Add Extended Access functionality
AnuragVasanwala Apr 14, 2023
0a4f7ee
🏗 ServerSide script to bypass paywall restriction
AnuragVasanwala Apr 16, 2023
485747b
♻️ Add `Single_Post_Subscription` to plugin `init`
AnuragVasanwala Apr 16, 2023
e48a4f7
✨ Add `Extended Access` submenu option page inside `WooCommerce`
AnuragVasanwala Apr 16, 2023
1ec4c1d
Merge branch 'feat/single-post-subscription' into feat/option-page
AnuragVasanwala Apr 19, 2023
4ca86f6
Merge pull request #3 from rtCamp/feat/option-page
AnuragVasanwala Apr 19, 2023
7fa81a7
✨ Add single-post subscription feature based on cookie
AnuragVasanwala Apr 25, 2023
68d6731
Merge branch 'feat/single-post-subscription' of https://github.com/rt…
AnuragVasanwala Apr 25, 2023
84a9439
♻️ Resolve merge conflict
AnuragVasanwala Apr 25, 2023
5e87bef
♻️ Minor Refactoring
AnuragVasanwala Apr 25, 2023
d8bc40d
🧪 Add Unit-tests for Single Post Subscription
AnuragVasanwala Apr 27, 2023
b3f4c90
♻️ Minor fixes related to coding-standards
AnuragVasanwala Apr 27, 2023
4435196
♻️ Minor fixes and code-formatting
AnuragVasanwala Apr 27, 2023
488958c
♻️ Minor fixes and code improvement
AnuragVasanwala Apr 27, 2023
4639dd7
🐛 Fix admin notice issue
AnuragVasanwala Apr 27, 2023
8ef2471
Merge pull request #9 from rtCamp/fix/notice-issue
AnuragVasanwala Apr 27, 2023
bc8ffcb
🔥 Fix critical issue where `$allowed_referrers` is not set correctly
AnuragVasanwala Apr 27, 2023
73d86ec
Merge pull request #10 from rtCamp/fix/notice-issue
AnuragVasanwala Apr 27, 2023
4c5c0c9
🏗 Fix `phpunit` version and Update incorrect variable type
AnuragVasanwala Apr 27, 2023
407275a
♻️ Refactor code, Add `test-` prefix for test case file
AnuragVasanwala Apr 27, 2023
d08f469
♻️ Name fixes, Improve comments, Remove unnecessary code
AnuragVasanwala Apr 27, 2023
9e8ae5a
📖 Add readme and images
AnuragVasanwala Apr 28, 2023
76ede70
♻️ Code refactoring
AnuragVasanwala Apr 28, 2023
1419683
🚮 Remove unnecessary comments
AnuragVasanwala Apr 28, 2023
b27357c
🔥 Fix UX flow as per Google Extended Access
AnuragVasanwala Apr 28, 2023
52d6a7b
🐛 Page should not reload for existing `Subscriber`
AnuragVasanwala Apr 28, 2023
2df1ea8
♻️ Refactor and minor fixes
AnuragVasanwala Apr 28, 2023
2944380
Merge commit '9e8ae5a82019bf29abe32eb2657432348276556f' into fix/goog…
AnuragVasanwala Apr 28, 2023
a425b87
Merge pull request #2 from AnuragVasanwala/fix/google-extended-access-ux
AnuragVasanwala Apr 28, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 32 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
# newspack-extended-access
Extended Access integration for Newspack
# Newspack Extended Access

## Dev Setup
1. `composer install` to build the plugin.
2. `vendor/bin/phpcs` to run the code sniffer.
Google Extended Access integration wrapper plugin for Newspack. The plugin enables readers to unlock free access to select paywalled articles on registration.

![Google Extended Access Prompt](assets/media/newspack-extended-access.png)

## Dependency

This plugin requires following plugin to be installed, configured and active:
- [Newspack Plugin](https://github.com/Automattic/newspack-plugin)
- [WooCommerce](https://github.com/woocommerce/woocommerce)
- [WooCommerce Memberships](https://woocommerce.com/products/woocommerce-memberships/)

## Configuration

This plugin requires a valid `Google Client API ID` specific to your Newspack Site domain. Please create a new `Google Client API ID` for your site, if not already created. Follow instruction provided on [Setting up Google Client API ID]([https://](https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid)).

> Make sure [dependent plugins](#dependency) are installed, configured and active on your site.

### Setting up Google Client API ID

Login to you Newspack site dashboard as an `Administrator`:
1. Open `WooCommerce` menu
2. Select `Settings` menu
3. Select `Memberships` tab
4. Select sub-tab `Newspack Extended Access`
5. Add your valid `Google Client API ID`

![Configure Google Client API ID](assets/media/configure-google-client-api-id.png)

## License

Newspack Extended Access is licensed under [GNU General Public License v3 (or later)](./LICENSE).
233 changes: 233 additions & 0 deletions assets/js/newspack-swg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
/**
* Newspack SwG Library.
*
* Initializes GAA and defines required callbacks to
* register / login to site via SwG, check post status
* and unlock article.
*
* @link https://www.newspack.com
* @file This files defines SwG required methods and callback for Newspack specific functionality.
* @author Newspack
* @since 1.0
* /

/**
* Holds logged-in user email for few REST Endpoints.
*/
let loggedInUserEmail = "";

/**
* Parses JWT token and converts into equivalent JSON object.
*
* @param {string} token JWT Token to be parse.
* @returns {Object} Parsed JWT as JSON Object.
*/
function parseJwt(token) {
var base64Url = token.split('.')[1];
var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
var jsonPayload = decodeURIComponent(
window.atob(base64).split('').map(
function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}
).join('')
);

return JSON.parse(jsonPayload);
}

/**
* Creates a cookie.
*
* @param {string} name Name of the cookie.
* @param {string} value Value to be stored.
* @param {number} days Expire cookie after specified days.
*/
function setCookie(name, value, days) {
var expires = "";
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + (value || "") + expires + "; path=/";
}

/**
* Retrieves cookie value.
*
* @param {string} name Name of the cookie.
* @returns {string|null} Returns string value if valid cookie is present else null.
*/
function getCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
}

/**
* Deletes cookie.
*
* @param {string} name Name of the cookie.
*/
function eraseCookie(name) {
document.cookie = name + '=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
}

/**
* Initializes GaaMetering for SwG.
*/
function initGaaMetering() {

/**
* Referrers to be allowed.
*/
const allowedReferrers = authenticationSettings.allowedReferrers;

/**
* Login Existing User Promise callback handler.
*/
handleLoginPromise = new Promise(
() => {
GaaMetering.getLoginPromise().then(
() => {
// Capture full URL, including URL parameters, to redirect the user to after login
const redirectUri = encodeURIComponent(window.location.href);
// Redirect to a login page for existing users to login.
window.location = `${window.location.protocol}//${window.location.hostname}/my-account?redirect_to=${redirectUri}`;
}
);
}
);

/**
* Register New User Promise callback handler.
*/
registerUserPromise = new Promise(
(resolve) => {
// Get the information for the user who has just registered.
GaaMetering.getGaaUserPromise().then(
(gaaUser) => {
// Send that information to your Registration endpoint to register the user and
// return the userState for the newly registered user.

const gaaUserDecoded = parseJwt(gaaUser.credential);
loggedInUserEmail = gaaUserDecoded.email;
fetch(
`${window.location.protocol}//${window.location.hostname}/wp-json/newspack-extended-access/v1/google/register`,
{
cache: 'no-store',
method: 'POST',
headers: {
'Content-type': 'text/plain',
'X-WP-Nonce': authenticationSettings.nonce,
'X-WP-Post-ID': authenticationSettings.postID
},
body: gaaUser.credential
}
)
.then(response => response.json())
.then(
userState => {
if(userState.grantReason === 'SUBSCRIBER'){
window.location.reload();
}
resolve(userState);
}
);
}
);
}
);

/**
* Check whether publisher has provided access to the User or not.
*/
publisherEntitlementPromise = new Promise(
(resolve) => {
resolve({ granted: false });
}
);

/**
* Check whether publisher has provided access to the User or not.
*/
getUserState = new Promise(
(resolve) => {
fetch(
`${window.location.protocol}//${window.location.hostname}/wp-json/newspack-extended-access/v1/login/status`,
{
cache: 'no-store',
method: 'GET',
headers: {
'Content-type': 'text/plain',
'X-WP-Nonce': authenticationSettings.nonce,
'X-WP-Post-ID': authenticationSettings.postID
},
}
)
.then(response => response.json())
.then(
userState => {
loggedInUserEmail = userState.email;
resolve(userState);
}
);
}
);

/**
* Fires when Extended Access grants permission.
*/
unlockArticle = () => {
fetch(
`${window.location.protocol}//${window.location.hostname}/wp-json/newspack-extended-access/v1/unlock-article`,
{
cache: 'no-store',
method: 'GET',
headers: {
'X-WP-Post-ID': authenticationSettings.postID,
'X-WP-User-Email': loggedInUserEmail
}
}
)
.then(response => response.json())
.then(jsonData => {
if (jsonData.status === 'UNLOCKED') {
if (getCookie(jsonData.c) === null) {
setCookie(jsonData.c, 'true', 365);
window.location.reload();
}
}
});
}

/**
* Display custom paywall instead of Google Intervention Dialog.
*/
showPaywall = () => {
// Redirect to a subscription page.
window.location = `${window.location.protocol}//${window.location.hostname}/subscribe`;
}

/**
* Initialize GAA for Extended Access.
*/
GaaMetering.init(
{
googleApiClientId: authenticationSettings.googleClientApiID,
userState: getUserState,
allowedReferrers: allowedReferrers,
handleLoginPromise: handleLoginPromise,
registerUserPromise: registerUserPromise,
publisherEntitlementPromise: getUserState,
unlockArticle: unlockArticle,
showPaywall: showPaywall,
}
);
}
Binary file added assets/media/configure-google-client-api-id.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/media/extended-access-dialog.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/media/extended-access-dialog.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/media/newspack-extended-access.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading