Module: OneSignal::Helpers

Defined in:
lib/onesignal/helpers.rb

Overview

Helpers for common OneSignal API usage patterns.

Defined Under Namespace

Classes: CreateNotificationWithRetryResult

Constant Summary collapse

RETRYABLE_CODES =
[429, 503].freeze
MIN_BASE_DELAY =
1.0
MAX_BASE_DELAY =
60.0

Class Method Summary collapse

Class Method Details

.create_notification_with_retry(api, notification, max_retries: 3, base_delay: 1.0) ⇒ CreateNotificationWithRetryResult

Create a notification with safe, idempotent retries.

Ensures notification.idempotency_key is set (generating a UUIDv4 when absent) so the server can deduplicate, then calls create_notification. Transient failures (HTTP 429, HTTP 503, or connection-level errors) are retried up to max_retries times with the SAME idempotency key, honoring the Retry-After response header when present and falling back to exponential backoff (+base_delay * 2**attempt+ seconds) otherwise. Other errors are raised immediately.

Parameters:

  • api (DefaultApi)

    the API instance to call through

  • notification (Notification)

    an existing idempotency_key is respected, never overwritten

  • max_retries (Integer) (defaults to: 3)

    retries after the initial attempt

  • base_delay (Float) (defaults to: 1.0)

    backoff base in seconds when Retry-After is absent; clamped to [1.0, 60.0]

Returns:

  • (CreateNotificationWithRetryResult)

    response holds the CreateNotificationSuccessResponse; was_replayed is true when the server answered from a previously completed request, as signaled by the Idempotent-Replayed response header



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/onesignal/helpers.rb', line 32

def self.create_notification_with_retry(api, notification, max_retries: 3, base_delay: 1.0)
  if notification.idempotency_key.nil? || notification.idempotency_key.to_s.empty?
    notification.idempotency_key = SecureRandom.uuid
  end

  # Clamp the backoff base so a stray value can neither hammer the API
  # (too small) nor stall the caller for an unbounded stretch (too large).
  base_delay = [[base_delay, MIN_BASE_DELAY].max, MAX_BASE_DELAY].min

  attempt = 0
  begin
    data, _status, headers = api.create_notification_with_http_info(notification)
    CreateNotificationWithRetryResult.new(data, replayed?(headers))
  rescue ApiError => e
    # code nil/0 covers connection timeouts and libcurl-level failures.
    retryable = e.code.nil? || e.code.zero? || RETRYABLE_CODES.include?(e.code)
    raise if !retryable || attempt >= max_retries

    delay = retry_delay(e.response_headers, attempt, base_delay)
    sleep(delay) if delay > 0
    attempt += 1
    retry
  end
end