expo_notifier 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -0
  3. data/CHANGELOG.md +3 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +172 -0
  6. data/Rakefile +17 -0
  7. data/lib/expo_notifier/mapper/base.rb +30 -0
  8. data/lib/expo_notifier/mapper/error.rb +24 -0
  9. data/lib/expo_notifier/mapper/push_message.rb +132 -0
  10. data/lib/expo_notifier/mapper/push_messages.rb +15 -0
  11. data/lib/expo_notifier/mapper/push_receipt.rb +23 -0
  12. data/lib/expo_notifier/mapper/push_receipt_error_details.rb +23 -0
  13. data/lib/expo_notifier/mapper/push_receipt_ids.rb +16 -0
  14. data/lib/expo_notifier/mapper/push_receipts.rb +38 -0
  15. data/lib/expo_notifier/mapper/push_ticket.rb +30 -0
  16. data/lib/expo_notifier/mapper/push_ticket_error_details.rb +19 -0
  17. data/lib/expo_notifier/mapper/push_tickets.rb +26 -0
  18. data/lib/expo_notifier/mapper/rich_content.rb +17 -0
  19. data/lib/expo_notifier/request/base.rb +162 -0
  20. data/lib/expo_notifier/request/get_push_notification_receipts.rb +18 -0
  21. data/lib/expo_notifier/request/send_push_notifications.rb +26 -0
  22. data/lib/expo_notifier/response.rb +93 -0
  23. data/lib/expo_notifier/sorbet_shim.rb +18 -0
  24. data/lib/expo_notifier/version.rb +6 -0
  25. data/lib/expo_notifier.rb +19 -0
  26. data/sorbet/config +6 -0
  27. data/sorbet/rbi/annotations/.gitattributes +1 -0
  28. data/sorbet/rbi/annotations/activesupport.rbi +495 -0
  29. data/sorbet/rbi/annotations/faraday.rbi +17 -0
  30. data/sorbet/rbi/annotations/minitest.rbi +119 -0
  31. data/sorbet/rbi/annotations/rainbow.rbi +269 -0
  32. data/sorbet/rbi/annotations/webmock.rbi +9 -0
  33. data/sorbet/rbi/dsl/.gitattributes +1 -0
  34. data/sorbet/rbi/dsl/active_support/callbacks.rbi +21 -0
  35. data/sorbet/rbi/dsl/expo_notifier/mapper/base.rbi +12 -0
  36. data/sorbet/rbi/dsl/expo_notifier/mapper/error.rbi +42 -0
  37. data/sorbet/rbi/dsl/expo_notifier/mapper/push_message.rbi +263 -0
  38. data/sorbet/rbi/dsl/expo_notifier/mapper/push_messages.rbi +29 -0
  39. data/sorbet/rbi/dsl/expo_notifier/mapper/push_receipt.rbi +45 -0
  40. data/sorbet/rbi/dsl/expo_notifier/mapper/push_receipt_error_details.rbi +38 -0
  41. data/sorbet/rbi/dsl/expo_notifier/mapper/push_receipt_ids.rbi +20 -0
  42. data/sorbet/rbi/dsl/expo_notifier/mapper/push_receipts.rbi +25 -0
  43. data/sorbet/rbi/dsl/expo_notifier/mapper/push_ticket.rbi +59 -0
  44. data/sorbet/rbi/dsl/expo_notifier/mapper/push_ticket_error_details.rbi +32 -0
  45. data/sorbet/rbi/dsl/expo_notifier/mapper/push_tickets.rbi +54 -0
  46. data/sorbet/rbi/dsl/expo_notifier/mapper/rich_content.rbi +26 -0
  47. data/sorbet/rbi/dsl/time.rbi +13 -0
  48. data/sorbet/rbi/gems/.gitattributes +1 -0
  49. data/sorbet/rbi/gems/activesupport@8.1.1.rbi +22456 -0
  50. data/sorbet/rbi/gems/addressable@2.8.8.rbi +2005 -0
  51. data/sorbet/rbi/gems/ast@2.4.3.rbi +586 -0
  52. data/sorbet/rbi/gems/base64@0.3.0.rbi +545 -0
  53. data/sorbet/rbi/gems/benchmark@0.5.0.rbi +637 -0
  54. data/sorbet/rbi/gems/bigdecimal@4.0.1.rbi +409 -0
  55. data/sorbet/rbi/gems/booleans@0.1.3.rbi +31 -0
  56. data/sorbet/rbi/gems/concurrent-ruby@1.3.6.rbi +11729 -0
  57. data/sorbet/rbi/gems/connection_pool@3.0.2.rbi +9 -0
  58. data/sorbet/rbi/gems/crack@1.0.1.rbi +145 -0
  59. data/sorbet/rbi/gems/date@3.5.1.rbi +403 -0
  60. data/sorbet/rbi/gems/drb@2.2.3.rbi +1661 -0
  61. data/sorbet/rbi/gems/erb@6.0.1.rbi +815 -0
  62. data/sorbet/rbi/gems/erubi@1.13.1.rbi +157 -0
  63. data/sorbet/rbi/gems/faraday-net_http@3.4.2.rbi +72 -0
  64. data/sorbet/rbi/gems/faraday@2.14.0.rbi +3301 -0
  65. data/sorbet/rbi/gems/hashdiff@1.2.1.rbi +355 -0
  66. data/sorbet/rbi/gems/i18n@1.14.8.rbi +2383 -0
  67. data/sorbet/rbi/gems/io-console@0.8.2.rbi +9 -0
  68. data/sorbet/rbi/gems/json@2.18.0.rbi +2278 -0
  69. data/sorbet/rbi/gems/lint_roller@1.1.0.rbi +323 -0
  70. data/sorbet/rbi/gems/logger@1.7.0.rbi +963 -0
  71. data/sorbet/rbi/gems/minitest@6.0.1.rbi +1524 -0
  72. data/sorbet/rbi/gems/net-http@0.9.1.rbi +4304 -0
  73. data/sorbet/rbi/gems/netrc@0.11.0.rbi +177 -0
  74. data/sorbet/rbi/gems/parallel@1.27.0.rbi +291 -0
  75. data/sorbet/rbi/gems/parser@3.3.10.0.rbi +5537 -0
  76. data/sorbet/rbi/gems/pp@0.6.3.rbi +390 -0
  77. data/sorbet/rbi/gems/prettyprint@0.2.0.rbi +477 -0
  78. data/sorbet/rbi/gems/prism@1.6.0.rbi +42126 -0
  79. data/sorbet/rbi/gems/psych@5.3.1.rbi +2556 -0
  80. data/sorbet/rbi/gems/public_suffix@7.0.2.rbi +957 -0
  81. data/sorbet/rbi/gems/racc@1.8.1.rbi +168 -0
  82. data/sorbet/rbi/gems/rainbow@3.1.1.rbi +404 -0
  83. data/sorbet/rbi/gems/rake@13.3.1.rbi +3038 -0
  84. data/sorbet/rbi/gems/rbi@0.3.8.rbi +5238 -0
  85. data/sorbet/rbi/gems/rbs@4.0.0.dev.4.rbi +7805 -0
  86. data/sorbet/rbi/gems/regexp_parser@2.11.3.rbi +3849 -0
  87. data/sorbet/rbi/gems/reline@0.6.3.rbi +2995 -0
  88. data/sorbet/rbi/gems/require-hooks@0.2.2.rbi +110 -0
  89. data/sorbet/rbi/gems/rexml@3.4.4.rbi +5258 -0
  90. data/sorbet/rbi/gems/rubocop-espago@1.2.0.rbi +9 -0
  91. data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1318 -0
  92. data/sorbet/rbi/gems/securerandom@0.4.1.rbi +75 -0
  93. data/sorbet/rbi/gems/shale-builder@0.8.5.rbi +267 -0
  94. data/sorbet/rbi/gems/shale@1.2.2.rbi +2323 -0
  95. data/sorbet/rbi/gems/shoulda-context@2.0.0.rbi +563 -0
  96. data/sorbet/rbi/gems/spoom@1.7.11.rbi +5878 -0
  97. data/sorbet/rbi/gems/stringio@3.2.0.rbi +9 -0
  98. data/sorbet/rbi/gems/tapioca@0.17.4.rbi +3507 -0
  99. data/sorbet/rbi/gems/thor@1.4.0.rbi +4399 -0
  100. data/sorbet/rbi/gems/tsort@0.2.0.rbi +393 -0
  101. data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +5919 -0
  102. data/sorbet/rbi/gems/unicode-display_width@3.2.0.rbi +132 -0
  103. data/sorbet/rbi/gems/unicode-emoji@4.2.0.rbi +254 -0
  104. data/sorbet/rbi/gems/uri@1.1.1.rbi +2407 -0
  105. data/sorbet/rbi/gems/vcr@6.4.0.rbi +3055 -0
  106. data/sorbet/rbi/gems/webmock@3.26.1.rbi +1816 -0
  107. data/sorbet/rbi/shims/gems/set.rbi +2 -0
  108. data/sorbet/rbi/shims/gems/shale.rbi +27 -0
  109. data/sorbet/rbi/shims/gems/shoulda-context.rbi +20 -0
  110. data/sorbet/tapioca/config.yml +21 -0
  111. data/sorbet/tapioca/extensions/load_gem.rb +1 -0
  112. data/sorbet/tapioca/require.rb +15 -0
  113. metadata +228 -0
@@ -0,0 +1,162 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require 'faraday'
5
+ require 'uri'
6
+ require 'stringio'
7
+ require 'zlib'
8
+
9
+ require_relative '../mapper/base'
10
+ require_relative '../response'
11
+
12
+ module ExpoNotifier
13
+ module Request
14
+ # An abstract class for building and sending requests.
15
+ # @abstract
16
+ class Base
17
+ extend T::Generic
18
+
19
+ RequestBody = type_member(:out) { { upper: Mapper::Base } }
20
+ ResponseBody = type_member(:out) { { upper: Mapper::Base } }
21
+
22
+ Generic = T.type_alias { Base[Mapper::Base, Mapper::Base] }
23
+
24
+ ########## ---------- Default config ---------- ##########
25
+ BASE_URL = 'https://exp.host'
26
+ WRITE_TIMEOUT = 5
27
+ OPEN_TIMEOUT = 5
28
+ READ_TIMEOUT = 30
29
+ GZIP_MIN_SIZE = 1024
30
+ BASE_HEADERS = {
31
+ 'Content-Type' => 'application/json',
32
+ 'Accept' => 'application/json',
33
+ 'Accept-Encoding' => 'gzip, deflate',
34
+ }.freeze #: Hash[String, String]
35
+ ########## ------------------------------------ ##########
36
+
37
+ class << self
38
+ #: String
39
+ attr_accessor :path
40
+
41
+ #: singleton(Mapper::Base)
42
+ attr_accessor :response_class
43
+ end
44
+
45
+ #: String
46
+ attr_reader :url
47
+
48
+ #: RequestBody
49
+ attr_reader :body
50
+
51
+ #: Response[ResponseBody]?
52
+ attr_reader :response
53
+
54
+ #: (
55
+ #| RequestBody,
56
+ #| ?access_token: String?,
57
+ #| ?faraday_adapter: Symbol,
58
+ #| ?base_url: String,
59
+ #| ?write_timeout: Integer,
60
+ #| ?open_timeout: Integer,
61
+ #| ?read_timeout: Integer,
62
+ #| ?gzip: bool,
63
+ #| ?gzip_min_size: Integer,
64
+ #| ?additional_headers: Hash[String, String]?,
65
+ #| ) -> void
66
+ def initialize(
67
+ body,
68
+ access_token: nil,
69
+ faraday_adapter: Faraday.default_adapter,
70
+ base_url: BASE_URL,
71
+ write_timeout: WRITE_TIMEOUT,
72
+ open_timeout: OPEN_TIMEOUT,
73
+ read_timeout: READ_TIMEOUT,
74
+ gzip: true,
75
+ gzip_min_size: GZIP_MIN_SIZE,
76
+ additional_headers: nil
77
+ )
78
+ @body = body
79
+ @access_token = access_token
80
+ @faraday_adapter = faraday_adapter
81
+ @url = URI.join(base_url, self.class.path).to_s #: String
82
+ @write_timeout = write_timeout
83
+ @open_timeout = open_timeout
84
+ @read_timeout = read_timeout
85
+ @gzip = gzip
86
+ @gzip_min_size = gzip_min_size
87
+ @additional_headers = additional_headers
88
+ end
89
+
90
+ #: -> Response[ResponseBody]
91
+ def execute
92
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
93
+
94
+ faraday_response = begin
95
+ faraday_connection.post do |req|
96
+ req.body = body_to_send
97
+ end
98
+ rescue Faraday::Error => e
99
+ e
100
+ end
101
+
102
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
103
+ @response = Response[ResponseBody].new(self.class.response_class, duration.to_f, faraday_response)
104
+ end
105
+
106
+ #: -> T::Hash[String, String]
107
+ def headers
108
+ h = BASE_HEADERS
109
+ h = h.merge({ 'Content-Encoding' => 'gzip' }) if compress_body?
110
+ h = h.merge(@additional_headers) if @additional_headers
111
+ h
112
+ end
113
+
114
+ # String in the JSON format
115
+ #: -> String
116
+ def raw_body
117
+ @raw_body ||= body.to_json
118
+ end
119
+
120
+ # String in the JSON format, compressed with gzip if needed
121
+ #: -> String
122
+ def body_to_send
123
+ return raw_body unless compress_body?
124
+
125
+ io = StringIO.new
126
+ Zlib::GzipWriter.wrap(io) { |gz| gz.write(raw_body) }
127
+ io.string
128
+ end
129
+
130
+ #: -> bool
131
+ def compress_body?
132
+ return false unless @gzip
133
+
134
+ raw_body.bytesize >= @gzip_min_size
135
+ end
136
+
137
+ alias body_compressed? compress_body?
138
+
139
+ #: -> String?
140
+ def name = self.class.name&.split('::')&.last
141
+
142
+ private
143
+
144
+ #: -> Faraday::Connection
145
+ def faraday_connection
146
+ Faraday.new(
147
+ @url,
148
+ headers: headers,
149
+ request: {
150
+ write_timeout: @write_timeout,
151
+ open_timeout: @open_timeout,
152
+ read_timeout: @read_timeout,
153
+ },
154
+ ) do |conn|
155
+ conn.request :authorization, 'Bearer', @access_token if @access_token
156
+ conn.adapter @faraday_adapter
157
+ end
158
+ end
159
+
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,18 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'base'
5
+ require_relative '../mapper/push_receipt_ids'
6
+ require_relative '../mapper/push_receipts'
7
+
8
+ module ExpoNotifier
9
+ module Request
10
+ class GetPushNotificationReceipts < Base
11
+ RequestBody = type_member(:out) { { fixed: Mapper::PushReceiptIds } }
12
+ ResponseBody = type_member(:out) { { fixed: Mapper::PushReceipts } }
13
+
14
+ self.path = '/--/api/v2/push/getReceipts'
15
+ self.response_class = Mapper::PushReceipts
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'base'
5
+ require_relative '../mapper/push_messages'
6
+ require_relative '../mapper/push_tickets'
7
+
8
+ module ExpoNotifier
9
+ module Request
10
+ class SendPushNotifications < Base
11
+ RequestBody = type_member(:out) { { fixed: Mapper::PushMessages } }
12
+ ResponseBody = type_member(:out) { { fixed: Mapper::PushTickets } }
13
+
14
+ self.path = '/--/api/v2/push/send'
15
+ self.response_class = Mapper::PushTickets
16
+
17
+ # String in the JSON format
18
+ # We override the raw body to send an array as a main object.
19
+ #: -> String
20
+ def raw_body
21
+ @raw_body ||= ExpoNotifier::Mapper::PushMessage.to_json(body.push_message) #: String?
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,93 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module ExpoNotifier
5
+ class Response
6
+ extend T::Generic
7
+
8
+ Body = type_member(:out) { { upper: Mapper::Base } }
9
+
10
+ #: Integer?
11
+ attr_reader :status_code
12
+
13
+ #: Hash[(String | Symbol), String]?
14
+ attr_reader :headers
15
+
16
+ #: String?
17
+ attr_reader :raw_body
18
+
19
+ #: Float
20
+ attr_reader :duration
21
+
22
+ #: Faraday::Error?
23
+ attr_reader :communication_error
24
+
25
+ #: (singleton(Mapper::Base), Float, (Faraday::Response | Faraday::Error)) -> void
26
+ def initialize(body_class, duration, faraday_response)
27
+ if faraday_response.is_a?(Faraday::Response)
28
+ initialize_with_faraday_response(faraday_response)
29
+ elsif faraday_response.is_a?(Faraday::Error)
30
+ @communication_error = faraday_response #: Faraday::Error?
31
+ end
32
+ @body_class = body_class
33
+ @duration = duration
34
+ end
35
+
36
+ # Response body mapped to Ruby objects.
37
+ #: -> Body?
38
+ def body
39
+ @body ||= parse_response_body #: Body?
40
+ end
41
+
42
+ #: -> String?
43
+ def communication_error_message
44
+ return unless communication_error
45
+
46
+ "[#{communication_error.class}] => #{T.must(communication_error).message}"
47
+ end
48
+
49
+ #: -> bool
50
+ def success?
51
+ @status_code == 200
52
+ end
53
+
54
+ #: -> bool
55
+ def too_many_requests_error?
56
+ @status_code == 429
57
+ end
58
+
59
+ #: -> bool
60
+ def malformed_request_error?
61
+ @status_code.to_s.start_with?('4') && !too_many_requests_error?
62
+ end
63
+
64
+ #: -> bool
65
+ def server_error?
66
+ @status_code.to_s.start_with?('5')
67
+ end
68
+
69
+ #: -> bool
70
+ def communication_error?
71
+ !!communication_error
72
+ end
73
+
74
+ private
75
+
76
+ #: (Faraday::Response) -> void
77
+ def initialize_with_faraday_response(faraday_response)
78
+ @status_code = faraday_response.status #: Integer?
79
+ @headers = faraday_response.headers #: Hash[(String | Symbol), String]?
80
+ @raw_body = faraday_response.body #: String?
81
+ end
82
+
83
+ #: -> Body?
84
+ def parse_response_body
85
+ return unless @raw_body
86
+
87
+ @body_class.from_json(@raw_body)
88
+ rescue Shale::ParseError, JSON::ParserError
89
+ nil
90
+ end
91
+
92
+ end
93
+ end
@@ -0,0 +1,18 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'sorbet-runtime'
5
+
6
+ begin
7
+ T::Configuration.default_checked_level = :never
8
+ rescue StandardError
9
+ puts 'WARN: Sorbet default checked level could not be set!'
10
+ end
11
+ error_handler = ->(error, *_) do
12
+ end
13
+ # Suppresses errors caused by incorrect parameter and return types
14
+ T::Configuration.call_validation_error_handler = error_handler
15
+ # Suppresses errors caused by T.cast, T.let, T.must, etc.
16
+ T::Configuration.inline_type_error_handler = error_handler
17
+ # Suppresses errors caused by incorrect parameter ordering
18
+ T::Configuration.sig_validation_error_handler = error_handler
@@ -0,0 +1,6 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module ExpoNotifier
5
+ VERSION = '0.1.0'
6
+ end
@@ -0,0 +1,19 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'expo_notifier/sorbet_shim'
5
+ require_relative 'expo_notifier/version'
6
+ require_relative 'expo_notifier/request/send_push_notifications'
7
+ require_relative 'expo_notifier/request/get_push_notification_receipts'
8
+
9
+ module ExpoNotifier
10
+ class << self
11
+ #: (String) -> bool
12
+ def expo_push_token?(token)
13
+ return false unless token.is_a?(String)
14
+
15
+ ((token.start_with?('ExponentPushToken[') || token.start_with?('ExpoPushToken[')) && token.end_with?(']')) ||
16
+ token.match?(/\A[a-z\d]{8}-[a-z\d]{4}-[a-z\d]{4}-[a-z\d]{4}-[a-z\d]{12}\z/i)
17
+ end
18
+ end
19
+ end
data/sorbet/config ADDED
@@ -0,0 +1,6 @@
1
+ --dir
2
+ .
3
+ --enable-experimental-requires-ancestor
4
+ --enable-experimental-rbs-comments
5
+ --ignore=tmp/
6
+ --ignore=vendor/
@@ -0,0 +1 @@
1
+ **/*.rbi linguist-vendored=true