Class: WhatsAppNotifier::WebAdapter

Inherits:
Object
  • Object
show all
Defined in:
lib/whatsapp_notifier/web_adapter.rb

Constant Summary collapse

DEFAULT_OPEN_TIMEOUT =
5
DEFAULT_READ_TIMEOUT =
30
MEDIA_OPEN_TIMEOUT =

Media bytes can be tens of MB over a slow link — give the binary fetch a longer read window than the JSON control plane.

5
MEDIA_READ_TIMEOUT =
60
HTTP_CLASSES =
{
  post: Net::HTTP::Post,
  get: Net::HTTP::Get,
  delete: Net::HTTP::Delete
}.freeze
INBOUND_OPTIONAL_KEYS =

Optional inbound keys introduced by the 0.7.0 service (media verdict + sender display name). Mapped ONLY when the wire payload carries them, so hosts can key-gate on has_media presence: a missing key means “0.6.0 service, no media support”, while has_media: false means “text message”.

{
  has_media: %w[hasMedia has_media],
  media_status: %w[mediaStatus media_status],
  media_error: %w[mediaError media_error],
  media_mime: %w[mediaMime media_mime],
  media_filename: %w[mediaFilename media_filename],
  media_size: %w[mediaSize media_size],
  sender_name: %w[senderName sender_name]
}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base_url: self.class.default_base_url, open_timeout: DEFAULT_OPEN_TIMEOUT, read_timeout: DEFAULT_READ_TIMEOUT) ⇒ WebAdapter

Returns a new instance of WebAdapter.



38
39
40
41
42
43
44
# File 'lib/whatsapp_notifier/web_adapter.rb', line 38

def initialize(base_url: self.class.default_base_url,
               open_timeout: DEFAULT_OPEN_TIMEOUT,
               read_timeout: DEFAULT_READ_TIMEOUT)
  @base_url = base_url
  @open_timeout = open_timeout
  @read_timeout = read_timeout
end

Class Method Details

.default_base_urlObject



34
35
36
# File 'lib/whatsapp_notifier/web_adapter.rb', line 34

def self.default_base_url
  ENV["WHATSAPP_NOTIFIER_SERVICE_URL"] || ENV["WHATSAPP_SERVICE_URL"] || "http://127.0.0.1:3001"
end

Instance Method Details

#connection_status(metadata: {}) ⇒ Object



69
70
71
72
73
74
75
76
77
# File 'lib/whatsapp_notifier/web_adapter.rb', line 69

def connection_status(metadata: {})
  user_id = user_id_from()
  response = request(:get, "/status/#{user_id}")
  {
    state: response["state"],
    authenticated: response["authenticated"],
    has_qr: response["hasQR"]
  }
end

#delete_media(message_id:, metadata: {}) ⇒ Object

Removes the service’s copy after the host has attached the bytes. Idempotent on the service side: deleting absent media still succeeds. A 0.6.0 service mid-rollout has no /media routes and answers 404 —degrade to { success: false } instead of raising, mirroring fetch_media’s nil-on-404.



117
118
119
120
121
# File 'lib/whatsapp_notifier/web_adapter.rb', line 117

def delete_media(message_id:, metadata: {})
  user_id = user_id_from()
  response = request(:delete, "/media/#{user_id}/#{path_id(message_id)}", allow_404: true)
  { success: response.fetch("success", false) }
end

#fetch_inbound(metadata: {}) ⇒ Object

Drains the service’s pending inbound queue for this user. The service returns the messages once, then clears them (at-least-once handoff —callers must dedupe on message_id). Accepts either a bare array or a { “messages” => […] } envelope so the wire format can evolve.



83
84
85
86
87
88
# File 'lib/whatsapp_notifier/web_adapter.rb', line 83

def fetch_inbound(metadata: {})
  user_id = user_id_from()
  response = request(:get, "/inbound/#{user_id}")
  raw = response.is_a?(Hash) ? response["messages"] : response
  Array(raw).map { |m| map_inbound_message(m) }
end

#fetch_media(message_id:, metadata: {}) ⇒ Object

Fetches the raw bytes of a downloaded inbound media file. Returns { body:, mime:, filename:, size: } or nil when the service has no copy (never downloaded, swept by TTL, or already deleted).

Deliberately NOT routed through #request: that path JSON-parses the response body (and host apps are known to patch it further), which would corrupt binary payloads.



97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/whatsapp_notifier/web_adapter.rb', line 97

def fetch_media(message_id:, metadata: {})
  user_id = user_id_from()
  res = binary_get("/media/#{user_id}/#{path_id(message_id)}")
  return nil if res.code.to_s == "404"
  raise "service request failed (#{res.code}): #{res.body}" unless res.is_a?(Net::HTTPSuccess)

  body = res.body.to_s
  {
    body: body,
    mime: res["Content-Type"],
    filename: filename_from(res["Content-Disposition"]),
    size: body.bytesize
  }
end

#fetch_qr_code(metadata: {}) ⇒ Object



63
64
65
66
67
# File 'lib/whatsapp_notifier/web_adapter.rb', line 63

def fetch_qr_code(metadata: {})
  user_id = user_id_from()
  response = request(:get, "/qr/#{user_id}")
  response["qr"]
end

#logout(metadata: {}) ⇒ Object

Logs the user out of WhatsApp and clears their saved session on the service.



124
125
126
127
128
# File 'lib/whatsapp_notifier/web_adapter.rb', line 124

def logout(metadata: {})
  user_id = user_id_from()
  response = request(:post, "/logout/#{user_id}")
  { success: response.fetch("success", false) }
end

#send_message(payload:, session: {}) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/whatsapp_notifier/web_adapter.rb', line 46

def send_message(payload:, session: {})
  user_id = user_id_from(payload[:metadata] || {})
  body = {
    to: payload[:to],
    message: payload[:body],
    mediaUrl: payload.dig(:metadata, :media_url)
  }.compact

  response = request(:post, "/send/#{user_id}", body: body)
  {
    success: response.fetch("success"),
    message_id: payload[:idempotency_key] || "local-#{Time.now.to_i}",
    session: session,
    error_message: response["error"]
  }
end