روزنوشت

می نویسم؛ تا به یاد آورم روزی، بودم

روزنوشت

می نویسم؛ تا به یاد آورم روزی، بودم

روزنوشت

من آرزویی ندارم، من از چیزی نمی‌ترسم، من آزادم...

«کازانتزاکیس»

دنبال کنندگان ۳ نفر
این وبلاگ را دنبال کنید
آخرین نظرات

رفع مشکل تحریم کاربران ایرانی توسط OneSignal

پنجشنبه, ۸ آذر ۱۳۹۷، ۰۹:۵۴ ب.ظ

پنل مدیریت وان سیگنال از مدتها پیش تنها با فیلتر شکن باز می‌شد اما کاربرهایی که IP ایران رو داشتند بدون مشکل در OneSignal رجیستر می‌شدن و در نتیجه مشکلی برای ارسال Push Notification وجود نداشت.

چندماه پیش بود که خبر اومد که APIهای وان سیگنال به روی کاربران ایرانی بسته شده و در نتیجه کاربرهامون دیگه تو سرویس وان سیگنال رجیستر نمیشن و عملاً این سرویس برای توسعه‌دهنده‌گان ایرانی که هدفشون بازار ایران هست کاربردی نداره.

 

همون زمان‌ها خاطرم هست که مطلبی رو خوندم که اشاره شده بود چون مشکل فقط در مرحله‌ی اول و دریافت Player ID هست، اگر این API رو از طریق سرور خودمون ارسال کنیم مشکل حل میشه.

 

چند هفته پیش لازم شد تا در پروژه‌ی جدید از سرویس Push Notification استفاده کنم. تو این پروژه اپ Android و iOS داریم و خب باید از سرویسی استفاده کنیم که هردو رو ساپورت کنه.

 

با توجه به ماجرای OneSignal اول از همه رفتم سمت سرویس‌های ایرانی و البته سرویس FCM خود گوگل. در تست من، مشکلی در سرویس FCM وجود نداشت و Device token به درستی دریافت می‌شد، البته وقتی تلاش کردم به همه‌ی کاربران از طریق پنل FCM پوش ارسال کنم، ناموفق بود اما اگر تنها یک Device رو هدف قرار می‌دادم، پوش به درستی دریافت می‌شد. برای من محرز شد که FCM مشکلی در رجیستر کردن کاربران (حداقل در اندروید) نداره و نهایتاً باید پنلی براش بنویسم یا اینکه از گزینه‌های اوپن سورس موجود (پنل هایی که برای ارسال پوش از FCM استفاده می‌کنن) که تعدادشون کم هم نبود استفاده کنم. تنها مشکل این بود که دولوپر iOS نتیجه‌ی دیگری گرفته بود که در iOS بدون فیلتر شکن کاربران در سرویس FCM رجیستر نمیشن. که البته برای من عجیب بود این اختلاف در دو پلتفرم (که البته راهی برای رد یا تاییدش درحال حاضر ندارم)

 

پس از اون سرویس‌های ایرانی رو بررسی کردیم، پوشه اولین گزینه بود اما ظاهراً SDK مختص iOS نداره هنوز و به همین دلیل حذف شد. سایر سرویس ها هم یا قیمت بالایی داشتن و یا پیچیدگی زیادی در پیاده سازی داشتن (که خب باعث شد اطمینان کمتری به کیفیت سرویس در من ایجاد بشه).

 

نهایتن تصمیم گرفتم مشکل OneSignal رو حل کنم. چون قبلن هم در چندین پروژه ازش استفاده کرده بودم و کلی متد کمکی برای شرایط متفاوت براش نوشته بودم که اینجا هم با دردسری کمتری می‌تونستم ازشون بهره ببرم، اما چون مطلبی که چندماه پیش خونده بودم رو پیدا نکردم، خودم شروع به پیاده‌سازیش کردم و دلیل اصلی نوشتن این پست هم همین هست، شاید در شرایطی به کمک دیگری بیاد.

 

با بررسی SDK وان سیگنال برای اندروید و iOS متوجه شدم که رفع مشکل خیلی ساده هست و و کافیه چیزی شبیه پروکسی سمت سرور ایجاد بشه تا درخواست ها از اون طریق ارسال بشه و در SDK هم تغییرات اندکی ایجاد بشه.

 

Android:

 

iOS:

 

اگه لینک‌های بالا رو بررسی کنید می‌بینید که خیلی راحت میشه درخواست ها رو به Url دیگری ارسال کرد. کافیه که سروری در خارج کشور داشته باشیم و یک API ساده که همه‌ی درخواست ها رو دریافت کنه، به سمت سرور OneSignal ارسال کنه و پاسخ رو برگردونه.

 

کد زیر به زیان PHP و فریم‌وورک Slim هست. البته مشخصه که مفهوم کلی بسیار ساده هست و با هر زبان دیگه و یا هر فریم‌وورکی قابل پیاده سازی هست:

 

<?php
require_once __DIR__ . '/vendor/autoload.php';

use GuzzleHttp\Client;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;

$app = new Slim\App();

//region Helper Functions
function startsWith($haystack, $needle) {
    // search backwards starting from haystack length characters from the end
    if (is_array($needle)) {
        foreach ($needle as $str) {
            if (strrpos($haystack, $str, -strlen($haystack)) !== FALSE) {

                return TRUE;
            }
        }

        return FALSE;

    }

    return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== FALSE;
}

/**
 * @param Response $response
 * @param string|array $body
 * @param int $status
 *
 * @return \Psr\Http\Message\ResponseInterface
 */
function postResponse(Response $response, $body, $status = 200) {
    $response->getBody()->write(is_array($body) ? json_encode($body, JSON_UNESCAPED_UNICODE) : $body);

    return $response->withStatus($status > 0 ? $status : 200);
}

/**
 * @param Request $request
 *
 * @return array
 */
function getInputData(Request $request) {
    $data = $request->getParsedBody();
    if (empty($data)) {
        $data = $request->getQueryParams();
        if (empty($data)) {
            $data = $_REQUEST;
        }
    }

    if (startsWith($data, "{") || startsWith($data, "[")) {
        return json_decode($data, TRUE);
    }
    parse_str($data, $array);

    return $array;

}
//endregion

$app->any('/one_signal[/{params:.*}]', function (Request $request, Response $response, $args) {
    $method      = $request->getMethod();
    $sentHeaders = $request->getHeaders();
    $uri         = $args['params'];
    $body        = getInputData($request);
    $headers     = [];
    foreach ($sentHeaders as $header => $value) {
        $normalizedHeader = str_replace('HTTP_', '', $header);
        if (startsWith($normalizedHeader, 'HOST')) {
            continue;
        }
        $headers[$normalizedHeader] = $value[0];
    }
    foreach ($body as $key => $value) {
        $correctType = $value;
        if (in_array($key, ['device_type', 'timezone', 'session_count', 'created_at', 'playtime', 'badge_count',
                            'last_active', 'notification_types', 'test_type', 'net_type', 'game_version'])) {
            $correctType = (int)$value;
        } else {
            if (in_array($key, ['lat', 'long'])) {
                $correctType = (double)$value;
            } else {
                if ($key == 'rooted') {
                    $correctType = filter_var($value, FILTER_VALIDATE_BOOLEAN);
                }
            }
        }


        $body[$key] = $correctType;
    }
    
    $client  = new Client();
    $options = [
        'headers' => $headers
    ];
    if (strtolower($method) != 'get') {
        $options['json'] = $body;
    }
    $result = $client->request($method, "https://onesignal.com/api/v1/{$uri}", $options);
    
    $responseHeaders = $result->getHeaders();
    foreach ($responseHeaders as $header => $value) {
        $response->withAddedHeader($header, is_array($value) ? $value[0] : $value);
    }
    return postResponse($response, (string)$result->getBody());
});

$app->run();

 

اینجا از پکیج Slim Framework برای ساخت API استفاده شده که با Composer به پروژه اضافه‌ش کنید:

composer require slim/slim "^3.0"

 

برای ارسال درخواست‌های HTTP هم از کتابخانه‌ی Guzzle استفاده شده:

composer require guzzlehttp/guzzle:~6.0

 

البته باید توجه داشته باشید که در این کدها، هیچ عامل محدود‌کننده گزاشته نشده، یعنی اگه فرد دیگری به این API درخواست رو ارسال کنه می‌تونه تو اپ خودش ازش استفاده کنه، که خب می‌تونه سربار زیادی برای سرورتون ایجاد کنه. منطقی هست که مقدار app_id در دیتای ورودی رو چک کنید و اگر متفاوت از app_id مختص خودتون بود، پیغام خطا بدید و درخواست رو رد کنید.

if ($body['app_id'] != "YOUR_APP_ID") {
    return postResponse($response, ['error_code' => -100, 'error_message' => 'Request rejected'], 400);
}

حالا کافیه که SDK رو به پروژه‌ی اندروید یا iOS اضافه کنید، و مقادیری که در ابتدای پست بهش اشاره شد رو به آدرس سرور خودتون تغییر بدید.

مثلاً به جای https://onesignal.com/api/v1/ از آدرس https://your-domain.com/one_signal/ استفاده کنید

 

برای اندروید سورس SDK رو دانلود کنید و بعنوان ماژول به پروژه اضافه کنید و اون خط رو تغییر بدید. برای iOS هم به همین شکل.

 

البته ناگفته نماند که در انتهای تحقیقات برای یافتن جایگزین OneSignal به سایت https://yeksignal.com رسیدم، اما چون راه‌حل ساده‌تر از چیزی بود که فکر می‌کردم و سرور خارج از ایران هم داشتم نیازی به استفاده از این سرویس ندیدم. اگر سرور خارج از ایران ندارید و مجبورید سرور جداگانه تهیه کنید، شاید برای شما گزینه‌ی معقولی باشه.

 

 

  • Nevercom

Android

OneSignal

Push Notification

SDK

iOS

نظرات (۰)

هیچ نظری هنوز ثبت نشده است
ارسال نظر آزاد است، اما اگر قبلا در بیان ثبت نام کرده اید می توانید ابتدا وارد شوید.
شما میتوانید از این تگهای html استفاده کنید:
<b> یا <strong>، <em> یا <i>، <u>، <strike> یا <s>، <sup>، <sub>، <blockquote>، <code>، <pre>، <hr>، <br>، <p>، <a href="" title="">، <span style="">، <div align="">
تجدید کد امنیتی