Class: Vortex::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/vortex/client.rb

Overview

Vortex API client for Ruby

Provides the same functionality as other Vortex SDKs with JWT generation, invitation management, and full API compatibility.

Constant Summary collapse

DEFAULT_BASE_URL =

Base URL for Vortex API

'https://api.vortexsoftware.com'

Instance Method Summary collapse

Constructor Details

#initialize(api_key, base_url: nil) ⇒ Client



20
21
22
23
24
# File 'lib/vortex/client.rb', line 20

def initialize(api_key, base_url: nil)
  @api_key = api_key
  @base_url = base_url || DEFAULT_BASE_URL
  @connection = build_connection
end

Instance Method Details

#accept_invitations(invitation_ids, user_or_target) ⇒ Hash

Accept invitations using the new User format (preferred)

Supports three formats:

  1. User hash (preferred): { email: ‘…’, phone: ‘…’, name: ‘…’ }

  2. Target hash (deprecated): { type: ‘email’, value: ‘…’ }

  3. Array of targets (deprecated): [{ type: ‘email’, value: ‘…’ }, …]

Examples:

New format (preferred)

user = { email: '[email protected]', name: 'John Doe' }
result = client.accept_invitations(['inv-123'], user)

Legacy format (deprecated)

target = { type: 'email', value: '[email protected]' }
result = client.accept_invitations(['inv-123'], target)

Raises:



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/vortex/client.rb', line 168

def accept_invitations(invitation_ids, user_or_target)
  # Check if it's an array of targets (legacy format with multiple targets)
  if user_or_target.is_a?(Array)
    warn '[Vortex SDK] DEPRECATED: Passing an array of targets is deprecated. ' \
         'Use the User format instead: accept_invitations(invitation_ids, { email: "[email protected]" })'

    raise VortexError, 'No targets provided' if user_or_target.empty?

    last_result = nil
    last_exception = nil

    user_or_target.each do |target|
      begin
        last_result = accept_invitations(invitation_ids, target)
      rescue => e
        last_exception = e
      end
    end

    raise last_exception if last_exception

    return last_result || {}
  end

  # Check if it's a legacy target format (has :type and :value keys)
  is_legacy_target = user_or_target.key?(:type) && user_or_target.key?(:value)

  if is_legacy_target
    warn '[Vortex SDK] DEPRECATED: Passing a target hash is deprecated. ' \
         'Use the User format instead: accept_invitations(invitation_ids, { email: "[email protected]" })'

    # Convert target to User format
    target_type = user_or_target[:type]
    target_value = user_or_target[:value]

    user = {}
    case target_type
    when 'email'
      user[:email] = target_value
    when 'sms', 'phoneNumber'
      user[:phone] = target_value
    else
      # For other types, try to use as email
      user[:email] = target_value
    end

    # Recursively call with User format
    return accept_invitations(invitation_ids, user)
  end

  # New User format
  user = user_or_target

  # Validate that either email or phone is provided
  raise VortexError, 'User must have either email or phone' if user[:email].nil? && user[:phone].nil?

  body = {
    invitationIds: invitation_ids,
    user: user.compact # Remove nil values
  }

  response = @connection.post('/api/v1/invitations/accept') do |req|
    req.headers['Content-Type'] = 'application/json'
    req.body = JSON.generate(body)
  end

  handle_response(response)
rescue VortexError
  raise
rescue => e
  raise VortexError, "Failed to accept invitations: #{e.message}"
end

#delete_invitations_by_group(group_type, group_id) ⇒ Hash

Delete invitations by group

Raises:



261
262
263
264
265
266
# File 'lib/vortex/client.rb', line 261

def delete_invitations_by_group(group_type, group_id)
  response = @connection.delete("/api/v1/invitations/by-group/#{group_type}/#{group_id}")
  handle_response(response)
rescue => e
  raise VortexError, "Failed to delete group invitations: #{e.message}"
end

#generate_jwt(params) ⇒ String

Generate a JWT token for a user

Examples:

Simple usage

client = Vortex::Client.new(ENV['VORTEX_API_KEY'])
jwt = client.generate_jwt({
  user: {
    id: 'user-123',
    email: '[email protected]',
    admin_scopes: ['autojoin']
  }
})

With additional attributes

jwt = client.generate_jwt({
  user: { id: 'user-123', email: '[email protected]' },
  attributes: { role: 'admin', department: 'Engineering' }
})

Raises:

  • (VortexError)

    If API key is invalid or JWT generation fails



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/vortex/client.rb', line 47

def generate_jwt(params)
  user = params[:user]
  attributes = params[:attributes]

  # Parse API key - same format as Node.js SDK
  prefix, encoded_id, key = @api_key.split('.')

  raise VortexError, 'Invalid API key format' unless prefix && encoded_id && key
  raise VortexError, 'Invalid API key prefix' unless prefix == 'VRTX'

  # Decode the ID from base64url (same as Node.js Buffer.from(encodedId, 'base64url'))
  decoded_bytes = Base64.urlsafe_decode64(encoded_id)

  # Convert to UUID string format (same as uuidStringify in Node.js)
  id = format_uuid(decoded_bytes)

  expires = Time.now.to_i + 3600

  # Step 1: Derive signing key from API key + ID (same as Node.js)
  signing_key = OpenSSL::HMAC.digest('sha256', key, id)

  # Step 2: Build header + payload
  header = {
    iat: Time.now.to_i,
    alg: 'HS256',
    typ: 'JWT',
    kid: id
  }

  # Build payload - start with required fields
  payload = {
    userId: user[:id],
    userEmail: user[:email],
    expires: expires
  }

  # Add adminScopes if present
  if user[:admin_scopes]
    payload[:adminScopes] = user[:admin_scopes]
  end

  # Add any additional properties from attributes
  if attributes && !attributes.empty?
    payload.merge!(attributes)
  end

  # Step 3: Base64URL encode (same as Node.js)
  header_b64 = base64url_encode(JSON.generate(header))
  payload_b64 = base64url_encode(JSON.generate(payload))

  # Step 4: Sign with HMAC-SHA256 (same as Node.js)
  signature = OpenSSL::HMAC.digest('sha256', signing_key, "#{header_b64}.#{payload_b64}")
  signature_b64 = base64url_encode(signature)

  "#{header_b64}.#{payload_b64}.#{signature_b64}"
rescue => e
  raise VortexError, "JWT generation failed: #{e.message}"
end

#get_invitation(invitation_id) ⇒ Hash

Get a specific invitation by ID

Raises:



130
131
132
133
134
135
# File 'lib/vortex/client.rb', line 130

def get_invitation(invitation_id)
  response = @connection.get("/api/v1/invitations/#{invitation_id}")
  handle_response(response)
rescue => e
  raise VortexError, "Failed to get invitation: #{e.message}"
end

#get_invitations_by_group(group_type, group_id) ⇒ Array<Hash>

Get invitations by group

Raises:



247
248
249
250
251
252
253
# File 'lib/vortex/client.rb', line 247

def get_invitations_by_group(group_type, group_id)
  response = @connection.get("/api/v1/invitations/by-group/#{group_type}/#{group_id}")
  result = handle_response(response)
  result['invitations'] || []
rescue => e
  raise VortexError, "Failed to get group invitations: #{e.message}"
end

#get_invitations_by_target(target_type, target_value) ⇒ Array<Hash>

Get invitations by target

Raises:



114
115
116
117
118
119
120
121
122
123
# File 'lib/vortex/client.rb', line 114

def get_invitations_by_target(target_type, target_value)
  response = @connection.get('/api/v1/invitations') do |req|
    req.params['targetType'] = target_type
    req.params['targetValue'] = target_value
  end

  handle_response(response)['invitations'] || []
rescue => e
  raise VortexError, "Failed to get invitations by target: #{e.message}"
end

#reinvite(invitation_id) ⇒ Hash

Reinvite a user

Raises:



273
274
275
276
277
278
# File 'lib/vortex/client.rb', line 273

def reinvite(invitation_id)
  response = @connection.post("/api/v1/invitations/#{invitation_id}/reinvite")
  handle_response(response)
rescue => e
  raise VortexError, "Failed to reinvite: #{e.message}"
end

#revoke_invitation(invitation_id) ⇒ Hash

Revoke (delete) an invitation

Raises:



142
143
144
145
146
147
# File 'lib/vortex/client.rb', line 142

def revoke_invitation(invitation_id)
  response = @connection.delete("/api/v1/invitations/#{invitation_id}")
  handle_response(response)
rescue => e
  raise VortexError, "Failed to revoke invitation: #{e.message}"
end