nextmail 0.0.1 → 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.
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nextmail
4
+ class LiveEventStream
5
+ STREAM_EVENTS = Protocol::LIVE_STREAM_EVENTS
6
+ MS_PER_SECOND = 1000.0
7
+
8
+ def initialize(transport, query:, last_event_id:, api_key:, timing:)
9
+ @transport = transport
10
+ @query = query
11
+ @api_key = api_key
12
+ @timing = timing
13
+ @resume_id = last_event_id.to_s
14
+ @reconnect_gap = seconds([timing[:DEFAULT_RETRY_MS], timing[:MAX_BACKOFF_MS]].min)
15
+ @failures = 0
16
+ @delivering = false
17
+ end
18
+
19
+ def run(yielder)
20
+ delay = 0
21
+
22
+ loop do
23
+ sleep(delay) if delay.positive?
24
+ delay = attempt(yielder)
25
+ end
26
+ end
27
+
28
+ def inspect
29
+ "#<#{self.class.name}>"
30
+ end
31
+
32
+ private
33
+
34
+ def attempt(yielder)
35
+ started = clock
36
+ return failure_delay if connect(yielder)
37
+
38
+ [0, started + @reconnect_gap - clock].max
39
+ rescue NetworkError, ApiError => error
40
+ raise if @delivering || !retryable?(error)
41
+
42
+ failure_delay(error.is_a?(ApiError) ? error.retry_after_seconds : nil)
43
+ end
44
+
45
+ def connect(yielder)
46
+ @transport.stream(ApiPaths::LIVE_EVENTS, query: @query, headers: resume_headers,
47
+ accept: Protocol::CONTENT_TYPES[:EVENT_STREAM], api_key: @api_key,
48
+ idle_timeout: seconds(@timing[:IDLE_TIMEOUT_MS])) do |response|
49
+ raise not_an_event_stream(response) unless event_stream?(response)
50
+
51
+ @failures = 0
52
+ read(response, yielder)
53
+ end
54
+ end
55
+
56
+ def read(response, yielder)
57
+ parser = EventStreamParser.new
58
+ response.each_chunk do |chunk|
59
+ break if dispatch_until_end(parser.push(chunk), yielder)
60
+ end
61
+ false
62
+ rescue NetworkError
63
+ raise if @delivering
64
+
65
+ true
66
+ end
67
+
68
+ def dispatch_until_end(records, yielder)
69
+ records.each do |record|
70
+ track(record)
71
+ next if record.data.nil?
72
+ return true if record.event == STREAM_EVENTS[:END]
73
+
74
+ event = event_of(record)
75
+ deliver(yielder, event) unless event.nil?
76
+ end
77
+ false
78
+ end
79
+
80
+ def track(record)
81
+ @reconnect_gap = seconds([record.retry_ms, @timing[:MAX_BACKOFF_MS]].min) unless record.retry_ms.nil?
82
+ @resume_id = record.id unless record.id.nil?
83
+ end
84
+
85
+ def deliver(yielder, event)
86
+ @delivering = true
87
+ yielder << event
88
+ @delivering = false
89
+ end
90
+
91
+ def event_of(record)
92
+ payload = payload_of(record.data)
93
+ return reset_of(payload) if record.event == STREAM_EVENTS[:RESET]
94
+ return nil if payload.nil? || !LIVE_EVENT_TYPES.value?(record.event)
95
+
96
+ payload
97
+ end
98
+
99
+ def payload_of(data)
100
+ parsed = Decode.json(data)
101
+ parsed.is_a?(Hash) ? parsed : nil
102
+ rescue JSON::ParserError
103
+ nil
104
+ end
105
+
106
+ def reset_of(payload)
107
+ reason = payload && payload[:reason]
108
+
109
+ {
110
+ id: @resume_id,
111
+ type: STREAM_EVENTS[:RESET],
112
+ occurredAt: Wire.instant(Time.now),
113
+ data: {reason: reason.is_a?(String) ? reason : LIVE_RESET_REASONS[:UNKNOWN]}
114
+ }
115
+ end
116
+
117
+ def resume_headers
118
+ @resume_id.empty? ? nil : {Protocol::HEADER_KEYS[:LAST_EVENT_ID] => @resume_id}
119
+ end
120
+
121
+ def event_stream?(response)
122
+ media_type(response) == Protocol::CONTENT_TYPES[:EVENT_STREAM]
123
+ end
124
+
125
+ def media_type(response)
126
+ value = response.header(Protocol::HEADER_KEYS[:CONTENT_TYPE]).to_s
127
+ value.split(Protocol::MEDIA_TYPE_PARAMETER_SEPARATOR).first.to_s.strip.downcase
128
+ end
129
+
130
+ def not_an_event_stream(response)
131
+ media = media_type(response)
132
+
133
+ ApiError.build(
134
+ message: format(Defaults::MESSAGES[:NOT_AN_EVENT_STREAM], status: response.status,
135
+ media_type: media.empty? ? Defaults::MESSAGES[:NO_CONTENT_TYPE] : media),
136
+ status: response.status,
137
+ type: ERROR_TYPES[:API_ERROR],
138
+ code: Protocol::ERROR_CODES[:UNRECOGNISED_RESPONSE],
139
+ request_id: response.header(Protocol::HEADER_KEYS[:REQUEST_ID])
140
+ )
141
+ end
142
+
143
+ def retryable?(error)
144
+ error.is_a?(NetworkError) || error.retryable? || error.server_error?
145
+ end
146
+
147
+ def failure_delay(retry_after = nil)
148
+ wait = backoff(@failures)
149
+ @failures += 1
150
+ return wait if retry_after.nil?
151
+
152
+ asked = [retry_after, seconds(@timing[:MAX_RETRY_AFTER_MS])].min
153
+ [wait, asked].max
154
+ end
155
+
156
+ def backoff(attempt)
157
+ ceiling = [@timing[:MAX_BACKOFF_MS], @timing[:BASE_BACKOFF_MS] * (2.0**attempt)].min
158
+ seconds((ceiling * (0.5 + (rand * 0.5))).round)
159
+ end
160
+
161
+ def seconds(milliseconds)
162
+ milliseconds / MS_PER_SECOND
163
+ end
164
+
165
+ def clock
166
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
167
+ end
168
+ end
169
+ end
@@ -12,7 +12,8 @@ module Nextmail
12
12
  JSON: "application/json",
13
13
  CALENDAR: "text/calendar",
14
14
  VCARD: "text/vcard",
15
- OCTET_STREAM: "application/octet-stream"
15
+ OCTET_STREAM: "application/octet-stream",
16
+ EVENT_STREAM: "text/event-stream"
16
17
  }.freeze
17
18
 
18
19
  CURSOR_STYLES = {
@@ -41,6 +42,18 @@ module Nextmail
41
42
  429 => "rate_limit_error"
42
43
  }.freeze
43
44
 
45
+ EVENT_STREAM_FIELDS = {EVENT: "event", DATA: "data", ID: "id", RETRY: "retry"}.freeze
46
+
47
+ EVENT_STREAM_SYNTAX = {
48
+ DEFAULT_EVENT: "message",
49
+ COLON: ":",
50
+ SPACE: " ",
51
+ NULL: "\u0000",
52
+ LINE_FEED: "\n",
53
+ CARRIAGE_RETURN: "\r",
54
+ MAX_BLOCK_CHARS: 1048576
55
+ }.freeze
56
+
44
57
  HEADER_KEYS = {
45
58
  AUTHORIZATION: "Authorization",
46
59
  ACCEPT: "Accept",
@@ -48,7 +61,8 @@ module Nextmail
48
61
  USER_AGENT: "User-Agent",
49
62
  IDEMPOTENCY_KEY: "Idempotency-Key",
50
63
  RETRY_AFTER: "retry-after",
51
- REQUEST_ID: "x-request-id"
64
+ REQUEST_ID: "x-request-id",
65
+ LAST_EVENT_ID: "Last-Event-ID"
52
66
  }.freeze
53
67
 
54
68
  HTTP_METHODS = {GET: "GET", POST: "POST", PUT: "PUT", PATCH: "PATCH", DELETE: "DELETE"}.freeze
@@ -84,6 +98,16 @@ module Nextmail
84
98
 
85
99
  LIST_SEPARATOR = ","
86
100
 
101
+ LIVE_STREAM_EVENTS = {READY: "ready", RESET: "reset", END: "end"}.freeze
102
+
103
+ LIVE_STREAM_TIMING = {
104
+ BASE_BACKOFF_MS: 1000,
105
+ MAX_BACKOFF_MS: 30000,
106
+ DEFAULT_RETRY_MS: 3000,
107
+ MAX_RETRY_AFTER_MS: 300000,
108
+ IDLE_TIMEOUT_MS: 60000
109
+ }.freeze
110
+
87
111
  LOCAL_HOSTS = ["localhost", "::1", "[::1]", "127.0.0.1", "0.0.0.0", "::", "[::]"].freeze
88
112
 
89
113
  LOOPBACK_HOSTNAMES = ["localhost", "::1", "[::1]"].freeze
@@ -92,6 +116,8 @@ module Nextmail
92
116
 
93
117
  LOOPBACK_IPV6_ADDRESS = "[::1]"
94
118
 
119
+ MEDIA_TYPE_PARAMETER_SEPARATOR = ";"
120
+
95
121
  QUERY_KEYS = {
96
122
  LIMIT: "limit",
97
123
  CURSOR: "cursor",
@@ -170,6 +196,7 @@ module Nextmail
170
196
  CONNECTED_APPS: "connectedApps",
171
197
  TASK_ID: "taskId",
172
198
  RUN_ID: "runId",
199
+ CHAT_ID: "chatId",
173
200
  VIEW: "view",
174
201
  PAGE_ID: "pageId",
175
202
  DURATION_MINUTES: "durationMinutes",
@@ -194,7 +221,19 @@ module Nextmail
194
221
  COUNT: "count",
195
222
  HIDE_LABELLED: "hideLabelled",
196
223
  INACTIVE_DAYS: "inactiveDays",
197
- MESSAGE_ID: "messageId"
224
+ MESSAGE_ID: "messageId",
225
+ DOMAIN_ID: "domainId",
226
+ TYPES: "types",
227
+ SUBJECT: "subject",
228
+ TIMEOUT: "timeout",
229
+ CALENDAR_IDS: "calendarIds",
230
+ CALENDAR_ID: "calendarId",
231
+ ADDRESS_BOOK: "addressBook",
232
+ MEMBER: "member",
233
+ STATE: "state",
234
+ DUE_BEFORE: "dueBefore",
235
+ DUE_AFTER: "dueAfter",
236
+ NOTIFY_GUESTS: "notifyGuests"
198
237
  }.freeze
199
238
 
200
239
  RETRY = {
@@ -208,6 +247,8 @@ module Nextmail
208
247
 
209
248
  SEALED_ENCRYPTION_FORMATS = ["pgp-mime", "pgp-inline", "smime-encrypted"].freeze
210
249
 
250
+ THREAD_WAIT = {DEFAULT_TIMEOUT_SECONDS: 30, DEADLINE_MARGIN_SECONDS: 15}.freeze
251
+
211
252
  UNSPECIFIED_HOSTNAMES = ["0.0.0.0", "::", "[::]"].freeze
212
253
 
213
254
  WEBHOOK_SIGNATURE = {
@@ -3,19 +3,21 @@
3
3
  module Nextmail
4
4
  module Resources
5
5
  class Calendar < Resource
6
- def list_events(from:, to:, timezone: nil, include_team: nil, q: nil, limit: nil, cursor: nil, api_key: nil)
7
- query = range_query(from, to, timezone, include_team, q)
6
+ def list_events(from:, to:, timezone: nil, include_team: nil, q: nil, calendar_ids: nil, limit: nil, cursor: nil,
7
+ api_key: nil)
8
+ query = range_query(from, to, timezone, include_team, q, calendar_ids)
8
9
  fetch_page(ApiPaths::CALENDAR_EVENTS, query, limit:, cursor:, api_key:)
9
10
  end
10
11
 
11
- def list_all_events(from:, to:, timezone: nil, include_team: nil, q: nil, limit: nil, cursor: nil, api_key: nil)
12
- query = range_query(from, to, timezone, include_team, q)
12
+ def list_all_events(from:, to:, timezone: nil, include_team: nil, q: nil, calendar_ids: nil, limit: nil, cursor: nil,
13
+ api_key: nil)
14
+ query = range_query(from, to, timezone, include_team, q, calendar_ids)
13
15
  collect_all(ApiPaths::CALENDAR_EVENTS, query, limit:, cursor:, api_key:)
14
16
  end
15
17
 
16
- def iterate_events(from:, to:, timezone: nil, include_team: nil, q: nil, limit: nil, cursor: nil, api_key: nil,
17
- &)
18
- query = range_query(from, to, timezone, include_team, q)
18
+ def iterate_events(from:, to:, timezone: nil, include_team: nil, q: nil, calendar_ids: nil, limit: nil, cursor: nil,
19
+ api_key: nil, &)
20
+ query = range_query(from, to, timezone, include_team, q, calendar_ids)
19
21
  iterate_pages(ApiPaths::CALENDAR_EVENTS, query, limit:, cursor:, api_key:, &)
20
22
  end
21
23
 
@@ -65,6 +67,85 @@ module Nextmail
65
67
  request(fill(ApiPaths::CALENDAR_EVENT_DECLINE_PROPOSAL, id:), method: :post, body: payload(body, fields), api_key:)
66
68
  end
67
69
 
70
+ def list_calendars(kind: nil, api_key: nil)
71
+ fetch_list(ApiPaths::CALENDAR_CALENDARS, query_params(kind:), api_key:)
72
+ end
73
+
74
+ def get_calendar(id, api_key: nil)
75
+ request(fill(ApiPaths::CALENDAR_CALENDAR, id:), api_key:)
76
+ end
77
+
78
+ def create_calendar(body = nil, api_key: nil, **fields)
79
+ request(ApiPaths::CALENDAR_CALENDARS, method: :post, body: payload(body, fields), api_key:)
80
+ end
81
+
82
+ def update_calendar(id, patch = nil, api_key: nil, **fields)
83
+ request(fill(ApiPaths::CALENDAR_CALENDAR, id:), method: :patch, body: payload(patch, fields), api_key:)
84
+ end
85
+
86
+ def delete_calendar(id, notify_guests: nil, api_key: nil)
87
+ query = query_params(notify_guests: flag(notify_guests))
88
+ request(fill(ApiPaths::CALENDAR_CALENDAR, id:), method: :delete, query:, api_key:)
89
+ end
90
+
91
+ def set_calendar_member(id, member_id, body = nil, api_key: nil, **fields)
92
+ request(fill(ApiPaths::CALENDAR_CALENDAR_MEMBER, id:, memberId: member_id), method: :put,
93
+ body: payload(body, fields), repeatable: true, api_key:)
94
+ end
95
+
96
+ def remove_calendar_member(id, member_id, api_key: nil)
97
+ request(fill(ApiPaths::CALENDAR_CALENDAR_MEMBER, id:, memberId: member_id), method: :delete, api_key:)
98
+ end
99
+
100
+ def list_todos(calendar_ids: nil, state: nil, due_before: nil, due_after: nil, limit: nil, cursor: nil, api_key: nil)
101
+ query = todo_query(calendar_ids, state, due_before, due_after)
102
+ fetch_page(ApiPaths::CALENDAR_TODOS, query, limit:, cursor:, api_key:)
103
+ end
104
+
105
+ def list_all_todos(calendar_ids: nil, state: nil, due_before: nil, due_after: nil, limit: nil, cursor: nil,
106
+ api_key: nil)
107
+ query = todo_query(calendar_ids, state, due_before, due_after)
108
+ collect_all(ApiPaths::CALENDAR_TODOS, query, limit:, cursor:, api_key:)
109
+ end
110
+
111
+ def iterate_todos(calendar_ids: nil, state: nil, due_before: nil, due_after: nil, limit: nil, cursor: nil,
112
+ api_key: nil, &)
113
+ query = todo_query(calendar_ids, state, due_before, due_after)
114
+ iterate_pages(ApiPaths::CALENDAR_TODOS, query, limit:, cursor:, api_key:, &)
115
+ end
116
+
117
+ def get_todo(id, api_key: nil)
118
+ request(fill(ApiPaths::CALENDAR_TODO, id:), api_key:)
119
+ end
120
+
121
+ def create_todo(body = nil, api_key: nil, **fields)
122
+ request(ApiPaths::CALENDAR_TODOS, method: :post, body: todo_wire(payload(body, fields)), api_key:)
123
+ end
124
+
125
+ def update_todo(id, patch = nil, api_key: nil, **fields)
126
+ request(fill(ApiPaths::CALENDAR_TODO, id:), method: :patch, body: todo_wire(payload(patch, fields)), api_key:)
127
+ end
128
+
129
+ def delete_todo(id, api_key: nil)
130
+ request(fill(ApiPaths::CALENDAR_TODO, id:), method: :delete, api_key:)
131
+ end
132
+
133
+ def list_calendar_logins(member: nil, api_key: nil)
134
+ fetch_list(ApiPaths::CALENDAR_LOGINS, query_params(member:), api_key:)
135
+ end
136
+
137
+ def create_calendar_login(body = nil, api_key: nil, **fields)
138
+ request(ApiPaths::CALENDAR_LOGINS, method: :post, body: payload(body, fields), api_key:)
139
+ end
140
+
141
+ def revoke_calendar_login(id, api_key: nil)
142
+ request(fill(ApiPaths::CALENDAR_LOGIN, id:), method: :delete, api_key:)
143
+ end
144
+
145
+ def create_calendar_login_profile_link(body = nil, api_key: nil, **fields)
146
+ request(ApiPaths::CALENDAR_LOGIN_PROFILE_LINK, method: :post, body: payload(body, fields), api_key:)
147
+ end
148
+
68
149
  def list_feeds(api_key: nil)
69
150
  fetch_list(ApiPaths::CALENDAR_FEEDS, api_key:)
70
151
  end
@@ -101,9 +182,10 @@ module Nextmail
101
182
  request(fill(ApiPaths::CALENDAR_SUBSCRIPTION, id:), method: :delete, api_key:)
102
183
  end
103
184
 
104
- def import_ics(data, filename: nil, api_key: nil)
105
- request(ApiPaths::CALENDAR_IMPORTS, method: :post, query: query_params(filename: optional_upload_name(data, filename)),
106
- raw: data, content_type: Protocol::CONTENT_TYPES[:CALENDAR], api_key:)
185
+ def import_ics(data, filename: nil, calendar_id: nil, api_key: nil)
186
+ query = query_params(filename: optional_upload_name(data, filename), calendar_id:)
187
+ request(ApiPaths::CALENDAR_IMPORTS, method: :post, query:, raw: data, content_type: Protocol::CONTENT_TYPES[:CALENDAR],
188
+ api_key:)
107
189
  end
108
190
 
109
191
  def list_imports(limit: nil, cursor: nil, api_key: nil)
@@ -128,8 +210,20 @@ module Nextmail
128
210
 
129
211
  private
130
212
 
131
- def range_query(from, to, timezone, include_team, q)
132
- query_params(from: instant(from), to: instant(to), timezone:, include_team: flag(include_team), q:)
213
+ def range_query(from, to, timezone, include_team, q, calendar_ids)
214
+ query_params(from: instant(from), to: instant(to), timezone:, include_team: flag(include_team), q:,
215
+ calendar_ids: joined(calendar_ids))
216
+ end
217
+
218
+ def todo_query(calendar_ids, state, due_before, due_after)
219
+ query_params(calendar_ids: joined(calendar_ids), state:, due_before: instant(due_before),
220
+ due_after: instant(due_after))
221
+ end
222
+
223
+ def todo_wire(wire)
224
+ wire["start"] = instant(wire["start"]) if wire.key?("start")
225
+ wire["due"] = instant(wire["due"]) if wire.key?("due")
226
+ wire
133
227
  end
134
228
 
135
229
  def event_wire(wire)
@@ -113,22 +113,37 @@ module Nextmail
113
113
  request(fill(ApiPaths::CONTACT_ACTIVITY, email:), query:, api_key:)
114
114
  end
115
115
 
116
- def list_cards(email: nil, without_email: nil, kind: nil, group_id: nil, limit: nil, cursor: nil, api_key: nil)
117
- query = card_query(email, without_email, kind, group_id)
116
+ def list_cards(email: nil, without_email: nil, kind: nil, group_id: nil, address_book: nil, limit: nil, cursor: nil,
117
+ api_key: nil)
118
+ query = card_query(email, without_email, kind, group_id, address_book)
118
119
  fetch_page(ApiPaths::CONTACT_CARDS, query, limit:, cursor:, api_key:)
119
120
  end
120
121
 
121
- def list_all_cards(email: nil, without_email: nil, kind: nil, group_id: nil, limit: nil, cursor: nil, api_key: nil)
122
- query = card_query(email, without_email, kind, group_id)
122
+ def list_all_cards(email: nil, without_email: nil, kind: nil, group_id: nil, address_book: nil, limit: nil,
123
+ cursor: nil, api_key: nil)
124
+ query = card_query(email, without_email, kind, group_id, address_book)
123
125
  collect_all(ApiPaths::CONTACT_CARDS, query, limit:, cursor:, api_key:)
124
126
  end
125
127
 
126
- def iterate_cards(email: nil, without_email: nil, kind: nil, group_id: nil, limit: nil, cursor: nil, api_key: nil,
127
- &)
128
- query = card_query(email, without_email, kind, group_id)
128
+ def iterate_cards(email: nil, without_email: nil, kind: nil, group_id: nil, address_book: nil, limit: nil,
129
+ cursor: nil, api_key: nil, &)
130
+ query = card_query(email, without_email, kind, group_id, address_book)
129
131
  iterate_pages(ApiPaths::CONTACT_CARDS, query, limit:, cursor:, api_key:, &)
130
132
  end
131
133
 
134
+ def list_address_books(api_key: nil)
135
+ fetch_list(ApiPaths::ADDRESS_BOOKS, api_key:)
136
+ end
137
+
138
+ def set_address_book_member(id, member_id, body = nil, api_key: nil, **fields)
139
+ request(fill(ApiPaths::ADDRESS_BOOK_MEMBER, id:, memberId: member_id), method: :put, body: payload(body, fields),
140
+ repeatable: true, api_key:)
141
+ end
142
+
143
+ def remove_address_book_member(id, member_id, api_key: nil)
144
+ request(fill(ApiPaths::ADDRESS_BOOK_MEMBER, id:, memberId: member_id), method: :delete, api_key:)
145
+ end
146
+
132
147
  def get_card(id, api_key: nil)
133
148
  request(fill(ApiPaths::CONTACT_CARD, id:), api_key:)
134
149
  end
@@ -189,8 +204,8 @@ module Nextmail
189
204
  query_params(q:, email:, sort:, blocked: flag(blocked))
190
205
  end
191
206
 
192
- def card_query(email, without_email, kind, group_id)
193
- query_params(email:, without_email: flag(without_email), kind:, group_id:)
207
+ def card_query(email, without_email, kind, group_id, address_book)
208
+ query_params(email:, without_email: flag(without_email), kind:, group_id:, address_book:)
194
209
  end
195
210
  end
196
211
  end
@@ -8,6 +8,11 @@ module Nextmail
8
8
  idempotency_key:, repeatable: true, api_key:)
9
9
  end
10
10
 
11
+ def send_raw(body = nil, idempotency_key: nil, api_key: nil, **fields)
12
+ request(ApiPaths::EMAILS_RAW, method: :post, body: Wire.email_raw(payload(body, fields)), idempotent: true,
13
+ idempotency_key:, repeatable: true, api_key:)
14
+ end
15
+
11
16
  def send_batch(emails, idempotency_key: nil, api_key: nil)
12
17
  wire = emails.map { |email| Wire.email(payload(email, {})) }
13
18
  result = request(ApiPaths::EMAILS_BATCH, method: :post, body: {"emails" => wire}, idempotent: true,
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nextmail
4
+ module Resources
5
+ class LiveEvents < Resource
6
+ def initialize(transport, timing: Protocol::LIVE_STREAM_TIMING)
7
+ super(transport)
8
+ @timing = timing
9
+ end
10
+
11
+ def stream(types: nil, address: nil, last_event_id: nil, api_key: nil, &)
12
+ query = query_params(types: joined(types), address:)
13
+ events = Enumerator.new do |yielder|
14
+ LiveEventStream.new(@transport, query:, last_event_id:, api_key:, timing: @timing).run(yielder)
15
+ end
16
+
17
+ each_or_enumerator(events, &)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nextmail
4
+ module Resources
5
+ class ShareLinks < Resource
6
+ def list(kind: nil, limit: nil, cursor: nil, api_key: nil)
7
+ fetch_page(ApiPaths::SHARE_LINKS, query_params(kind:), limit:, cursor:, api_key:)
8
+ end
9
+
10
+ def list_all(kind: nil, limit: nil, cursor: nil, api_key: nil)
11
+ collect_all(ApiPaths::SHARE_LINKS, query_params(kind:), limit:, cursor:, api_key:)
12
+ end
13
+
14
+ def iterate(kind: nil, limit: nil, cursor: nil, api_key: nil, &)
15
+ iterate_pages(ApiPaths::SHARE_LINKS, query_params(kind:), limit:, cursor:, api_key:, &)
16
+ end
17
+
18
+ def get(kind, item_id, api_key: nil)
19
+ request(fill(ApiPaths::SHARE_LINK, kind:, itemId: item_id), api_key:)
20
+ end
21
+
22
+ def set(kind, item_id, body = nil, api_key: nil, **fields)
23
+ request(fill(ApiPaths::SHARE_LINK, kind:, itemId: item_id), method: :put, body: payload(body, fields),
24
+ repeatable: true, api_key:)
25
+ end
26
+
27
+ def delete(kind, item_id, api_key: nil)
28
+ request(fill(ApiPaths::SHARE_LINK, kind:, itemId: item_id), method: :delete, api_key:)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nextmail
4
+ module Resources
5
+ class Shared < Resource
6
+ def get(token, api_key: nil)
7
+ request(fill(ApiPaths::SHARED_ITEM, token:), api_key:)
8
+ end
9
+
10
+ def copy(token, api_key: nil)
11
+ request(fill(ApiPaths::SHARED_ITEM_COPY, token:), method: :post, api_key:)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -3,16 +3,16 @@
3
3
  module Nextmail
4
4
  module Resources
5
5
  class Tasks < Resource
6
- def list(status: nil, limit: nil, cursor: nil, api_key: nil)
7
- fetch_page(ApiPaths::TASKS, query_params(status:), limit:, cursor:, api_key:)
6
+ def list(status: nil, chat_id: nil, limit: nil, cursor: nil, api_key: nil)
7
+ fetch_page(ApiPaths::TASKS, query_params(status:, chat_id:), limit:, cursor:, api_key:)
8
8
  end
9
9
 
10
- def list_all(status: nil, limit: nil, cursor: nil, api_key: nil)
11
- collect_all(ApiPaths::TASKS, query_params(status:), limit:, cursor:, api_key:)
10
+ def list_all(status: nil, chat_id: nil, limit: nil, cursor: nil, api_key: nil)
11
+ collect_all(ApiPaths::TASKS, query_params(status:, chat_id:), limit:, cursor:, api_key:)
12
12
  end
13
13
 
14
- def iterate(status: nil, limit: nil, cursor: nil, api_key: nil, &)
15
- iterate_pages(ApiPaths::TASKS, query_params(status:), limit:, cursor:, api_key:, &)
14
+ def iterate(status: nil, chat_id: nil, limit: nil, cursor: nil, api_key: nil, &)
15
+ iterate_pages(ApiPaths::TASKS, query_params(status:, chat_id:), limit:, cursor:, api_key:, &)
16
16
  end
17
17
 
18
18
  def get(id, api_key: nil)
@@ -92,6 +92,10 @@ module Nextmail
92
92
  request(fill(ApiPaths::THREAD_DELIVERY, id:), query: query_params(address:), api_key:)
93
93
  end
94
94
 
95
+ def list_automations(id, api_key: nil)
96
+ fetch_list(fill(ApiPaths::THREAD_AUTOMATIONS, id:), api_key:)
97
+ end
98
+
95
99
  def get_event(id, api_key: nil)
96
100
  request(fill(ApiPaths::THREAD_EVENT, id:), api_key:)
97
101
  end
@@ -123,6 +127,11 @@ module Nextmail
123
127
  request(ApiPaths::THREADS_COUNTS, query: query_params(address:, hide_labelled: flag(hide_labelled)), api_key:)
124
128
  end
125
129
 
130
+ def wait_for_email(address: nil, from: nil, subject: nil, since: nil, timeout: nil, api_key: nil)
131
+ request(ApiPaths::THREADS_WAIT, query: query_params(address:, from:, subject:, since: instant(since), timeout:),
132
+ timeout: wait_deadline(timeout), api_key:)
133
+ end
134
+
126
135
  def summary(id, api_key: nil)
127
136
  request(fill(ApiPaths::THREAD_SUMMARY, id:), api_key:)
128
137
  end
@@ -213,6 +222,13 @@ module Nextmail
213
222
  def file_link_query(include_revoked)
214
223
  query_params(include_revoked: flag(include_revoked))
215
224
  end
225
+
226
+ def wait_deadline(timeout)
227
+ wait = timeout.nil? ? Protocol::THREAD_WAIT[:DEFAULT_TIMEOUT_SECONDS] : timeout
228
+ raise ArgumentError, Defaults::MESSAGES[:WAIT_TIMEOUT_SHAPE] unless wait.is_a?(Numeric)
229
+
230
+ wait + Protocol::THREAD_WAIT[:DEADLINE_MARGIN_SECONDS]
231
+ end
216
232
  end
217
233
  end
218
234
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nextmail
4
+ module Resources
5
+ class Traffic < Resource
6
+ def list(domain_id: nil, direction: nil, outcomes: nil, address: nil, recipient: nil, since: nil, until: nil,
7
+ limit: nil, cursor: nil, api_key: nil)
8
+ query = traffic_query(domain_id, direction, outcomes, address, recipient, since,
9
+ binding.local_variable_get(:until))
10
+ fetch_page(ApiPaths::TRAFFIC, query, limit:, cursor:, api_key:)
11
+ end
12
+
13
+ def list_all(domain_id: nil, direction: nil, outcomes: nil, address: nil, recipient: nil, since: nil,
14
+ until: nil, limit: nil, cursor: nil, api_key: nil)
15
+ query = traffic_query(domain_id, direction, outcomes, address, recipient, since,
16
+ binding.local_variable_get(:until))
17
+ collect_all(ApiPaths::TRAFFIC, query, limit:, cursor:, api_key:)
18
+ end
19
+
20
+ def iterate(domain_id: nil, direction: nil, outcomes: nil, address: nil, recipient: nil, since: nil,
21
+ until: nil, limit: nil, cursor: nil, api_key: nil, &)
22
+ query = traffic_query(domain_id, direction, outcomes, address, recipient, since,
23
+ binding.local_variable_get(:until))
24
+ iterate_pages(ApiPaths::TRAFFIC, query, limit:, cursor:, api_key:, &)
25
+ end
26
+
27
+ private
28
+
29
+ def traffic_query(domain_id, direction, outcomes, address, recipient, since, until_value)
30
+ outcome = (outcomes.nil? || outcomes.empty?) ? nil : joined(outcomes)
31
+ log_window_query(since, until_value)
32
+ .merge(query_params(domain_id:, direction:, outcome:, address:, recipient:))
33
+ end
34
+ end
35
+ end
36
+ end