@teamvortexsoftware/vortex-node-22-sdk 0.0.8 โ†’ 0.1.1

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.
package/README.md CHANGED
@@ -17,7 +17,8 @@ npm install --save @teamvortexsoftware/vortex-node-22-sdk
17
17
 
18
18
  Once you have the SDK, [login](https://admin.vortexsoftware.com/signin) to Vortex and [create an API Key](https://admin.vortexsoftware.com/members/api-keys). Keep your API key safe! Vortex does not store the API key and it is not retrievable once it has been created. Also, it should be noted that the API key you use is scoped to the environment you're targeting. The environment is implied based on the API key used to sign JWTs and to make API requests.
19
19
 
20
- Your API key is used to
20
+ Your API key is used to
21
+
21
22
  - Sign JWTs for use with the Vortex Widget
22
23
  - Make API calls against the [Vortex API](https://api.vortexsoftware.com/api)
23
24
 
@@ -42,18 +43,18 @@ app.get('/vortex-jwt', (req, res) => {
42
43
 
43
44
  const token = vortex.generateJwt({
44
45
  user: {
45
- id: "user-123",
46
- email: "user@example.com",
47
- adminScopes: ['autoJoin'] // Optional: grants admin privileges for auto-joining
48
- }
46
+ id: 'user-123',
47
+ email: 'user@example.com',
48
+ adminScopes: ['autojoin'], // Optional: grants admin privileges for autojoining
49
+ },
49
50
  });
50
51
 
51
52
  res.end(JSON.stringify({ jwt: token }));
52
- })
53
+ });
53
54
 
54
55
  app.listen(port, () => {
55
- console.log(`Example app listening on port ${port}. Fetch example JWT by hitting /vortex-jwt`)
56
- })
56
+ console.log(`Example app listening on port ${port}. Fetch example JWT by hitting /vortex-jwt`);
57
+ });
57
58
  ```
58
59
 
59
60
  You can also add extra properties to the JWT payload:
@@ -61,12 +62,12 @@ You can also add extra properties to the JWT payload:
61
62
  ```ts
62
63
  const token = vortex.generateJwt({
63
64
  user: {
64
- id: "user-123",
65
- email: "user@example.com",
66
- adminScopes: ['autoJoin']
65
+ id: 'user-123',
66
+ email: 'user@example.com',
67
+ adminScopes: ['autojoin'],
67
68
  },
68
- role: "admin",
69
- department: "Engineering"
69
+ role: 'admin',
70
+ department: 'Engineering',
70
71
  });
71
72
  ```
72
73
 
@@ -86,14 +87,18 @@ const userId = 'users-id-in-my-system';
86
87
  const identifiers = [
87
88
  { type: 'email', value: 'users@emailaddress.com' },
88
89
  { type: 'email', value: 'someother@address.com' },
89
- { type: 'sms', value: '18008675309' }
90
+ { type: 'sms', value: '18008675309' },
90
91
  ];
91
92
 
92
93
  // groups are specific to your product. This list should be the groups that the current requesting user is a part of. It is up to you to define them if you so choose. Based on the values here, we can determine whether or not the user is allowed to invite others to a particular group
93
94
  const groups = [
94
- { type: 'workspace', groupId: 'some-workspace-id', name: 'The greatest workspace...pause...in the world' },
95
- { type: 'document', groupId: 'some-document-id', name: 'Ricky\'s grade 10 word papers' },
96
- { type: 'document', groupId: 'another-document-id', name: 'Sunnyvale bylaws' }
95
+ {
96
+ type: 'workspace',
97
+ groupId: 'some-workspace-id',
98
+ name: 'The greatest workspace...pause...in the world',
99
+ },
100
+ { type: 'document', groupId: 'some-document-id', name: "Ricky's grade 10 word papers" },
101
+ { type: 'document', groupId: 'another-document-id', name: 'Sunnyvale bylaws' },
97
102
  ];
98
103
 
99
104
  // If your product has the concept of user roles (admin, guest, member, etc), provide it here
@@ -104,17 +109,21 @@ const vortex = new Vortex(process.env.VORTEX_API_KEY);
104
109
 
105
110
  app.get('/vortex-jwt', (req, res) => {
106
111
  res.setHeader('Content-Type', 'application/json');
107
- res.end(JSON.stringify({ jwt: vortex.generateJwt({
108
- userId,
109
- identifiers,
110
- groups,
111
- role
112
- })}));
113
- })
112
+ res.end(
113
+ JSON.stringify({
114
+ jwt: vortex.generateJwt({
115
+ userId,
116
+ identifiers,
117
+ groups,
118
+ role,
119
+ }),
120
+ })
121
+ );
122
+ });
114
123
 
115
124
  app.listen(port, () => {
116
- console.log(`Example app listening on port ${port}. Fetch example JWT by hitting /vortex-jwt`)
117
- })
125
+ console.log(`Example app listening on port ${port}. Fetch example JWT by hitting /vortex-jwt`);
126
+ });
118
127
  ```
119
128
 
120
129
  Now, you can utilize that JWT endpoint in conjuction with the Vortex widget
@@ -122,7 +131,7 @@ Now, you can utilize that JWT endpoint in conjuction with the Vortex widget
122
131
  Here's an example hook:
123
132
 
124
133
  ```ts
125
- import { useEffect, useState } from "react";
134
+ import { useEffect, useState } from 'react';
126
135
 
127
136
  export function useVortexJwt() {
128
137
  const [data, setData] = useState<any>(null);
@@ -134,7 +143,7 @@ export function useVortexJwt() {
134
143
 
135
144
  async function fetchJwt() {
136
145
  try {
137
- const res = await fetch("/vortex-jwt");
146
+ const res = await fetch('/vortex-jwt');
138
147
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
139
148
  const json = await res.json();
140
149
  if (!cancelled) setData(json);
@@ -179,6 +188,7 @@ function InviteWrapperComponent() {
179
188
  />);
180
189
  }
181
190
  ```
191
+
182
192
  ### Fetch an invitation by ID
183
193
 
184
194
  When a shared invitation link or an invitaion link sent via email is clicked, the user who clicks it is redirected to the landing page you set in the widget configurator. For instance, if you set http://localhost:3000/invite/landing as the landing page and the invitation being clicked has an id of deadbeef-dead-4bad-8dad-c001d00dc0de, the user who clicks the invitation link will land on http://localhost:3000/invite/landing?invitationId=deadbeef-dead-4bad-8dad-c001d00dc0de
@@ -204,8 +214,9 @@ app.get('/invite/landing', async (req, res) => {
204
214
  // For the sake of simplicity, we'll simply return the raw JSON
205
215
  res.setHeader('Content-Type', 'application/json');
206
216
  res.end(JSON.stringify(invitation));
207
- })
217
+ });
208
218
  ```
219
+
209
220
  ### View invitations by target (email address for example)
210
221
 
211
222
  Depending on your use case, you may want to accept all outstanding invitations to a given user when they sign up for your service. If you don't want to auto accept, you may want to present the new user with a list of all invitations that target them. Either way, the example below shows how you fetch these invitations once you know how to identify (via email, sms or others in the future) a new user to your product.
@@ -229,8 +240,9 @@ app.get('/invitations/by-email', async (req, res) => {
229
240
  // For the sake of simplicity, we'll simply return the raw JSON
230
241
  res.setHeader('Content-Type', 'application/json');
231
242
  res.end(JSON.stringify(invitations));
232
- })
243
+ });
233
244
  ```
245
+
234
246
  ### Accept invitations
235
247
 
236
248
  This is how you'd accept one or more invitations with the SDK. You want this as part of your signup flow more than likely. When someone clicks on an invitation link, we redirect to the landing page you specified in the widget configuration. Ultimately, the user will sign up with your service and that is when you create the relationship between the newly created user in your system and whatever grouping defined in the invitation itself.
@@ -246,12 +258,12 @@ app.post('/signup', async (req, res) => {
246
258
  }
247
259
 
248
260
  // YOUR signup logic, whatever it may be
249
- await myApp.doSignupLogic(email);
261
+ await myApp.doSignupLogic(email);
250
262
 
251
263
  // you may want to do this even if the user is signing up without clicking an invitaiton link
252
264
  const invitations = await vortex.getInvitationsByTarget('email', email);
253
265
 
254
- // Assume that your application may pass the original invitationId from the
266
+ // Assume that your application may pass the original invitationId from the
255
267
  // landing page registered with the configured widget
256
268
  const invitationId = req.body.invitationId;
257
269
 
@@ -260,16 +272,17 @@ app.post('/signup', async (req, res) => {
260
272
  uniqueInvitationIds.push(invitationId);
261
273
  }
262
274
 
263
- const acceptedInvitations = await vortex.acceptInvitations(
264
- uniqueInvitationIds,
265
- { type: 'email', value: email }
266
- );
267
-
275
+ const acceptedInvitations = await vortex.acceptInvitations(uniqueInvitationIds, {
276
+ type: 'email',
277
+ value: email,
278
+ });
279
+
268
280
  // continue with post-signup activity. perhaps redirect to your logged in landing page
269
281
 
270
282
  res.redirect(302, '/app');
271
- })
283
+ });
272
284
  ```
285
+
273
286
  ### Fetch invitations by group
274
287
 
275
288
  Perhaps you want to allow your users to see all outstanding invitations for a group that they are a member of. Or perhaps you want this exclusively for admins of the group. However you choose to do it, this SDK feature will allow you to fetch all outstanding invitations for a group.
@@ -286,8 +299,9 @@ app.get('/invitations/by-group', async (req, res) => {
286
299
 
287
300
  res.setHeader('Content-Type', 'application/json');
288
301
  res.end(JSON.stringify(invitations));
289
- })
302
+ });
290
303
  ```
304
+
291
305
  ### Reinvite
292
306
 
293
307
  You may want to allow your users to resend an existing invitation. Allowing for this will increase the conversion chances of a stale invitation. Perhaps you display a list of outstanding invites and allow for a reinvite based on that list.
@@ -304,8 +318,9 @@ app.post('/invitations/reinvite', async (req, res) => {
304
318
 
305
319
  res.setHeader('Content-Type', 'application/json');
306
320
  res.end(JSON.stringify(invitation));
307
- })
321
+ });
308
322
  ```
323
+
309
324
  ### Revoke invitation
310
325
 
311
326
  In addition to reinvite, you may want to present your users (or perhaps just admins) with the ability to revoke outstanding invitations.
@@ -322,13 +337,15 @@ app.post('/invitations/revoke', async (req, res) => {
322
337
 
323
338
  res.setHeader('Content-Type', 'application/json');
324
339
  res.end(JSON.stringify({}));
325
- })
340
+ });
326
341
  ```
342
+
327
343
  ### Delete invitations by group
328
344
 
329
345
  Your product may allow for your users to delete the underlying resource that is tied to one or more invitations. For instance, say your product has the concept of a 'workspace' and your invitations are created specifying a particular workspace associated with each invitation. Then, at some point in the future, the admin of the workspace decides to delete it. This means all invitations associated with that workspace are now invalid and need to be removed so that reminders don't go out for any outstanding invite to the now deleted workspace.
330
346
 
331
347
  Here is how to clean them up when the workspace is deleted.
348
+
332
349
  ```ts
333
350
  app.delete('/workspace/:workspaceId', async (req, res) => {
334
351
  const { workspaceId } = req.params;
@@ -341,5 +358,5 @@ app.delete('/workspace/:workspaceId', async (req, res) => {
341
358
 
342
359
  res.setHeader('Content-Type', 'application/json');
343
360
  res.end(JSON.stringify({}));
344
- })
345
- ```
361
+ });
362
+ ```
package/dist/index.d.mts CHANGED
@@ -51,6 +51,8 @@ type InvitationResult = {
51
51
  projectId: string;
52
52
  groups: InvitationGroup[];
53
53
  accepts: InvitationAcceptance[];
54
+ expired: boolean;
55
+ expires?: string;
54
56
  };
55
57
  type AcceptInvitationRequest = {
56
58
  invitationIds: string[];
@@ -69,6 +71,31 @@ type User = {
69
71
  adminScopes?: string[];
70
72
  [key: string]: any;
71
73
  };
74
+ /**
75
+ * Autojoin domain configuration
76
+ */
77
+ type AutojoinDomain = {
78
+ id: string;
79
+ domain: string;
80
+ };
81
+ /**
82
+ * Response from autojoin API endpoints
83
+ */
84
+ type AutojoinDomainsResponse = {
85
+ autojoinDomains: AutojoinDomain[];
86
+ invitation: InvitationResult | null;
87
+ };
88
+ /**
89
+ * Request body for configuring autojoin domains
90
+ */
91
+ type ConfigureAutojoinRequest = {
92
+ scope: string;
93
+ scopeType: string;
94
+ scopeName?: string;
95
+ domains: string[];
96
+ widgetId: string;
97
+ metadata?: Record<string, any>;
98
+ };
72
99
 
73
100
  declare class Vortex {
74
101
  private apiKey;
@@ -86,7 +113,7 @@ declare class Vortex {
86
113
  * user: {
87
114
  * id: "user-123",
88
115
  * email: "user@example.com",
89
- * adminScopes: ['autoJoin']
116
+ * adminScopes: ['autojoin']
90
117
  * }
91
118
  * });
92
119
  * ```
@@ -111,6 +138,47 @@ declare class Vortex {
111
138
  deleteInvitationsByGroup(groupType: string, groupId: string): Promise<{}>;
112
139
  getInvitationsByGroup(groupType: string, groupId: string): Promise<InvitationResult[]>;
113
140
  reinvite(invitationId: string): Promise<InvitationResult>;
141
+ /**
142
+ * Get autojoin domains configured for a specific scope
143
+ *
144
+ * @param scopeType - The type of scope (e.g., "organization", "team", "project")
145
+ * @param scope - The scope identifier (customer's group ID)
146
+ * @returns Autojoin domains and associated invitation
147
+ *
148
+ * @example
149
+ * ```typescript
150
+ * const result = await vortex.getAutojoinDomains('organization', 'acme-org');
151
+ * console.log(result.autojoinDomains); // [{ id: '...', domain: 'acme.com' }]
152
+ * ```
153
+ */
154
+ getAutojoinDomains(scopeType: string, scope: string): Promise<AutojoinDomainsResponse>;
155
+ /**
156
+ * Configure autojoin domains for a specific scope
157
+ *
158
+ * This endpoint syncs autojoin domains - it will add new domains, remove domains
159
+ * not in the provided list, and deactivate the autojoin invitation if all domains
160
+ * are removed (empty array).
161
+ *
162
+ * @param params - Configuration parameters
163
+ * @param params.scope - The scope identifier (customer's group ID)
164
+ * @param params.scopeType - The type of scope (e.g., "organization", "team")
165
+ * @param params.scopeName - Optional display name for the scope
166
+ * @param params.domains - Array of domains to configure for autojoin
167
+ * @param params.widgetId - The widget configuration ID
168
+ * @returns Updated autojoin domains and associated invitation
169
+ *
170
+ * @example
171
+ * ```typescript
172
+ * const result = await vortex.configureAutojoin({
173
+ * scope: 'acme-org',
174
+ * scopeType: 'organization',
175
+ * scopeName: 'Acme Corporation',
176
+ * domains: ['acme.com', 'acme.org'],
177
+ * widgetId: 'widget-123',
178
+ * });
179
+ * ```
180
+ */
181
+ configureAutojoin(params: ConfigureAutojoinRequest): Promise<AutojoinDomainsResponse>;
114
182
  }
115
183
 
116
- export { type AcceptInvitationRequest, type ApiRequestBody, type ApiResponseJson, type GroupInput, type InvitationAcceptance, type InvitationGroup, type InvitationResult, type InvitationTarget, type User, Vortex };
184
+ export { type AcceptInvitationRequest, type ApiRequestBody, type ApiResponseJson, type AutojoinDomain, type AutojoinDomainsResponse, type ConfigureAutojoinRequest, type GroupInput, type InvitationAcceptance, type InvitationGroup, type InvitationResult, type InvitationTarget, type User, Vortex };
package/dist/index.d.ts CHANGED
@@ -51,6 +51,8 @@ type InvitationResult = {
51
51
  projectId: string;
52
52
  groups: InvitationGroup[];
53
53
  accepts: InvitationAcceptance[];
54
+ expired: boolean;
55
+ expires?: string;
54
56
  };
55
57
  type AcceptInvitationRequest = {
56
58
  invitationIds: string[];
@@ -69,6 +71,31 @@ type User = {
69
71
  adminScopes?: string[];
70
72
  [key: string]: any;
71
73
  };
74
+ /**
75
+ * Autojoin domain configuration
76
+ */
77
+ type AutojoinDomain = {
78
+ id: string;
79
+ domain: string;
80
+ };
81
+ /**
82
+ * Response from autojoin API endpoints
83
+ */
84
+ type AutojoinDomainsResponse = {
85
+ autojoinDomains: AutojoinDomain[];
86
+ invitation: InvitationResult | null;
87
+ };
88
+ /**
89
+ * Request body for configuring autojoin domains
90
+ */
91
+ type ConfigureAutojoinRequest = {
92
+ scope: string;
93
+ scopeType: string;
94
+ scopeName?: string;
95
+ domains: string[];
96
+ widgetId: string;
97
+ metadata?: Record<string, any>;
98
+ };
72
99
 
73
100
  declare class Vortex {
74
101
  private apiKey;
@@ -86,7 +113,7 @@ declare class Vortex {
86
113
  * user: {
87
114
  * id: "user-123",
88
115
  * email: "user@example.com",
89
- * adminScopes: ['autoJoin']
116
+ * adminScopes: ['autojoin']
90
117
  * }
91
118
  * });
92
119
  * ```
@@ -111,6 +138,47 @@ declare class Vortex {
111
138
  deleteInvitationsByGroup(groupType: string, groupId: string): Promise<{}>;
112
139
  getInvitationsByGroup(groupType: string, groupId: string): Promise<InvitationResult[]>;
113
140
  reinvite(invitationId: string): Promise<InvitationResult>;
141
+ /**
142
+ * Get autojoin domains configured for a specific scope
143
+ *
144
+ * @param scopeType - The type of scope (e.g., "organization", "team", "project")
145
+ * @param scope - The scope identifier (customer's group ID)
146
+ * @returns Autojoin domains and associated invitation
147
+ *
148
+ * @example
149
+ * ```typescript
150
+ * const result = await vortex.getAutojoinDomains('organization', 'acme-org');
151
+ * console.log(result.autojoinDomains); // [{ id: '...', domain: 'acme.com' }]
152
+ * ```
153
+ */
154
+ getAutojoinDomains(scopeType: string, scope: string): Promise<AutojoinDomainsResponse>;
155
+ /**
156
+ * Configure autojoin domains for a specific scope
157
+ *
158
+ * This endpoint syncs autojoin domains - it will add new domains, remove domains
159
+ * not in the provided list, and deactivate the autojoin invitation if all domains
160
+ * are removed (empty array).
161
+ *
162
+ * @param params - Configuration parameters
163
+ * @param params.scope - The scope identifier (customer's group ID)
164
+ * @param params.scopeType - The type of scope (e.g., "organization", "team")
165
+ * @param params.scopeName - Optional display name for the scope
166
+ * @param params.domains - Array of domains to configure for autojoin
167
+ * @param params.widgetId - The widget configuration ID
168
+ * @returns Updated autojoin domains and associated invitation
169
+ *
170
+ * @example
171
+ * ```typescript
172
+ * const result = await vortex.configureAutojoin({
173
+ * scope: 'acme-org',
174
+ * scopeType: 'organization',
175
+ * scopeName: 'Acme Corporation',
176
+ * domains: ['acme.com', 'acme.org'],
177
+ * widgetId: 'widget-123',
178
+ * });
179
+ * ```
180
+ */
181
+ configureAutojoin(params: ConfigureAutojoinRequest): Promise<AutojoinDomainsResponse>;
114
182
  }
115
183
 
116
- export { type AcceptInvitationRequest, type ApiRequestBody, type ApiResponseJson, type GroupInput, type InvitationAcceptance, type InvitationGroup, type InvitationResult, type InvitationTarget, type User, Vortex };
184
+ export { type AcceptInvitationRequest, type ApiRequestBody, type ApiResponseJson, type AutojoinDomain, type AutojoinDomainsResponse, type ConfigureAutojoinRequest, type GroupInput, type InvitationAcceptance, type InvitationGroup, type InvitationResult, type InvitationTarget, type User, Vortex };
package/dist/index.js CHANGED
@@ -54,7 +54,7 @@ var Vortex = class {
54
54
  * user: {
55
55
  * id: "user-123",
56
56
  * email: "user@example.com",
57
- * adminScopes: ['autoJoin']
57
+ * adminScopes: ['autojoin']
58
58
  * }
59
59
  * });
60
60
  * ```
@@ -80,10 +80,16 @@ var Vortex = class {
80
80
  const payload = {
81
81
  userId: user.id,
82
82
  userEmail: user.email,
83
- expires
83
+ expires,
84
+ // Include identifiers array for widget compatibility (VrtxAutojoin checks this)
85
+ identifiers: user.email ? [{ type: "email", value: user.email }] : []
84
86
  };
85
87
  if (user.adminScopes) {
86
88
  payload.adminScopes = user.adminScopes;
89
+ if (user.adminScopes.includes("autojoin")) {
90
+ payload.userIsAutojoinAdmin = true;
91
+ payload.role = "admin";
92
+ }
87
93
  }
88
94
  if (rest && Object.keys(rest).length > 0) {
89
95
  Object.assign(payload, rest);
@@ -99,7 +105,9 @@ var Vortex = class {
99
105
  }
100
106
  async vortexApiRequest(options) {
101
107
  const { method, path, body, queryParams } = options;
102
- const url = new URL(`${process.env.VORTEX_API_BASE_URL || "https://api.vortexsoftware.com"}${path}`);
108
+ const url = new URL(
109
+ `${process.env.VORTEX_API_BASE_URL || "https://api.vortexsoftware.com"}${path}`
110
+ );
103
111
  if (queryParams) {
104
112
  Object.entries(queryParams).forEach(([key, value]) => {
105
113
  url.searchParams.append(key, String(value));
@@ -115,11 +123,13 @@ var Vortex = class {
115
123
  });
116
124
  if (!results.ok) {
117
125
  const errorBody = await results.text();
118
- throw new Error(`Vortex API request failed: ${results.status} ${results.statusText} - ${errorBody}`);
126
+ throw new Error(
127
+ `Vortex API request failed: ${results.status} ${results.statusText} - ${errorBody}`
128
+ );
119
129
  }
120
130
  const contentLength = results.headers.get("content-length");
121
131
  const contentType = results.headers.get("content-type");
122
- if (contentLength === "0" || !(contentType == null ? void 0 : contentType.includes("application/json")) && !contentLength) {
132
+ if (contentLength === "0" || !contentType?.includes("application/json") && !contentLength) {
123
133
  return {};
124
134
  }
125
135
  const responseText = await results.text();
@@ -135,7 +145,7 @@ var Vortex = class {
135
145
  async getInvitationsByTarget(targetType, targetValue) {
136
146
  const response = await this.vortexApiRequest({
137
147
  method: "GET",
138
- path: "/api/v1/invitations?targetType",
148
+ path: "/api/v1/invitations",
139
149
  queryParams: {
140
150
  targetType,
141
151
  targetValue
@@ -185,6 +195,58 @@ var Vortex = class {
185
195
  path: `/api/v1/invitations/${invitationId}/reinvite`
186
196
  });
187
197
  }
198
+ /**
199
+ * Get autojoin domains configured for a specific scope
200
+ *
201
+ * @param scopeType - The type of scope (e.g., "organization", "team", "project")
202
+ * @param scope - The scope identifier (customer's group ID)
203
+ * @returns Autojoin domains and associated invitation
204
+ *
205
+ * @example
206
+ * ```typescript
207
+ * const result = await vortex.getAutojoinDomains('organization', 'acme-org');
208
+ * console.log(result.autojoinDomains); // [{ id: '...', domain: 'acme.com' }]
209
+ * ```
210
+ */
211
+ async getAutojoinDomains(scopeType, scope) {
212
+ return this.vortexApiRequest({
213
+ method: "GET",
214
+ path: `/api/v1/invitations/by-scope/${encodeURIComponent(scopeType)}/${encodeURIComponent(scope)}/autojoin`
215
+ });
216
+ }
217
+ /**
218
+ * Configure autojoin domains for a specific scope
219
+ *
220
+ * This endpoint syncs autojoin domains - it will add new domains, remove domains
221
+ * not in the provided list, and deactivate the autojoin invitation if all domains
222
+ * are removed (empty array).
223
+ *
224
+ * @param params - Configuration parameters
225
+ * @param params.scope - The scope identifier (customer's group ID)
226
+ * @param params.scopeType - The type of scope (e.g., "organization", "team")
227
+ * @param params.scopeName - Optional display name for the scope
228
+ * @param params.domains - Array of domains to configure for autojoin
229
+ * @param params.widgetId - The widget configuration ID
230
+ * @returns Updated autojoin domains and associated invitation
231
+ *
232
+ * @example
233
+ * ```typescript
234
+ * const result = await vortex.configureAutojoin({
235
+ * scope: 'acme-org',
236
+ * scopeType: 'organization',
237
+ * scopeName: 'Acme Corporation',
238
+ * domains: ['acme.com', 'acme.org'],
239
+ * widgetId: 'widget-123',
240
+ * });
241
+ * ```
242
+ */
243
+ async configureAutojoin(params) {
244
+ return this.vortexApiRequest({
245
+ method: "POST",
246
+ path: "/api/v1/invitations/autojoin",
247
+ body: params
248
+ });
249
+ }
188
250
  };
189
251
  // Annotate the CommonJS export names for ESM import in node:
190
252
  0 && (module.exports = {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/vortex.ts"],"sourcesContent":["export * from './vortex';\nexport * from './types';","import crypto from 'node:crypto';\nimport { stringify as uuidStringify } from 'uuid';\nimport { ApiRequestBody, ApiResponseJson, InvitationResult, AcceptInvitationRequest, User } from './types';\n\nexport class Vortex {\n constructor(private apiKey: string) { }\n\n /**\n * Generate a JWT token for a user\n *\n * @param params - Object containing user and optional additional properties\n * @param params.user - User object with id, email, and optional adminScopes\n * @returns JWT token string\n *\n * @example\n * ```typescript\n * const token = vortex.generateJwt({\n * user: {\n * id: \"user-123\",\n * email: \"user@example.com\",\n * adminScopes: ['autoJoin']\n * }\n * });\n * ```\n */\n generateJwt(params: { user: User; [key: string]: any }): string {\n const { user, ...rest } = params;\n const [prefix, encodedId, key] = this.apiKey.split('.'); // prefix is just VRTX\n if (!prefix || !encodedId || !key) {\n throw new Error('Invalid API key format');\n }\n if (prefix !== 'VRTX') {\n throw new Error('Invalid API key prefix');\n }\n const id = uuidStringify(Buffer.from(encodedId, 'base64url'));\n\n const expires = Math.floor(Date.now() / 1000) + 3600;\n\n // ๐Ÿ” Step 1: Derive signing key from API key + ID\n const signingKey = crypto.createHmac('sha256', key).update(id).digest(); // <- raw Buffer\n\n // ๐Ÿงฑ Step 2: Build header + payload\n const header = {\n iat: Math.floor(Date.now() / 1000),\n alg: 'HS256',\n typ: 'JWT',\n kid: id,\n };\n\n // Build payload with user data\n const payload: any = {\n userId: user.id,\n userEmail: user.email,\n expires,\n };\n\n // Add adminScopes if present\n if (user.adminScopes) {\n payload.adminScopes = user.adminScopes;\n }\n\n // Add any additional properties from rest\n if (rest && Object.keys(rest).length > 0) {\n Object.assign(payload, rest);\n }\n\n // ๐Ÿงฑ Step 3: Base64URL encode\n const headerB64 = Buffer.from(JSON.stringify(header)).toString('base64url');\n const payloadB64 = Buffer.from(JSON.stringify(payload)).toString('base64url');\n\n // ๐Ÿงพ Step 4: Sign\n const toSign = `${headerB64}.${payloadB64}`;\n const signature = Buffer.from(\n crypto.createHmac('sha256', signingKey).update(toSign).digest()\n ).toString('base64url');\n const jwt = `${toSign}.${signature}`;\n return jwt;\n }\n\n async vortexApiRequest(options: {\n method: 'GET' | 'POST' | 'PUT' | 'DELETE',\n path: string,\n body?: ApiRequestBody,\n queryParams?: Record<string, string | number | boolean>,\n }): Promise<ApiResponseJson> {\n const { method, path, body, queryParams } = options;\n const url = new URL(`${process.env.VORTEX_API_BASE_URL || 'https://api.vortexsoftware.com'}${path}`);\n if (queryParams) {\n Object.entries(queryParams).forEach(([key, value]) => {\n url.searchParams.append(key, String(value));\n });\n }\n const results = await fetch(url.toString(), {\n method,\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': this.apiKey,\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n if (!results.ok) {\n const errorBody = await results.text();\n throw new Error(`Vortex API request failed: ${results.status} ${results.statusText} - ${errorBody}`);\n }\n\n // Check if response has content to parse\n const contentLength = results.headers.get('content-length');\n const contentType = results.headers.get('content-type');\n\n // If no content or content-length is 0, return empty object\n if (contentLength === '0' || (!contentType?.includes('application/json') && !contentLength)) {\n return {};\n }\n\n // Try to get text first to check if there's actually content\n const responseText = await results.text();\n if (!responseText.trim()) {\n return {};\n }\n\n // Parse JSON if there's content\n try {\n return JSON.parse(responseText);\n } catch (error) {\n // If JSON parsing fails, return the text or empty object\n return {};\n }\n }\n\n async getInvitationsByTarget(targetType: 'email' | 'username' | 'phoneNumber', targetValue: string): Promise<InvitationResult[]> {\n const response = await this.vortexApiRequest({\n method: 'GET',\n path: '/api/v1/invitations?targetType',\n queryParams: {\n targetType,\n targetValue,\n }\n }) as { invitations: InvitationResult[] };\n return response.invitations;\n }\n\n async getInvitation(invitationId: string): Promise<InvitationResult> {\n return this.vortexApiRequest({\n method: 'GET',\n path: `/api/v1/invitations/${invitationId}`,\n }) as Promise<InvitationResult>;\n }\n\n async revokeInvitation(invitationId: string): Promise<{}> {\n return this.vortexApiRequest({\n method: 'DELETE',\n path: `/api/v1/invitations/${invitationId}`,\n }) as Promise<{}>;\n }\n\n async acceptInvitations(\n invitationIds: string[],\n target: { type: 'email' | 'username' | 'phoneNumber'; value: string }\n ): Promise<InvitationResult> {\n const response = await this.vortexApiRequest({\n method: 'POST',\n body: {\n invitationIds,\n target,\n } as AcceptInvitationRequest,\n path: `/api/v1/invitations/accept`,\n }) as InvitationResult;\n return response;\n }\n\n async deleteInvitationsByGroup(groupType: string, groupId: string): Promise<{}> {\n return this.vortexApiRequest({\n method: 'DELETE',\n path: `/api/v1/invitations/by-group/${groupType}/${groupId}`,\n }) as Promise<{}>;\n }\n\n async getInvitationsByGroup(groupType: string, groupId: string): Promise<InvitationResult[]> {\n const response = await this.vortexApiRequest({\n method: 'GET',\n path: `/api/v1/invitations/by-group/${groupType}/${groupId}`,\n }) as { invitations: InvitationResult[] };\n return response.invitations;\n }\n\n async reinvite(invitationId: string): Promise<InvitationResult> {\n return this.vortexApiRequest({\n method: 'POST',\n path: `/api/v1/invitations/${invitationId}/reinvite`,\n }) as Promise<InvitationResult>;\n }\n}"],"mappings":"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","names":["uuidStringify","crypto"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/vortex.ts"],"sourcesContent":["export * from './vortex';\nexport * from './types';","import crypto from 'node:crypto';\nimport { stringify as uuidStringify } from 'uuid';\nimport {\n ApiRequestBody,\n ApiResponseJson,\n InvitationResult,\n AcceptInvitationRequest,\n User,\n AutojoinDomainsResponse,\n ConfigureAutojoinRequest,\n} from './types';\n\nexport class Vortex {\n constructor(private apiKey: string) {}\n\n /**\n * Generate a JWT token for a user\n *\n * @param params - Object containing user and optional additional properties\n * @param params.user - User object with id, email, and optional adminScopes\n * @returns JWT token string\n *\n * @example\n * ```typescript\n * const token = vortex.generateJwt({\n * user: {\n * id: \"user-123\",\n * email: \"user@example.com\",\n * adminScopes: ['autojoin']\n * }\n * });\n * ```\n */\n generateJwt(params: { user: User; [key: string]: any }): string {\n const { user, ...rest } = params;\n const [prefix, encodedId, key] = this.apiKey.split('.'); // prefix is just VRTX\n if (!prefix || !encodedId || !key) {\n throw new Error('Invalid API key format');\n }\n if (prefix !== 'VRTX') {\n throw new Error('Invalid API key prefix');\n }\n const id = uuidStringify(Buffer.from(encodedId, 'base64url'));\n\n const expires = Math.floor(Date.now() / 1000) + 3600;\n\n // ๐Ÿ” Step 1: Derive signing key from API key + ID\n const signingKey = crypto.createHmac('sha256', key).update(id).digest(); // <- raw Buffer\n\n // ๐Ÿงฑ Step 2: Build header + payload\n const header = {\n iat: Math.floor(Date.now() / 1000),\n alg: 'HS256',\n typ: 'JWT',\n kid: id,\n };\n\n // Build payload with user data\n const payload: any = {\n userId: user.id,\n userEmail: user.email,\n expires,\n // Include identifiers array for widget compatibility (VrtxAutojoin checks this)\n identifiers: user.email ? [{ type: 'email', value: user.email }] : [],\n };\n\n // Add adminScopes if present\n if (user.adminScopes) {\n payload.adminScopes = user.adminScopes;\n // Add widget compatibility fields for autojoin admin\n if (user.adminScopes.includes('autojoin')) {\n payload.userIsAutojoinAdmin = true;\n payload.role = 'admin'; // VrtxAutojoin checks parsedJwt.role === 'admin'\n }\n }\n\n // Add any additional properties from rest\n if (rest && Object.keys(rest).length > 0) {\n Object.assign(payload, rest);\n }\n\n // ๐Ÿงฑ Step 3: Base64URL encode\n const headerB64 = Buffer.from(JSON.stringify(header)).toString('base64url');\n const payloadB64 = Buffer.from(JSON.stringify(payload)).toString('base64url');\n\n // ๐Ÿงพ Step 4: Sign\n const toSign = `${headerB64}.${payloadB64}`;\n const signature = Buffer.from(\n crypto.createHmac('sha256', signingKey).update(toSign).digest()\n ).toString('base64url');\n const jwt = `${toSign}.${signature}`;\n return jwt;\n }\n\n async vortexApiRequest(options: {\n method: 'GET' | 'POST' | 'PUT' | 'DELETE';\n path: string;\n body?: ApiRequestBody;\n queryParams?: Record<string, string | number | boolean>;\n }): Promise<ApiResponseJson> {\n const { method, path, body, queryParams } = options;\n const url = new URL(\n `${process.env.VORTEX_API_BASE_URL || 'https://api.vortexsoftware.com'}${path}`\n );\n if (queryParams) {\n Object.entries(queryParams).forEach(([key, value]) => {\n url.searchParams.append(key, String(value));\n });\n }\n const results = await fetch(url.toString(), {\n method,\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': this.apiKey,\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n if (!results.ok) {\n const errorBody = await results.text();\n throw new Error(\n `Vortex API request failed: ${results.status} ${results.statusText} - ${errorBody}`\n );\n }\n\n // Check if response has content to parse\n const contentLength = results.headers.get('content-length');\n const contentType = results.headers.get('content-type');\n\n // If no content or content-length is 0, return empty object\n if (contentLength === '0' || (!contentType?.includes('application/json') && !contentLength)) {\n return {};\n }\n\n // Try to get text first to check if there's actually content\n const responseText = await results.text();\n if (!responseText.trim()) {\n return {};\n }\n\n // Parse JSON if there's content\n try {\n return JSON.parse(responseText);\n } catch (error) {\n // If JSON parsing fails, return the text or empty object\n return {};\n }\n }\n\n async getInvitationsByTarget(\n targetType: 'email' | 'username' | 'phoneNumber',\n targetValue: string\n ): Promise<InvitationResult[]> {\n const response = (await this.vortexApiRequest({\n method: 'GET',\n path: '/api/v1/invitations',\n queryParams: {\n targetType,\n targetValue,\n },\n })) as { invitations: InvitationResult[] };\n return response.invitations;\n }\n\n async getInvitation(invitationId: string): Promise<InvitationResult> {\n return this.vortexApiRequest({\n method: 'GET',\n path: `/api/v1/invitations/${invitationId}`,\n }) as Promise<InvitationResult>;\n }\n\n async revokeInvitation(invitationId: string): Promise<{}> {\n return this.vortexApiRequest({\n method: 'DELETE',\n path: `/api/v1/invitations/${invitationId}`,\n }) as Promise<{}>;\n }\n\n async acceptInvitations(\n invitationIds: string[],\n target: { type: 'email' | 'username' | 'phoneNumber'; value: string }\n ): Promise<InvitationResult> {\n const response = (await this.vortexApiRequest({\n method: 'POST',\n body: {\n invitationIds,\n target,\n } as AcceptInvitationRequest,\n path: `/api/v1/invitations/accept`,\n })) as InvitationResult;\n return response;\n }\n\n async deleteInvitationsByGroup(groupType: string, groupId: string): Promise<{}> {\n return this.vortexApiRequest({\n method: 'DELETE',\n path: `/api/v1/invitations/by-group/${groupType}/${groupId}`,\n }) as Promise<{}>;\n }\n\n async getInvitationsByGroup(groupType: string, groupId: string): Promise<InvitationResult[]> {\n const response = (await this.vortexApiRequest({\n method: 'GET',\n path: `/api/v1/invitations/by-group/${groupType}/${groupId}`,\n })) as { invitations: InvitationResult[] };\n return response.invitations;\n }\n\n async reinvite(invitationId: string): Promise<InvitationResult> {\n return this.vortexApiRequest({\n method: 'POST',\n path: `/api/v1/invitations/${invitationId}/reinvite`,\n }) as Promise<InvitationResult>;\n }\n\n /**\n * Get autojoin domains configured for a specific scope\n *\n * @param scopeType - The type of scope (e.g., \"organization\", \"team\", \"project\")\n * @param scope - The scope identifier (customer's group ID)\n * @returns Autojoin domains and associated invitation\n *\n * @example\n * ```typescript\n * const result = await vortex.getAutojoinDomains('organization', 'acme-org');\n * console.log(result.autojoinDomains); // [{ id: '...', domain: 'acme.com' }]\n * ```\n */\n async getAutojoinDomains(scopeType: string, scope: string): Promise<AutojoinDomainsResponse> {\n return this.vortexApiRequest({\n method: 'GET',\n path: `/api/v1/invitations/by-scope/${encodeURIComponent(scopeType)}/${encodeURIComponent(scope)}/autojoin`,\n }) as Promise<AutojoinDomainsResponse>;\n }\n\n /**\n * Configure autojoin domains for a specific scope\n *\n * This endpoint syncs autojoin domains - it will add new domains, remove domains\n * not in the provided list, and deactivate the autojoin invitation if all domains\n * are removed (empty array).\n *\n * @param params - Configuration parameters\n * @param params.scope - The scope identifier (customer's group ID)\n * @param params.scopeType - The type of scope (e.g., \"organization\", \"team\")\n * @param params.scopeName - Optional display name for the scope\n * @param params.domains - Array of domains to configure for autojoin\n * @param params.widgetId - The widget configuration ID\n * @returns Updated autojoin domains and associated invitation\n *\n * @example\n * ```typescript\n * const result = await vortex.configureAutojoin({\n * scope: 'acme-org',\n * scopeType: 'organization',\n * scopeName: 'Acme Corporation',\n * domains: ['acme.com', 'acme.org'],\n * widgetId: 'widget-123',\n * });\n * ```\n */\n async configureAutojoin(params: ConfigureAutojoinRequest): Promise<AutojoinDomainsResponse> {\n return this.vortexApiRequest({\n method: 'POST',\n path: '/api/v1/invitations/autojoin',\n body: params as unknown as ApiRequestBody,\n }) as Promise<AutojoinDomainsResponse>;\n }\n}\n"],"mappings":"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","names":["uuidStringify","crypto"]}
package/dist/index.mjs CHANGED
@@ -18,7 +18,7 @@ var Vortex = class {
18
18
  * user: {
19
19
  * id: "user-123",
20
20
  * email: "user@example.com",
21
- * adminScopes: ['autoJoin']
21
+ * adminScopes: ['autojoin']
22
22
  * }
23
23
  * });
24
24
  * ```
@@ -44,10 +44,16 @@ var Vortex = class {
44
44
  const payload = {
45
45
  userId: user.id,
46
46
  userEmail: user.email,
47
- expires
47
+ expires,
48
+ // Include identifiers array for widget compatibility (VrtxAutojoin checks this)
49
+ identifiers: user.email ? [{ type: "email", value: user.email }] : []
48
50
  };
49
51
  if (user.adminScopes) {
50
52
  payload.adminScopes = user.adminScopes;
53
+ if (user.adminScopes.includes("autojoin")) {
54
+ payload.userIsAutojoinAdmin = true;
55
+ payload.role = "admin";
56
+ }
51
57
  }
52
58
  if (rest && Object.keys(rest).length > 0) {
53
59
  Object.assign(payload, rest);
@@ -63,7 +69,9 @@ var Vortex = class {
63
69
  }
64
70
  async vortexApiRequest(options) {
65
71
  const { method, path, body, queryParams } = options;
66
- const url = new URL(`${process.env.VORTEX_API_BASE_URL || "https://api.vortexsoftware.com"}${path}`);
72
+ const url = new URL(
73
+ `${process.env.VORTEX_API_BASE_URL || "https://api.vortexsoftware.com"}${path}`
74
+ );
67
75
  if (queryParams) {
68
76
  Object.entries(queryParams).forEach(([key, value]) => {
69
77
  url.searchParams.append(key, String(value));
@@ -79,11 +87,13 @@ var Vortex = class {
79
87
  });
80
88
  if (!results.ok) {
81
89
  const errorBody = await results.text();
82
- throw new Error(`Vortex API request failed: ${results.status} ${results.statusText} - ${errorBody}`);
90
+ throw new Error(
91
+ `Vortex API request failed: ${results.status} ${results.statusText} - ${errorBody}`
92
+ );
83
93
  }
84
94
  const contentLength = results.headers.get("content-length");
85
95
  const contentType = results.headers.get("content-type");
86
- if (contentLength === "0" || !(contentType == null ? void 0 : contentType.includes("application/json")) && !contentLength) {
96
+ if (contentLength === "0" || !contentType?.includes("application/json") && !contentLength) {
87
97
  return {};
88
98
  }
89
99
  const responseText = await results.text();
@@ -99,7 +109,7 @@ var Vortex = class {
99
109
  async getInvitationsByTarget(targetType, targetValue) {
100
110
  const response = await this.vortexApiRequest({
101
111
  method: "GET",
102
- path: "/api/v1/invitations?targetType",
112
+ path: "/api/v1/invitations",
103
113
  queryParams: {
104
114
  targetType,
105
115
  targetValue
@@ -149,6 +159,58 @@ var Vortex = class {
149
159
  path: `/api/v1/invitations/${invitationId}/reinvite`
150
160
  });
151
161
  }
162
+ /**
163
+ * Get autojoin domains configured for a specific scope
164
+ *
165
+ * @param scopeType - The type of scope (e.g., "organization", "team", "project")
166
+ * @param scope - The scope identifier (customer's group ID)
167
+ * @returns Autojoin domains and associated invitation
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * const result = await vortex.getAutojoinDomains('organization', 'acme-org');
172
+ * console.log(result.autojoinDomains); // [{ id: '...', domain: 'acme.com' }]
173
+ * ```
174
+ */
175
+ async getAutojoinDomains(scopeType, scope) {
176
+ return this.vortexApiRequest({
177
+ method: "GET",
178
+ path: `/api/v1/invitations/by-scope/${encodeURIComponent(scopeType)}/${encodeURIComponent(scope)}/autojoin`
179
+ });
180
+ }
181
+ /**
182
+ * Configure autojoin domains for a specific scope
183
+ *
184
+ * This endpoint syncs autojoin domains - it will add new domains, remove domains
185
+ * not in the provided list, and deactivate the autojoin invitation if all domains
186
+ * are removed (empty array).
187
+ *
188
+ * @param params - Configuration parameters
189
+ * @param params.scope - The scope identifier (customer's group ID)
190
+ * @param params.scopeType - The type of scope (e.g., "organization", "team")
191
+ * @param params.scopeName - Optional display name for the scope
192
+ * @param params.domains - Array of domains to configure for autojoin
193
+ * @param params.widgetId - The widget configuration ID
194
+ * @returns Updated autojoin domains and associated invitation
195
+ *
196
+ * @example
197
+ * ```typescript
198
+ * const result = await vortex.configureAutojoin({
199
+ * scope: 'acme-org',
200
+ * scopeType: 'organization',
201
+ * scopeName: 'Acme Corporation',
202
+ * domains: ['acme.com', 'acme.org'],
203
+ * widgetId: 'widget-123',
204
+ * });
205
+ * ```
206
+ */
207
+ async configureAutojoin(params) {
208
+ return this.vortexApiRequest({
209
+ method: "POST",
210
+ path: "/api/v1/invitations/autojoin",
211
+ body: params
212
+ });
213
+ }
152
214
  };
153
215
  export {
154
216
  Vortex
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/vortex.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport { stringify as uuidStringify } from 'uuid';\nimport { ApiRequestBody, ApiResponseJson, InvitationResult, AcceptInvitationRequest, User } from './types';\n\nexport class Vortex {\n constructor(private apiKey: string) { }\n\n /**\n * Generate a JWT token for a user\n *\n * @param params - Object containing user and optional additional properties\n * @param params.user - User object with id, email, and optional adminScopes\n * @returns JWT token string\n *\n * @example\n * ```typescript\n * const token = vortex.generateJwt({\n * user: {\n * id: \"user-123\",\n * email: \"user@example.com\",\n * adminScopes: ['autoJoin']\n * }\n * });\n * ```\n */\n generateJwt(params: { user: User; [key: string]: any }): string {\n const { user, ...rest } = params;\n const [prefix, encodedId, key] = this.apiKey.split('.'); // prefix is just VRTX\n if (!prefix || !encodedId || !key) {\n throw new Error('Invalid API key format');\n }\n if (prefix !== 'VRTX') {\n throw new Error('Invalid API key prefix');\n }\n const id = uuidStringify(Buffer.from(encodedId, 'base64url'));\n\n const expires = Math.floor(Date.now() / 1000) + 3600;\n\n // ๐Ÿ” Step 1: Derive signing key from API key + ID\n const signingKey = crypto.createHmac('sha256', key).update(id).digest(); // <- raw Buffer\n\n // ๐Ÿงฑ Step 2: Build header + payload\n const header = {\n iat: Math.floor(Date.now() / 1000),\n alg: 'HS256',\n typ: 'JWT',\n kid: id,\n };\n\n // Build payload with user data\n const payload: any = {\n userId: user.id,\n userEmail: user.email,\n expires,\n };\n\n // Add adminScopes if present\n if (user.adminScopes) {\n payload.adminScopes = user.adminScopes;\n }\n\n // Add any additional properties from rest\n if (rest && Object.keys(rest).length > 0) {\n Object.assign(payload, rest);\n }\n\n // ๐Ÿงฑ Step 3: Base64URL encode\n const headerB64 = Buffer.from(JSON.stringify(header)).toString('base64url');\n const payloadB64 = Buffer.from(JSON.stringify(payload)).toString('base64url');\n\n // ๐Ÿงพ Step 4: Sign\n const toSign = `${headerB64}.${payloadB64}`;\n const signature = Buffer.from(\n crypto.createHmac('sha256', signingKey).update(toSign).digest()\n ).toString('base64url');\n const jwt = `${toSign}.${signature}`;\n return jwt;\n }\n\n async vortexApiRequest(options: {\n method: 'GET' | 'POST' | 'PUT' | 'DELETE',\n path: string,\n body?: ApiRequestBody,\n queryParams?: Record<string, string | number | boolean>,\n }): Promise<ApiResponseJson> {\n const { method, path, body, queryParams } = options;\n const url = new URL(`${process.env.VORTEX_API_BASE_URL || 'https://api.vortexsoftware.com'}${path}`);\n if (queryParams) {\n Object.entries(queryParams).forEach(([key, value]) => {\n url.searchParams.append(key, String(value));\n });\n }\n const results = await fetch(url.toString(), {\n method,\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': this.apiKey,\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n if (!results.ok) {\n const errorBody = await results.text();\n throw new Error(`Vortex API request failed: ${results.status} ${results.statusText} - ${errorBody}`);\n }\n\n // Check if response has content to parse\n const contentLength = results.headers.get('content-length');\n const contentType = results.headers.get('content-type');\n\n // If no content or content-length is 0, return empty object\n if (contentLength === '0' || (!contentType?.includes('application/json') && !contentLength)) {\n return {};\n }\n\n // Try to get text first to check if there's actually content\n const responseText = await results.text();\n if (!responseText.trim()) {\n return {};\n }\n\n // Parse JSON if there's content\n try {\n return JSON.parse(responseText);\n } catch (error) {\n // If JSON parsing fails, return the text or empty object\n return {};\n }\n }\n\n async getInvitationsByTarget(targetType: 'email' | 'username' | 'phoneNumber', targetValue: string): Promise<InvitationResult[]> {\n const response = await this.vortexApiRequest({\n method: 'GET',\n path: '/api/v1/invitations?targetType',\n queryParams: {\n targetType,\n targetValue,\n }\n }) as { invitations: InvitationResult[] };\n return response.invitations;\n }\n\n async getInvitation(invitationId: string): Promise<InvitationResult> {\n return this.vortexApiRequest({\n method: 'GET',\n path: `/api/v1/invitations/${invitationId}`,\n }) as Promise<InvitationResult>;\n }\n\n async revokeInvitation(invitationId: string): Promise<{}> {\n return this.vortexApiRequest({\n method: 'DELETE',\n path: `/api/v1/invitations/${invitationId}`,\n }) as Promise<{}>;\n }\n\n async acceptInvitations(\n invitationIds: string[],\n target: { type: 'email' | 'username' | 'phoneNumber'; value: string }\n ): Promise<InvitationResult> {\n const response = await this.vortexApiRequest({\n method: 'POST',\n body: {\n invitationIds,\n target,\n } as AcceptInvitationRequest,\n path: `/api/v1/invitations/accept`,\n }) as InvitationResult;\n return response;\n }\n\n async deleteInvitationsByGroup(groupType: string, groupId: string): Promise<{}> {\n return this.vortexApiRequest({\n method: 'DELETE',\n path: `/api/v1/invitations/by-group/${groupType}/${groupId}`,\n }) as Promise<{}>;\n }\n\n async getInvitationsByGroup(groupType: string, groupId: string): Promise<InvitationResult[]> {\n const response = await this.vortexApiRequest({\n method: 'GET',\n path: `/api/v1/invitations/by-group/${groupType}/${groupId}`,\n }) as { invitations: InvitationResult[] };\n return response.invitations;\n }\n\n async reinvite(invitationId: string): Promise<InvitationResult> {\n return this.vortexApiRequest({\n method: 'POST',\n path: `/api/v1/invitations/${invitationId}/reinvite`,\n }) as Promise<InvitationResult>;\n }\n}"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/vortex.ts"],"sourcesContent":["import crypto from 'node:crypto';\nimport { stringify as uuidStringify } from 'uuid';\nimport {\n ApiRequestBody,\n ApiResponseJson,\n InvitationResult,\n AcceptInvitationRequest,\n User,\n AutojoinDomainsResponse,\n ConfigureAutojoinRequest,\n} from './types';\n\nexport class Vortex {\n constructor(private apiKey: string) {}\n\n /**\n * Generate a JWT token for a user\n *\n * @param params - Object containing user and optional additional properties\n * @param params.user - User object with id, email, and optional adminScopes\n * @returns JWT token string\n *\n * @example\n * ```typescript\n * const token = vortex.generateJwt({\n * user: {\n * id: \"user-123\",\n * email: \"user@example.com\",\n * adminScopes: ['autojoin']\n * }\n * });\n * ```\n */\n generateJwt(params: { user: User; [key: string]: any }): string {\n const { user, ...rest } = params;\n const [prefix, encodedId, key] = this.apiKey.split('.'); // prefix is just VRTX\n if (!prefix || !encodedId || !key) {\n throw new Error('Invalid API key format');\n }\n if (prefix !== 'VRTX') {\n throw new Error('Invalid API key prefix');\n }\n const id = uuidStringify(Buffer.from(encodedId, 'base64url'));\n\n const expires = Math.floor(Date.now() / 1000) + 3600;\n\n // ๐Ÿ” Step 1: Derive signing key from API key + ID\n const signingKey = crypto.createHmac('sha256', key).update(id).digest(); // <- raw Buffer\n\n // ๐Ÿงฑ Step 2: Build header + payload\n const header = {\n iat: Math.floor(Date.now() / 1000),\n alg: 'HS256',\n typ: 'JWT',\n kid: id,\n };\n\n // Build payload with user data\n const payload: any = {\n userId: user.id,\n userEmail: user.email,\n expires,\n // Include identifiers array for widget compatibility (VrtxAutojoin checks this)\n identifiers: user.email ? [{ type: 'email', value: user.email }] : [],\n };\n\n // Add adminScopes if present\n if (user.adminScopes) {\n payload.adminScopes = user.adminScopes;\n // Add widget compatibility fields for autojoin admin\n if (user.adminScopes.includes('autojoin')) {\n payload.userIsAutojoinAdmin = true;\n payload.role = 'admin'; // VrtxAutojoin checks parsedJwt.role === 'admin'\n }\n }\n\n // Add any additional properties from rest\n if (rest && Object.keys(rest).length > 0) {\n Object.assign(payload, rest);\n }\n\n // ๐Ÿงฑ Step 3: Base64URL encode\n const headerB64 = Buffer.from(JSON.stringify(header)).toString('base64url');\n const payloadB64 = Buffer.from(JSON.stringify(payload)).toString('base64url');\n\n // ๐Ÿงพ Step 4: Sign\n const toSign = `${headerB64}.${payloadB64}`;\n const signature = Buffer.from(\n crypto.createHmac('sha256', signingKey).update(toSign).digest()\n ).toString('base64url');\n const jwt = `${toSign}.${signature}`;\n return jwt;\n }\n\n async vortexApiRequest(options: {\n method: 'GET' | 'POST' | 'PUT' | 'DELETE';\n path: string;\n body?: ApiRequestBody;\n queryParams?: Record<string, string | number | boolean>;\n }): Promise<ApiResponseJson> {\n const { method, path, body, queryParams } = options;\n const url = new URL(\n `${process.env.VORTEX_API_BASE_URL || 'https://api.vortexsoftware.com'}${path}`\n );\n if (queryParams) {\n Object.entries(queryParams).forEach(([key, value]) => {\n url.searchParams.append(key, String(value));\n });\n }\n const results = await fetch(url.toString(), {\n method,\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': this.apiKey,\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n if (!results.ok) {\n const errorBody = await results.text();\n throw new Error(\n `Vortex API request failed: ${results.status} ${results.statusText} - ${errorBody}`\n );\n }\n\n // Check if response has content to parse\n const contentLength = results.headers.get('content-length');\n const contentType = results.headers.get('content-type');\n\n // If no content or content-length is 0, return empty object\n if (contentLength === '0' || (!contentType?.includes('application/json') && !contentLength)) {\n return {};\n }\n\n // Try to get text first to check if there's actually content\n const responseText = await results.text();\n if (!responseText.trim()) {\n return {};\n }\n\n // Parse JSON if there's content\n try {\n return JSON.parse(responseText);\n } catch (error) {\n // If JSON parsing fails, return the text or empty object\n return {};\n }\n }\n\n async getInvitationsByTarget(\n targetType: 'email' | 'username' | 'phoneNumber',\n targetValue: string\n ): Promise<InvitationResult[]> {\n const response = (await this.vortexApiRequest({\n method: 'GET',\n path: '/api/v1/invitations',\n queryParams: {\n targetType,\n targetValue,\n },\n })) as { invitations: InvitationResult[] };\n return response.invitations;\n }\n\n async getInvitation(invitationId: string): Promise<InvitationResult> {\n return this.vortexApiRequest({\n method: 'GET',\n path: `/api/v1/invitations/${invitationId}`,\n }) as Promise<InvitationResult>;\n }\n\n async revokeInvitation(invitationId: string): Promise<{}> {\n return this.vortexApiRequest({\n method: 'DELETE',\n path: `/api/v1/invitations/${invitationId}`,\n }) as Promise<{}>;\n }\n\n async acceptInvitations(\n invitationIds: string[],\n target: { type: 'email' | 'username' | 'phoneNumber'; value: string }\n ): Promise<InvitationResult> {\n const response = (await this.vortexApiRequest({\n method: 'POST',\n body: {\n invitationIds,\n target,\n } as AcceptInvitationRequest,\n path: `/api/v1/invitations/accept`,\n })) as InvitationResult;\n return response;\n }\n\n async deleteInvitationsByGroup(groupType: string, groupId: string): Promise<{}> {\n return this.vortexApiRequest({\n method: 'DELETE',\n path: `/api/v1/invitations/by-group/${groupType}/${groupId}`,\n }) as Promise<{}>;\n }\n\n async getInvitationsByGroup(groupType: string, groupId: string): Promise<InvitationResult[]> {\n const response = (await this.vortexApiRequest({\n method: 'GET',\n path: `/api/v1/invitations/by-group/${groupType}/${groupId}`,\n })) as { invitations: InvitationResult[] };\n return response.invitations;\n }\n\n async reinvite(invitationId: string): Promise<InvitationResult> {\n return this.vortexApiRequest({\n method: 'POST',\n path: `/api/v1/invitations/${invitationId}/reinvite`,\n }) as Promise<InvitationResult>;\n }\n\n /**\n * Get autojoin domains configured for a specific scope\n *\n * @param scopeType - The type of scope (e.g., \"organization\", \"team\", \"project\")\n * @param scope - The scope identifier (customer's group ID)\n * @returns Autojoin domains and associated invitation\n *\n * @example\n * ```typescript\n * const result = await vortex.getAutojoinDomains('organization', 'acme-org');\n * console.log(result.autojoinDomains); // [{ id: '...', domain: 'acme.com' }]\n * ```\n */\n async getAutojoinDomains(scopeType: string, scope: string): Promise<AutojoinDomainsResponse> {\n return this.vortexApiRequest({\n method: 'GET',\n path: `/api/v1/invitations/by-scope/${encodeURIComponent(scopeType)}/${encodeURIComponent(scope)}/autojoin`,\n }) as Promise<AutojoinDomainsResponse>;\n }\n\n /**\n * Configure autojoin domains for a specific scope\n *\n * This endpoint syncs autojoin domains - it will add new domains, remove domains\n * not in the provided list, and deactivate the autojoin invitation if all domains\n * are removed (empty array).\n *\n * @param params - Configuration parameters\n * @param params.scope - The scope identifier (customer's group ID)\n * @param params.scopeType - The type of scope (e.g., \"organization\", \"team\")\n * @param params.scopeName - Optional display name for the scope\n * @param params.domains - Array of domains to configure for autojoin\n * @param params.widgetId - The widget configuration ID\n * @returns Updated autojoin domains and associated invitation\n *\n * @example\n * ```typescript\n * const result = await vortex.configureAutojoin({\n * scope: 'acme-org',\n * scopeType: 'organization',\n * scopeName: 'Acme Corporation',\n * domains: ['acme.com', 'acme.org'],\n * widgetId: 'widget-123',\n * });\n * ```\n */\n async configureAutojoin(params: ConfigureAutojoinRequest): Promise<AutojoinDomainsResponse> {\n return this.vortexApiRequest({\n method: 'POST',\n path: '/api/v1/invitations/autojoin',\n body: params as unknown as ApiRequestBody,\n }) as Promise<AutojoinDomainsResponse>;\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@teamvortexsoftware/vortex-node-22-sdk",
3
3
  "description": "Vortex Node 22 SDK",
4
4
  "author": "@teamvortexsoftware",
5
- "version": "0.0.8",
5
+ "version": "0.1.1",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
8
8
  "types": "./dist/index.d.ts",
@@ -19,14 +19,14 @@
19
19
  "scripts": {
20
20
  "build:stg": "pnpm run distclean && pnpm run build && mkdir -p dist.d/stg && mv dist dist.d/stg && echo && echo 'Build in ./dist.d/stg'",
21
21
  "prepublish:stg": "cp ./LICENSE ./README.md ./dist.d/stg/ && jq '.name = \"@teamvortexsoftware/vortex-node-22-sdk-stg\" | .description = \"Vortex Node 22 SDK (STG)\" | del(.scripts.prepack)' ./package.json > ./dist.d/stg/package.json",
22
- "publish:stg": "pnpm run prepublish:stg && npm publish --userconfig ../../.npmrc --access restricted ./dist.d/stg",
22
+ "publish:stg": "pnpm run prepublish:stg && pnpm publish --access restricted ./dist.d/stg",
23
23
  "build:prod": "pnpm run distclean && pnpm run build && mkdir -p dist.d/prod && mv dist dist.d/prod && echo && echo 'Build in ./dist.d/prod'",
24
24
  "prepublish:prod": "cp ./LICENSE ./README.md ./dist.d/prod/ && jq 'del(.scripts.prepack)' ./package.json > ./dist.d/prod/package.json",
25
- "publish:prod": "pnpm run prepublish:prod && npm publish --userconfig ../../.npmrc --access public ./dist.d/prod",
25
+ "publish:prod": "pnpm run prepublish:prod && pnpm publish --access public ./dist.d/prod",
26
26
  "check-types": "tsc --noEmit",
27
27
  "distclean": "rm -rf ./dist ./dist.d",
28
28
  "clean": "rm -rf ./dist",
29
- "dev": "",
29
+ "dev": "tsup --watch",
30
30
  "test": "jest",
31
31
  "package": "pnpm run build && cd dist/vortex-node-22-sdk && npm pack",
32
32
  "build": "tsup"
@@ -44,16 +44,18 @@
44
44
  "testEnvironment": "node"
45
45
  },
46
46
  "devDependencies": {
47
- "@eslint/js": "^9.24.0",
47
+ "@eslint/js": "catalog:",
48
48
  "@jest/globals": "29.7.0",
49
49
  "@teamvortexsoftware/eslint-config": "workspace:*",
50
50
  "@teamvortexsoftware/typescript-config": "workspace:*",
51
- "eslint": "^9.24.0",
51
+ "@types/node": "catalog:",
52
+ "eslint": "catalog:",
52
53
  "jest": "29.7.0",
53
- "tsup": "^8.5.0",
54
- "typescript-eslint": "^8.30.1"
54
+ "ts-jest": "catalog:",
55
+ "tsup": "catalog:",
56
+ "typescript-eslint": "catalog:"
55
57
  },
56
58
  "dependencies": {
57
- "uuid": "^13.0.0"
59
+ "uuid": "catalog:"
58
60
  }
59
61
  }