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

[WIP] [BC] General project maintenance #236

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e91985a
move depdency management from living in code to living in composer co…
ryancco Jun 4, 2019
7b31ecc
Standardization and cleanup
ryancco Jun 5, 2019
dae1c89
Merge pull request #2 from ryancco/standardization-and-cleanup
ryancco Jun 5, 2019
256a513
renaming openssl depdendency
ryancco Jun 5, 2019
7173909
Merge pull request #1 from ryancco/depdendency-management
ryancco Jun 5, 2019
ff28200
PSR-4 development
ryancco Jun 5, 2019
e7de87c
Merge pull request #3 from ryancco/development-psr4
ryancco Jun 5, 2019
7ab1fe4
Notification tests
ryancco Jun 5, 2019
e88ba23
Merge pull request #4 from ryancco/notification-test
ryancco Jun 5, 2019
eb7ddcf
Simplifying MessageSentReport
ryancco Jun 5, 2019
ed52704
Merge pull request #5 from ryancco/simplifying-messagesentreport
ryancco Jun 5, 2019
0b150b9
fixing a few static analysis issues i've let slip through the cracks
ryancco Jun 5, 2019
990b95e
Merge pull request #6 from ryancco/boyscouting
ryancco Jun 5, 2019
3c9bdf0
Separating test suites
ryancco Jun 5, 2019
7310521
this file needs to be cleaned up if we plan on skipping integration t…
ryancco Jun 5, 2019
0ee2485
Merge pull request #7 from ryancco/separating-test-suites
ryancco Jun 5, 2019
e222206
Implement an http client to begin the process of abstracting http com…
ryancco Jun 15, 2019
8bf6f80
Merge pull request #8 from ryancco/http-client
ryancco Jun 15, 2019
4bf92e9
implement options vo (#9)
ryancco Jun 17, 2019
6515b0d
miscellaneous and formatting fixes
ryancco Jun 17, 2019
9e09e3b
Http client decoupling (#10)
ryancco Jun 20, 2019
a718b62
Revert "Http client decoupling (#10)" (#11)
ryancco Jun 20, 2019
78bff65
Http client decoupling (#12)
ryancco Jun 20, 2019
e7cc878
Implement Auth, drop support for GCM (#13)
ryancco Jun 21, 2019
afc2cc5
Headers (#14)
ryancco Jun 21, 2019
ea9646e
WIP (#15)
ryancco Jun 25, 2019
97cea56
add php 7.3 as test candidate
ryancco Jun 25, 2019
f278579
First round review changes (#16)
ryancco Jun 25, 2019
1d02834
removing php7.4 as a test candidate as we have incompatible dependenc…
ryancco Jun 25, 2019
6781ca9
update readme
ryancco Jun 25, 2019
ea43b8d
fixing docs: guzzle6 not guzzle2
ryancco Jun 25, 2019
8aa117c
removing unnecessary dependency
ryancco Jun 26, 2019
c77a197
implementing psr1,2,12 style fixing with phpcbf, removing Subscriptio…
ryancco Jun 26, 2019
e96d01e
cache miss handling per @spomky review
ryancco Jun 26, 2019
ac7a1ce
removing some left over traces of GCM
ryancco Jun 26, 2019
2a3a20b
fixed integration tests (#17)
ryancco Jun 27, 2019
0206f1a
using fork of web-push-testing-service temporarily; running full test…
ryancco Jul 17, 2019
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
2 changes: 1 addition & 1 deletion .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ alias web-push-testing-service='/usr/local/Cellar/node/7.4.0/bin/web-push-testin

After that, please create your own configuration file by copying
`phpunit.dist.xml` to phpunit.xml and filling in the fields you need for
testing (i.e. STANDARD_ENDPOINT, GCM_API_KEY etc.).
testing (i.e. STANDARD_ENDPOINT, etc.).

## Running Tests

Expand Down
12 changes: 5 additions & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@ cache:
- $HOME/.composer/cache/files # and composer packages

matrix:
allow_failures:
- php: nightly
fast_finish: true
include:
- php: 7.1
- php: 7.2
- php: nightly
- php: '7.1'
- php: '7.2'
- php: '7.3'

env:
- TRAVIS_NODE_VERSION="stable"
Expand All @@ -27,7 +25,7 @@ before_install:
- nvm install node

install:
- npm install github:GoogleChromeLabs/web-push-testing-service -g
- npm install github:ryancco/web-push-testing-service -g

before_script:
- composer install --prefer-source -n
Expand All @@ -37,7 +35,7 @@ before_script:

script:
- web-push-testing-service start example -p 9012
- composer test:unit
- composer test
- web-push-testing-service stop example
- composer test:typing
- composer test:syntax
145 changes: 52 additions & 93 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ As it is standardized, you don't have to worry about what server type it relies

## Requirements

* PHP 7.1+
* gmp
* mbstring
* curl
* openssl

PHP 7.2+ is recommended for better performance.
* PHP 7.1+ (_recommended 7.3 for increased performance_)
* [Async HTTP client](https://packagist.org/providers/php-http/async-client-implementation)
* [PSR-7 implementation](https://packagist.org/providers/psr/http-message-implementation)
* gmp
* mbstring
* curl
* openssl

There is no support and maintenance for older PHP versions, however you are free to use the following compatible versions:
- PHP 5.6 or HHVM: `v1.x`
Expand All @@ -25,82 +25,66 @@ There is no support and maintenance for older PHP versions, however you are free
## Installation
Use [composer](https://getcomposer.org/) to download and install the library and its dependencies.

`composer require minishlink/web-push`
`composer require minishlink/web-push php-http/guzzle6-adapter guzzlehttp/psr7`

## Usage
```php
<?php

use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Subscription;
use Minishlink\WebPush\SubscriptionFactory;

// array of notifications
$notifications = [
[
'subscription' => Subscription::create([
'subscription' => SubscriptionFactory::create([
'endpoint' => 'https://updates.push.services.mozilla.com/push/abc...', // Firefox 43+,
'publicKey' => 'BPcMbnWQL5GOYX/5LKZXT6sLmHiMsJSiEvIFvfcDvX7IZ9qqtq68onpTPEYmyxSQNiH7UD/98AUcQ12kBoxz/0s=', // base 64 encoded, should be 88 chars
'authToken' => 'CxVX6QsVToEGEcjfYPqXQw==', // base 64 encoded, should be 24 chars
]),
'payload' => 'hello !',
], [
'subscription' => Subscription::create([
'endpoint' => 'https://android.googleapis.com/gcm/send/abcdef...', // Chrome
]),
'payload' => null,
], [
'subscription' => Subscription::create([
'subscription' => SubscriptionFactory::create([
'endpoint' => 'https://example.com/other/endpoint/of/another/vendor/abcdef...',
'publicKey' => '(stringOf88Chars)',
'authToken' => '(stringOf24Chars)',
'contentEncoding' => 'aesgcm', // one of PushManager.supportedContentEncodings
]),
'payload' => '{msg:"test"}',
], [
'subscription' => Subscription::create([ // this is the structure for the working draft from october 2018 (https://www.w3.org/TR/2018/WD-push-api-20181026/)
"endpoint" => "https://example.com/other/endpoint/of/another/vendor/abcdef...",
"keys" => [
'subscription' => SubscriptionFactory::create([ // this is the structure for the working draft from october 2018 (https://www.w3.org/TR/2018/WD-push-api-20181026/)
'endpoint' => 'https://example.com/other/endpoint/of/another/vendor/abcdef...',
'keys' => [
'p256dh' => '(stringOf88Chars)',
'auth' => '(stringOf24Chars)'
],
]),
'payload' => '{msg:"Hello World!"}',
'payload' => 'Hello World!',
],
];

$webPush = new WebPush();

// send multiple notifications with payload
foreach ($notifications as $notification) {
$webPush->sendNotification(
$webPush->queueNotification(
$notification['subscription'],
$notification['payload'] // optional (defaults null)
$notification['payload'] ?? null // optional, string value (defaults null)
);
}

/**
* Check sent results
* @var MessageSentReport $report
* @var Minishlink\WebPush\MessageSentReport $report
*/
foreach ($webPush->flush() as $report) {
$endpoint = $report->getRequest()->getUri()->__toString();
foreach ($webPush->deliver() as $report) {
$endpoint = $report->getEndpoint();

if ($report->isSuccess()) {
echo "[v] Message sent successfully for subscription {$endpoint}.";
} else {
echo "[x] Message failed to sent for subscription {$endpoint}: {$report->getReason()}";
echo "[x] Message failed to send for subscription {$endpoint}: {$report->getReasonPhrase()}";
}
}

/**
* send one notification and flush directly
* @var \Generator<MessageSentReport> $sent
*/
$sent = $webPush->sendNotification(
$notifications[0]['subscription'],
$notifications[0]['payload'], // optional (defaults null)
true // optional (defaults false)
);
```

### Full examples of Web Push implementations
Expand All @@ -118,22 +102,18 @@ You can specify your authentication details when instantiating WebPush. The keys
<?php

use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Authorization;

$endpoint = 'https://android.googleapis.com/gcm/send/abcdef...'; // Chrome

$auth = [
'GCM' => 'MY_GCM_API_KEY', // deprecated and optional, it's here only for compatibility reasons
'VAPID' => [
'subject' => 'mailto:[email protected]', // can be a mailto: or your website address
'publicKey' => '~88 chars', // (recommended) uncompressed public key P-256 encoded in Base64-URL
'privateKey' => '~44 chars', // (recommended) in fact the secret multiplier of the private key encoded in Base64-URL
'pemFile' => 'path/to/pem', // if you have a PEM file and can link to it on your filesystem
'pem' => 'pemFileContent', // if you have a PEM file and want to hardcode its content
],
];
$auth = new Authorization(
'private_key', // ~44 chars, the secret multiplier of the private key encoded in Base64-URL
'public_key', // ~88 chars, uncompressed public key P-256 encoded in Base64-URL
'subject' // can be a mailto: or your website address
);

$webPush = new WebPush($auth);
$webPush->sendNotification(...);
$webPush->queueNotification(...);
```

In order to generate the uncompressed public and secret key, encoded in Base64, enter the following in your Linux bash:
Expand Down Expand Up @@ -161,31 +141,35 @@ serviceWorkerRegistration.pushManager.subscribe({
VAPID headers make use of a JSON Web Token (JWT) to verify your identity. That token payload includes the protocol and hostname of the endpoint included in the subscription and an expiration timestamp (usually between 12-24h), and it's signed using your public and private key. Given that, two notifications sent to the same push service will use the same token, so you can reuse them for the same flush session to boost performance using:

```php
$webPush->setReuseVAPIDHeaders(true);
$webPush->enableVapidHeaderReuse();
```

### Notifications and default options
Each notification can have a specific Time To Live, urgency, and topic.
You can change the default options with `setDefaultOptions()` or in the constructor:
You can specify options by specifying the `$options` parameter to the `WebPush` constructor, or override those by specifying the `$options` parameter to the `WebPush::queueNotification()` call:

```php
<?php

use Minishlink\WebPush\WebPush;
use Minishlink\WebPush\Options;

$defaultOptions = [
'TTL' => 300, // defaults to 4 weeks
$options = new Options([
'ttl' => 300, // defaults to 4 weeks if left empty
'urgency' => 'normal', // protocol defaults to "normal"
'topic' => 'new_event', // not defined by default,
'batchSize' => 200, // defaults to 1000
];
]);

$overrides = new Options([
'ttl' => '600'
]);

// for every notifications
$webPush = new WebPush([], $defaultOptions);
$webPush->setDefaultOptions($defaultOptions);
$webPush = new WebPush(null, $options);

// or for one notification
$webPush->sendNotification($subscription, $payload, $flush, ['TTL' => 5000]);
// or, for one individual notification - this will also
// override the default options specified on $webpush
$webPush->queueNotification($subscription, $payload, $auth, $options);
```

#### TTL
Expand All @@ -201,39 +185,27 @@ Urgency can be either "very-low", "low", "normal", or "high". If the browser ven
#### topic
Similar to the old `collapse_key` on legacy GCM servers, this string will make the vendor show to the user only the last notification of this topic (cf. [protocol](https://tools.ietf.org/html/draft-ietf-webpush-protocol-08#section-5.4)).

#### batchSize
If you send tens of thousands notifications at a time, you may get memory overflows due to how endpoints are called in Guzzle.
In order to fix this, WebPush sends notifications in batches. The default size is 1000. Depending on your server configuration (memory), you may want
to decrease this number. Do this while instanciating WebPush or calling `setDefaultOptions`. Or, if you want to customize this for a specific flush, give
it as a parameter : `$webPush->flush($batchSize)`.

### Server errors
You can see what the browser vendor's server sends back in case it encountered an error (push subscription expiration, wrong parameters...).
`sendNotification()` (with `$flush` as `true`) and `flush()` **always** returns a [`\Generator`](http://php.net/manual/en/language.generators.php) with [`MessageSentReport`](https://github.com/web-push-libs/web-push-php/blob/master/src/MessageSentReport.php) objects, even if you just send one notification.
`WebPush::deliver()` **always** returns a [`\Generator`](http://php.net/manual/en/language.generators.php) with [`MessageSentReport`](https://github.com/web-push-libs/web-push-php/blob/master/src/MessageSentReport.php) objects.
To loop through the results, just pass it into `foreach`. You can also use [`iterator_to_array`](http://php.net/manual/en/function.iterator-to-array.php) to check the contents while debugging.

```php
<?php

/** @var \Minishlink\WebPush\MessageSentReport $report */
foreach ($webPush->flush() as $report) {
/** @var Minishlink\WebPush\MessageSentReport $report */
foreach ($webPush->deliver() as $report) {
$endpoint = $report->getEndpoint();

if ($report->isSuccess()) {
echo "[v] Message sent successfully for subscription {$endpoint}.";
} else {
echo "[x] Message failed to sent for subscription {$endpoint}: {$report->getReason()}";
echo "[x] Message failed to sent for subscription {$endpoint}: {$report->getReasonPhrase()}";

// also available (to get more info)

/** @var \Psr\Http\Message\RequestInterface $requestToPushService */
$requestToPushService = $report->getRequest();

/** @var \Psr\Http\Message\ResponseInterface $responseOfPushService */
$responseOfPushService = $report->getResponse();

/** @var string $failReason */
$failReason = $report->getReason();
$failReason = $report->getReasonPhrase();

/** @var bool $isTheEndpointWrongOrExpired */
$isTheEndpointWrongOrExpired = $report->isSubscriptionExpired();
Expand Down Expand Up @@ -277,27 +249,14 @@ Here are some ideas of settings:
use Minishlink\WebPush\WebPush;

$webPush = new WebPush();
$webPush->setAutomaticPadding(false); // disable automatic padding
$webPush->setAutomaticPadding(512); // enable automatic padding to 512 bytes (you should make sure that your payload is less than 512 bytes, or else an attacker could guess the content)
$webPush->setAutomaticPadding(true); // enable automatic padding to default maximum compatibility length
$webPush->setPadding(false); // disable automatic padding
$webPush->setPadding(512); // enable automatic padding to 512 bytes (you should make sure that your payload is less than 512 bytes, or else an attacker could guess the content)
$webPush->setPadding(true); // enable automatic padding to default maximum compatibility length
```

### Customizing the HTTP client
WebPush uses [Guzzle](https://github.com/guzzle/guzzle). It will use the most appropriate client it finds,
and most of the time it will be `MultiCurl`, which allows to send multiple notifications in parallel.

You can customize the default request options and timeout when instantiating WebPush:
```php
<?php

use Minishlink\WebPush\WebPush;

$timeout = 20; // seconds
$clientOptions = [
\GuzzleHttp\RequestOptions::ALLOW_REDIRECTS => false,
]; // see \GuzzleHttp\RequestOptions
$webPush = new WebPush([], [], $timeout, $clientOptions);
```
WebPush uses [HTTPlug](https://httplug.io). It will use the most appropriate client it finds,
but you must first install [one](https://packagist.org/providers/php-http/async-client-implementation)

## Common questions

Expand Down
44 changes: 35 additions & 9 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@
"name": "minishlink/web-push",
"type": "library",
"description": "Web Push library for PHP",
"keywords": ["push", "notifications", "web", "WebPush", "Push API"],
"keywords": [
"push",
"notifications",
"web",
"WebPush",
"Push API"
],
"homepage": "https://github.com/web-push-libs/web-push-php",
"license": "MIT",
"authors": [
Expand All @@ -13,27 +19,47 @@
}
],
"scripts": {
"test:unit": "./vendor/bin/phpunit --color",
"test": "./vendor/bin/phpunit --color",
"test:unit": "./vendor/bin/phpunit --color --testsuite Unit",
"test:integration": "./vendor/bin/phpunit --color --testsuite Integration",
"test:typing": "./vendor/bin/phpstan analyse --level max src",
"test:syntax": "./vendor/bin/php-cs-fixer fix ./src --dry-run --stop-on-violation --using-cache=no"
"test:syntax": "./vendor/bin/php-cs-fixer fix ./src --dry-run --stop-on-violation --using-cache=no && ./vendor/bin/phpcs src --standard=psr1,psr2,psr12 --ignore=vendor -q",
"fix:syntax": "./vendor/bin/php-cs-fixer fix ./src --using-cache=no && ./vendor/bin/phpcbf src --standard=psr1,psr2,psr12 --ignore=vendor -q"
},
"require": {
"php": "^7.1",
"ext-json": "*",
"ext-curl": "*",
"ext-gmp": "*",
"lib-openssl": "*",
"guzzlehttp/guzzle": "^6.2",
"ext-openssl": "*",
"ext-mbstring": "*",
"web-token/jwt-signature": "^1.0",
"web-token/jwt-key-mgmt": "^1.0"
"web-token/jwt-key-mgmt": "^1.0",
ryancco marked this conversation as resolved.
Show resolved Hide resolved
"mockery/mockery": "^1.2",
"psr/http-message": "^1.0",
"psr/http-factory": "^1.0",
"php-http/httplug": "^2.0",
"php-http/discovery": "^1.0",
"php-http/async-client-implementation": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^7.0",
"phpstan/phpstan": "0.11.2",
"friendsofphp/php-cs-fixer": "^2.14"
"friendsofphp/php-cs-fixer": "^2.14",
"php-http/mock-client": "^1.3",
"guzzlehttp/psr7": "^1.5",
"http-interop/http-factory-guzzle": "^1.0",
"squizlabs/php_codesniffer": "^3.4",
"php-http/guzzle6-adapter": "^2.0"
},
"autoload": {
"psr-4" : {
"Minishlink\\WebPush\\" : "src"
"psr-4": {
"Minishlink\\WebPush\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"Minishlink\\WebPush\\Tests\\": "tests"
}
}
}
Loading