Skip to content

Commit

Permalink
Merge pull request #56 from iMattPro/persistpushes
Browse files Browse the repository at this point in the history
Support receiving push notifications if not logged in
  • Loading branch information
iMattPro authored Jun 16, 2024
2 parents d602afa + ef35567 commit 55e1cd8
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 6 deletions.
48 changes: 48 additions & 0 deletions migrations/add_webpush_token.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
/**
*
* phpBB Browser Push Notifications. An extension for the phpBB Forum Software package.
*
* @copyright (c) 2024, phpBB Limited <https://www.phpbb.com>
* @license GNU General Public License, version 2 (GPL-2.0)
*
*/

namespace phpbb\webpushnotifications\migrations;

use phpbb\db\migration\migration;

class add_webpush_token extends migration
{
public static function depends_on()
{
return ['\phpbb\webpushnotifications\migrations\add_webpush'];
}

public function effectively_installed(): bool
{
return $this->db_tools->sql_column_exists($this->table_prefix . 'wpn_notification_push', 'push_token');
}

public function update_schema(): array
{
return [
'add_columns' => [
$this->table_prefix . 'wpn_notification_push' => [
'push_token' => ['VCHAR', ''],
],
],
];
}

public function revert_schema(): array
{
return [
'drop_columns' => [
$this->table_prefix . 'wpn_notification_push' => [
'push_token',
],
],
];
}
}
8 changes: 8 additions & 0 deletions notification/method/webpush.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ class webpush extends messenger_base implements extended_method_interface
/** @var int Fallback size for padding if endpoint is mozilla, see https://github.com/web-push-libs/web-push-php/issues/108#issuecomment-2133477054 */
public const MOZILLA_FALLBACK_PADDING = 2820;

/** @var array Map for storing push token between db insertion and sending of notifications */
private $push_token_map = [];

/**
* Notification Method Web Push constructor
*
Expand Down Expand Up @@ -150,9 +153,11 @@ public function notify()
'avatar' => $this->prepare_avatar($notification->get_avatar()),
]),
'notification_time' => time(),
'push_token' => hash('sha256', random_bytes(32))
];
$data = self::clean_data($data);
$insert_buffer->insert($data);
$this->push_token_map[$notification->notification_type_id][$notification->item_id] = $data['push_token'];
}

$insert_buffer->flush();
Expand Down Expand Up @@ -226,7 +231,9 @@ protected function notify_using_webpush(): void
$data = [
'item_id' => $notification->item_id,
'type_id' => $notification->notification_type_id,
'user_id' => $notification->user_id,
'version' => $this->config['assets_version'],
'token' => hash('sha256', $user['user_form_salt'] . $this->push_token_map[$notification->notification_type_id][$notification->item_id]),
];
$json_data = json_encode($data);

Expand Down Expand Up @@ -342,6 +349,7 @@ public static function clean_data(array $data): array
'item_parent_id' => null,
'user_id' => null,
'push_data' => null,
'push_token' => null,
'notification_time' => null,
];

Expand Down
8 changes: 7 additions & 1 deletion styles/all/template/push_worker.js.twig
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,16 @@ self.addEventListener('push', event => {

let itemId = 0;
let typeId = 0;
let notificationVersion = 5;
let userId = 0;
let notificationVersion = 0;
let pushToken = '';
try {
const notificationData = event.data.json();
itemId = notificationData.item_id;
typeId = notificationData.type_id;
userId = notificationData.user_id;
notificationVersion = parseInt(notificationData.version, 10);
pushToken = notificationData.token;
} catch {
self.registration.showNotification(event.data.text());
return;
Expand All @@ -45,6 +49,8 @@ self.addEventListener('push', event => {
const formData = new FormData();
formData.append('item_id', itemId.toString(10));
formData.append('type_id', typeId.toString(10));
formData.append('user_id', userId.toString(10));
formData.append('token', pushToken);

fetch(getNotificationUrl, {
method: 'POST',
Expand Down
80 changes: 75 additions & 5 deletions ucp/controller/webpush.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,37 @@ public function __construct(config $config, controller_helper $controller_helper
* @return JsonResponse
*/
public function notification(): JsonResponse
{
if (!$this->request->is_ajax() || $this->user->data['is_bot'] || $this->user->data['user_type'] == USER_INACTIVE)
{
throw new http_exception(Response::HTTP_FORBIDDEN, 'Forbidden');
}

if ($this->user->id() !== ANONYMOUS)
{
$notification_data = $this->get_user_notifications();
}
else
{
$notification_data = $this->get_anonymous_notifications();
}

// Decode and return data if everything is fine
$data = json_decode($notification_data, true);
$data['url'] = isset($data['url']) ? $this->path_helper->update_web_root_path($data['url']) : '';

return new JsonResponse($data);
}

/**
* Get notification data for logged in user
*
* @return string Notification data
*/
private function get_user_notifications(): string
{
// Subscribe should only be available for logged-in "normal" users
if (!$this->request->is_ajax() || $this->user->id() == ANONYMOUS || $this->user->data['is_bot']
|| $this->user->data['user_type'] == USER_IGNORE || $this->user->data['user_type'] == USER_INACTIVE)
if ($this->user->data['user_type'] == USER_IGNORE)
{
throw new http_exception(Response::HTTP_FORBIDDEN, 'Forbidden');
}
Expand All @@ -116,10 +143,53 @@ public function notification(): JsonResponse
$result = $this->db->sql_query($sql);
$notification_data = $this->db->sql_fetchfield('push_data');
$this->db->sql_freeresult($result);
$data = json_decode($notification_data, true);
$data['url'] = isset($data['url']) ? $this->path_helper->update_web_root_path($data['url']) : '';

return new JsonResponse($data);
return $notification_data;
}

/**
* Get notification data for not logged in user via token
*
* @return string
*/
private function get_anonymous_notifications(): string
{
$token = $this->request->variable('token', '');

if ($token)
{
$item_id = $this->request->variable('item_id', 0);
$type_id = $this->request->variable('type_id', 0);
$user_id = $this->request->variable('user_id', 0);

$sql = 'SELECT push_data, push_token
FROM ' . $this->notification_webpush_table . '
WHERE user_id = ' . (int) $user_id . '
AND notification_type_id = ' . (int) $type_id . '
AND item_id = ' . (int) $item_id;
$result = $this->db->sql_query($sql);
$notification_row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);

$notification_data = $notification_row['push_data'];
$push_token = $notification_row['push_token'];

// Check if passed push token is valid
$sql = 'SELECT user_form_salt
FROM ' . USERS_TABLE . '
WHERE user_id = ' . (int) $user_id;
$result = $this->db->sql_query($sql);
$user_form_token = $this->db->sql_fetchfield('user_form_salt');
$this->db->sql_freeresult($result);

$expected_push_token = hash('sha256', $user_form_token . $push_token);
if ($expected_push_token === $token)
{
return $notification_data;
}
}

throw new http_exception(Response::HTTP_FORBIDDEN, 'Forbidden');
}

/**
Expand Down

0 comments on commit 55e1cd8

Please sign in to comment.