Would сайтовые точно нет, мессенджеры и аппы
Новый тип уведомлений. Подумать, как аккуратно и неназойливо спрашивать о таких вещах. Возможно, над меню.
`
ОЖИДАНИЕ РЕКЛАМЫ...
0
29
1 год назад
Отредактирован nazarpunk
0
Там нужен composer, который политически неравнодушен.
"require": {
    "php": ">=7.3",
    "ext-curl": "*",
    "ext-json": "*",
    "ext-mbstring": "*",
    "ext-openssl": "*",
    "guzzlehttp/guzzle": "^7.0.1|^6.2",
    "web-token/jwt-signature": "^2.0|^3.0.2",
    "web-token/jwt-key-mgmt": "^2.0|^3.0.2",
    "web-token/jwt-signature-algorithm-ecdsa": "^2.0|^3.0.2",
    "web-token/jwt-util-ecc": "^2.0|^3.0.2",
    "spomky-labs/base64url": "^2.0"
  },

Я кстати костылил такое же вообще без всяких зависимостей.
раскрыть
<?php
declare(strict_types=1);

namespace Send;

use CurlHandle;
use Exception;
use JetBrains\PhpStorm\ArrayShape;
use Util\Response;

class Comet {
	private static CurlHandle $ch;
	private static string     $project_id = 'my-project-id';
	private static ?string    $token      = null;


	/**
	 * @param array  $data
	 * @param array  $counter
	 * @param array  $notification
	 * @param string $route
	 * @return array[]
	 */
	#[ArrayShape(['message' => 'array'])]
	private static function message(
		array  $data,
		array  $counter,
		array  $notification = [],
		string $route = ''
	): array {
		$data += [
			//'click_action'   => 'FLUTTER_NOTIFICATION_CLICK',
			'data_timestamp' => (string)time()
		];

		if (count($counter) > 0) {
			$data['data_counter'] = $counter;
		}

		if (mb_strlen($route) > 0) {
			$data['data_route'] = $route;
		}

		foreach ($data as $k => $v) {
			if (is_string($v)) {
				continue;
			} elseif (is_array($v)) {
				$data[$k] = json_encode($v);
			} else {
				settype($data[$k], 'string');
			}
		}

		/**
		 * @see https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages
		 */
		$message = [
			'data' => $data,
		];

		if (count($counter) > 0) {
			$message += ['apns' => ['payload' => ['aps' => [
				'mutable-content' => 1,
				'badge'           => $counter['summary'],
			]]]];
		}

		if (count($notification) > 0) {
			$message = array_merge_recursive($message, [
				'notification'      => [
					'title' => $notification[0],
					'body'  => $notification[1] ?? '',
				],
				'apns'              => [
					'payload' => [
						'aps' => [
							'sound' => 'default'
						]
					]
				],
				'android'           => [
					'priority'     => 'HIGH',
					'notification' => [
						'sound' => 'default'
					]
				]
			]);
		}

		return $message;
	}

	/**
	 * @param CometType $type
	 * @param int|null  $user_id
	 * @param array     $data
	 * @param array     $notification
	 * @param string    $route
	 * @return array
	 * @throws Exception
	 */
	public static function send(
		CometType $type,
		?int      $user_id,
		array     $data,
		array     $notification = [],
		string    $route = ''
	): array {
		[$list, $counter] = DB::user_token_list($user_id)->execute();

		$tokens = [];
		foreach ($list as $v) {
			$tokens[] = $v['token'];
		}

		if (count($tokens) == 0) {
			Response::bad_request();
		}

		$access_token = self::$token ?? self::token();

		if ($access_token == null) {
			Response::bad_request();
		}

		$message = self::message(
			data:         $data + ['type' => $type->name],
			counter:      count($counter) > 0 ? $counter[0] : [],
			notification: $notification,
			route:        $route
		);

		curl_setopt_array(self::$ch, [
			CURLOPT_URL        => 'https://fcm.googleapis.com/v1/projects/' . self::$project_id . '/messages:send',
			CURLOPT_HTTPHEADER => [
				'Connection: close',
				'Content-Type: application/json',
				"Authorization: Bearer $access_token",
			],
		]);

		$unset   = [];
		$success = [];

		$send = function (int $attempt) use (&$send, &$tokens, &$message, &$unset, &$success) {
			while (count($tokens) > 0) {
				$token = array_shift($tokens);

				$message['token'] = $token;
				curl_setopt(self::$ch, CURLOPT_POSTFIELDS, json_encode(['message' => $message]));

				$status   = curl_getinfo(self::$ch, CURLINFO_HTTP_CODE);
				$response = curl_exec(self::$ch);

				switch (json_decode($response, true)['error']['status'] ?? null) {
					case 'UNAUTHENTICATED':
					case 'NOT_FOUND':
						$unset[] = $token;
						break;
					case 'INTERNAL':
						if ($attempt <= 5) {
							$tokens[] = $token;
							set_time_limit(10);
							usleep($attempt * 1050000);
							$send($attempt + 1);
							break 2;
						}
						break;
					case 'INVALID_ARGUMENT':
						if (mb_strpos($response, 'message.token') > 0) {
							echo "--------- invalid token\n\n";
						} else {
							var_dump($response);
						}
						break;
					default:
						if ($status == 200) {
							$success[] = [$response, $message];
						} else {
							var_dump($response);
						}
				}

			}
		};

		$send(1);

		curl_close(self::$ch);

		if (count($unset) > 0) {
			DB::user_token_unset(implode(',', $unset))->execute();
		}

		return $success;
	}

	private static function token(): ?string {
		$time = time();
		$aud  = 'https://oauth2.googleapis.com/token';

		$JWT = base64_encode(json_encode(['alg' => 'RS256', 'typ' => 'JWT']))
		       . '.' .
		       base64_encode(json_encode([
			                                 'iss'   => 'firebase-adminsdk-9p1bi@' . self::$project_id . '.iam.gserviceaccount.com',
			                                 'scope' => 'https://www.googleapis.com/auth/firebase.messaging',
			                                 'aud'   => $aud,
			                                 'iat'   => $time,
			                                 'exp'   => $time + 3600
		                                 ]));

		$private_key = <<<EOT
-----BEGIN PRIVATE KEY-----
-----END PRIVATE KEY-----
		
EOT;
		$signature   = '';
		openssl_sign($JWT, $signature, $private_key, OPENSSL_ALGO_SHA256);

		curl_setopt_array(self::$ch = curl_init(), [
			CURLOPT_URL            => $aud,
			CURLOPT_PORT           => 443,
			CURLOPT_POST           => true,
			CURLOPT_RETURNTRANSFER => true,
			CURLOPT_HTTP_VERSION   => CURL_HTTP_VERSION_1_0,
			CURLOPT_HTTPHEADER     => ['Connection: close'],
			CURLOPT_POSTFIELDS     => [
				'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
				'assertion'  => $JWT . '.' . base64_encode($signature),
			],
		]);

		$call = function (int $attempt) use (&$call): ?string {
			$response = json_decode(curl_exec(self::$ch), true);
			$status   = curl_getinfo(self::$ch, CURLINFO_HTTP_CODE);

			if ($status != 200 || !array_key_exists('access_token', $response)) {
				if ($attempt > 5) {
					curl_close(self::$ch);
					return null;
				}
				set_time_limit(10);
				usleep($attempt * 1050000);
				return $call($attempt + 1);
			}

			return self::$token = $response['access_token'];
		};

		return $call(1);
	}

}
0
37
1 год назад
0
nazarpunk, композер нет проблем поставить, давно пора
0
35
1 год назад
0
Не надо, плохой UX. Только в аппы.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.