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.
- checksums.yaml +4 -4
- data/README.md +13 -1
- data/lib/nextmail/api_paths.rb +19 -0
- data/lib/nextmail/client.rb +4 -0
- data/lib/nextmail/constants.rb +82 -2
- data/lib/nextmail/defaults.rb +10 -0
- data/lib/nextmail/event_stream.rb +120 -0
- data/lib/nextmail/http.rb +64 -0
- data/lib/nextmail/live_event_stream.rb +169 -0
- data/lib/nextmail/protocol.rb +44 -3
- data/lib/nextmail/resources/calendar.rb +106 -12
- data/lib/nextmail/resources/contacts.rb +24 -9
- data/lib/nextmail/resources/emails.rb +5 -0
- data/lib/nextmail/resources/live_events.rb +21 -0
- data/lib/nextmail/resources/share_links.rb +32 -0
- data/lib/nextmail/resources/shared.rb +15 -0
- data/lib/nextmail/resources/tasks.rb +6 -6
- data/lib/nextmail/resources/threads.rb +16 -0
- data/lib/nextmail/resources/traffic.rb +36 -0
- data/lib/nextmail/transport.rb +118 -3
- data/lib/nextmail/version.rb +1 -1
- data/lib/nextmail/wire.rb +14 -0
- data/lib/nextmail.rb +2 -0
- metadata +8 -2
|
@@ -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
|
data/lib/nextmail/protocol.rb
CHANGED
|
@@ -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,
|
|
7
|
-
|
|
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,
|
|
12
|
-
|
|
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,
|
|
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
|
-
|
|
106
|
-
|
|
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,
|
|
117
|
-
|
|
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,
|
|
122
|
-
|
|
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,
|
|
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
|