@teamvortexsoftware/vortex-node-22-sdk 0.0.3
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/LICENSE +201 -0
- package/README.md +298 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/types.d.ts +46 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/vortex.d.ts +35 -0
- package/dist/vortex.d.ts.map +1 -0
- package/dist/vortex.js +217 -0
- package/package.json +57 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2025 Vortex Software, Inc.
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# Vortex NodeJS SDK
|
|
2
|
+
|
|
3
|
+
This package provides the Vortex Node/Typescript SDK.
|
|
4
|
+
|
|
5
|
+
With this module, you can both generate a JWT for use with the Vortex Widget and make API calls to the Vortex API.
|
|
6
|
+
|
|
7
|
+
## Use Cases and Examples
|
|
8
|
+
|
|
9
|
+
### Install the NodeJS SDK
|
|
10
|
+
|
|
11
|
+
To install the SDK, simply run the following in your NodeJS backend repo:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
cd your-repo
|
|
15
|
+
npm install --save @teamvortexsoftware/vortex-node-22-sdk
|
|
16
|
+
```
|
|
17
|
+
|
|
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
|
+
|
|
20
|
+
Your API key is used to
|
|
21
|
+
- Sign JWTs for use with the Vortex Widget
|
|
22
|
+
- Make API calls against the [Vortex API](https://api.vortexsoftware.com/api)
|
|
23
|
+
|
|
24
|
+
### Generate a JWT for use with the Vortex Widget
|
|
25
|
+
|
|
26
|
+
Let's assume you have an express powered API and a user that is looking to invite others and you have a Vortex widget embedded on a component in your frontend codebase. Your frontend will need to provide a JWT to the Vortex widget which allows Vortex to validate the user making the request.
|
|
27
|
+
|
|
28
|
+
You could populate the JWT in some set of initial data that your application already provides (recommended) or you could create an endpoint specifically to fetch the JWT on demand for use with the widget. For the purposes of this example, we'll create an endpoint to fetch the JWT.
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
const express = require('express');
|
|
32
|
+
const app = express();
|
|
33
|
+
const port = 3000;
|
|
34
|
+
|
|
35
|
+
// This is the id of the user in your system.
|
|
36
|
+
const userId = 'users-id-in-my-system';
|
|
37
|
+
|
|
38
|
+
// These identifiers are associated with users in your system.
|
|
39
|
+
const identifiers = [
|
|
40
|
+
{ type: 'email', value: 'users@emailaddress.com' },
|
|
41
|
+
{ type: 'email', value: 'someother@address.com' },
|
|
42
|
+
{ type: 'sms', value: '18008675309' }
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
// 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
|
|
46
|
+
const groups = [
|
|
47
|
+
{ type: 'workspace', id: 'some-workspace-id', name: 'The greatest workspace...pause...in the world' },
|
|
48
|
+
{ type: 'document', id: 'some-document-id', name: 'Ricky\'s grade 10 word papers' },
|
|
49
|
+
{ type: 'document', id: 'another-document-id', name: 'Sunnyvale bylaws' }
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
// If your product has the concept of user roles (admin, guest, member, etc), provide it here
|
|
53
|
+
const role = 'admin';
|
|
54
|
+
|
|
55
|
+
// Provide your API key however you see fit.
|
|
56
|
+
const vortex = new Vortex(process.env.VORTEX_API_KEY);
|
|
57
|
+
|
|
58
|
+
app.get('/vortex-jwt', (req, res) => {
|
|
59
|
+
res.setHeader('Content-Type', 'application/json');
|
|
60
|
+
res.end(JSON.stringify({ jwt: vortex.generateJwt({
|
|
61
|
+
userId,
|
|
62
|
+
identifiers,
|
|
63
|
+
groups,
|
|
64
|
+
role
|
|
65
|
+
})}));
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
app.listen(port, () => {
|
|
69
|
+
console.log(`Example app listening on port ${port}. Fetch example JWT by hitting /vortex-jwt`)
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Now, you can utilize that JWT endpoint in conjuction with the Vortex widget
|
|
74
|
+
|
|
75
|
+
Here's an example hook:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { useEffect, useState } from "react";
|
|
79
|
+
|
|
80
|
+
export function useVortexJwt() {
|
|
81
|
+
const [data, setData] = useState<any>(null);
|
|
82
|
+
const [loading, setLoading] = useState(true);
|
|
83
|
+
const [error, setError] = useState<Error | null>(null);
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
let cancelled = false;
|
|
87
|
+
|
|
88
|
+
async function fetchJwt() {
|
|
89
|
+
try {
|
|
90
|
+
const res = await fetch("/vortex-jwt");
|
|
91
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
92
|
+
const json = await res.json();
|
|
93
|
+
if (!cancelled) setData(json);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
if (!cancelled) setError(err as Error);
|
|
96
|
+
} finally {
|
|
97
|
+
if (!cancelled) setLoading(false);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
fetchJwt();
|
|
102
|
+
|
|
103
|
+
return () => {
|
|
104
|
+
cancelled = true;
|
|
105
|
+
};
|
|
106
|
+
}, []);
|
|
107
|
+
|
|
108
|
+
return { data, loading, error };
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Now here is that hook in use in conjuction with a Vortex widget:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
function InviteWrapperComponent() {
|
|
116
|
+
const { data, loading, error } = useVortexJwt();
|
|
117
|
+
|
|
118
|
+
if (loading) return <p>Loading...</p>;
|
|
119
|
+
if (error) return <p>Error: {error.message}</p>;
|
|
120
|
+
const widgetId = 'this-id-comes-from-the-widget-configurator';
|
|
121
|
+
const { jwt } = data;
|
|
122
|
+
return (<VortexInvite
|
|
123
|
+
widgetId={widgetId}
|
|
124
|
+
jwt={jwt}
|
|
125
|
+
group={{ id: "workspace", type: "some-workspace-id", name: "The greatest workspace...pause...in the world" }}
|
|
126
|
+
templateVariables={{
|
|
127
|
+
group_name: "The greatest workspace...pause...in the world",
|
|
128
|
+
inviter_name: "James Lahey",
|
|
129
|
+
group_member_count: "23",
|
|
130
|
+
company_name: "Sunnyvale, Inc"
|
|
131
|
+
}}
|
|
132
|
+
/>);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
### Fetch an invitation by ID
|
|
136
|
+
|
|
137
|
+
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
|
|
138
|
+
|
|
139
|
+
Before the user signs up, you may want to display the invitation. To do this, your backend will need to fetch it from our API.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
app.get('/invite/landing', async (req, res) => {
|
|
143
|
+
const invitationId = req.query?.invitationId;
|
|
144
|
+
if (!invitationId) {
|
|
145
|
+
// gracefully handle this situation
|
|
146
|
+
return res.status(400).send('Invitation ID required');
|
|
147
|
+
}
|
|
148
|
+
const invitation = await vortex.getInvitation(invitationId);
|
|
149
|
+
|
|
150
|
+
if (!invitation) {
|
|
151
|
+
return res.status(404).send('Not found');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// you probably want to do something with the invitation at this point.
|
|
155
|
+
// For example, you could render the invitation as HTML and ask the user
|
|
156
|
+
// to signup and then accept the invite automatically on join.
|
|
157
|
+
// For the sake of simplicity, we'll simply return the raw JSON
|
|
158
|
+
res.setHeader('Content-Type', 'application/json');
|
|
159
|
+
res.end(JSON.stringify(invitation));
|
|
160
|
+
})
|
|
161
|
+
```
|
|
162
|
+
### View invitations by target (email address for example)
|
|
163
|
+
|
|
164
|
+
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.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
app.get('/invitations/by-email', async (req, res) => {
|
|
168
|
+
const email = req.query?.email;
|
|
169
|
+
if (!email) {
|
|
170
|
+
// gracefully handle this situation
|
|
171
|
+
return res.status(400).send('Email is required');
|
|
172
|
+
}
|
|
173
|
+
const invitations = await vortex.getInvitationsByTarget('email', email);
|
|
174
|
+
|
|
175
|
+
if (!invitations || !invitations.length) {
|
|
176
|
+
return res.status(404).send('Not found');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// you probably want to do something with the invitation at this point.
|
|
180
|
+
// For example, you could render the invitation as HTML and ask the user
|
|
181
|
+
// to signup and then accept the invite automatically on join.
|
|
182
|
+
// For the sake of simplicity, we'll simply return the raw JSON
|
|
183
|
+
res.setHeader('Content-Type', 'application/json');
|
|
184
|
+
res.end(JSON.stringify(invitations));
|
|
185
|
+
})
|
|
186
|
+
```
|
|
187
|
+
### Accept invitations
|
|
188
|
+
|
|
189
|
+
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.
|
|
190
|
+
|
|
191
|
+
Illustrated in the example below, you will see how to accept one or more invitations. For the sake of the example, we'll assume you want to accept all open invitations when a user signs up to your service. The example will leverage the previous example's call to getInvitationsByTarget with the acceptInvitations call.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
app.post('/signup', async (req, res) => {
|
|
195
|
+
const email = req.body.email;
|
|
196
|
+
if (!email) {
|
|
197
|
+
// gracefully handle this situation
|
|
198
|
+
return res.status(400).send('Email is required');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// YOUR signup logic, whatever it may be
|
|
202
|
+
await myApp.doSignupLogic(email);
|
|
203
|
+
|
|
204
|
+
// you may want to do this even if the user is signing up without clicking an invitaiton link
|
|
205
|
+
const invitations = await vortex.getInvitationsByTarget('email', email);
|
|
206
|
+
|
|
207
|
+
// Assume that your application may pass the original invitationId from the
|
|
208
|
+
// landing page registered with the configured widget
|
|
209
|
+
const invitationId = req.body.invitationId;
|
|
210
|
+
|
|
211
|
+
const uniqueInvitationIds = invitations.map((invitation) => invitation.id);
|
|
212
|
+
if (invitationId && uniqueInvitationIds.indexOf(invitationId) === -1) {
|
|
213
|
+
uniqueInvitationIds.push(invitationId);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const acceptedInvitations = await vortex.acceptInvitations(
|
|
217
|
+
uniqueInvitationIds,
|
|
218
|
+
{ type: 'email', value: email }
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
// continue with post-signup activity. perhaps redirect to your logged in landing page
|
|
222
|
+
|
|
223
|
+
res.redirect(302, '/app');
|
|
224
|
+
})
|
|
225
|
+
```
|
|
226
|
+
### Fetch invitations by group
|
|
227
|
+
|
|
228
|
+
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.
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
app.get('/invitations/by-group', async (req, res) => {
|
|
232
|
+
const { groupType, groupId } = req.query;
|
|
233
|
+
if (!groupType || !groupId) {
|
|
234
|
+
// gracefully handle this situation
|
|
235
|
+
return res.status(400).send('Required: groupType and groupId');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const invitations = await vortex.getInvitationsByGroup(groupType, groupId);
|
|
239
|
+
|
|
240
|
+
res.setHeader('Content-Type', 'application/json');
|
|
241
|
+
res.end(JSON.stringify(invitations));
|
|
242
|
+
})
|
|
243
|
+
```
|
|
244
|
+
### Reinvite
|
|
245
|
+
|
|
246
|
+
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.
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
app.post('/invitations/reinvite', async (req, res) => {
|
|
250
|
+
const { invitationId } = req.body;
|
|
251
|
+
if (!invitationId) {
|
|
252
|
+
// gracefully handle this situation
|
|
253
|
+
return res.status(400).send('Required: invitationId');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const invitation = await vortex.reinvite(invitationId);
|
|
257
|
+
|
|
258
|
+
res.setHeader('Content-Type', 'application/json');
|
|
259
|
+
res.end(JSON.stringify(invitation));
|
|
260
|
+
})
|
|
261
|
+
```
|
|
262
|
+
### Revoke invitation
|
|
263
|
+
|
|
264
|
+
In addition to reinvite, you may want to present your users (or perhaps just admins) with the ability to revoke outstanding invitations.
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
app.post('/invitations/revoke', async (req, res) => {
|
|
268
|
+
const { invitationId } = req.body;
|
|
269
|
+
if (!invitationId) {
|
|
270
|
+
// gracefully handle this situation
|
|
271
|
+
return res.status(400).send('Required: invitationId');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
await vortex.revokeInvitation(invitationId);
|
|
275
|
+
|
|
276
|
+
res.setHeader('Content-Type', 'application/json');
|
|
277
|
+
res.end(JSON.stringify({}));
|
|
278
|
+
})
|
|
279
|
+
```
|
|
280
|
+
### Delete invitations by group
|
|
281
|
+
|
|
282
|
+
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.
|
|
283
|
+
|
|
284
|
+
Here is how to clean them up when the workspace is deleted.
|
|
285
|
+
```ts
|
|
286
|
+
app.delete('/workspace/:workspaceId', async (req, res) => {
|
|
287
|
+
const { workspaceId } = req.params;
|
|
288
|
+
if (!workspaceId) {
|
|
289
|
+
// gracefully handle this situation
|
|
290
|
+
return res.status(400).send('Required: workspaceId');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
await vortex.deleteInvitationsByGroup('workspace', workspaceId);
|
|
294
|
+
|
|
295
|
+
res.setHeader('Content-Type', 'application/json');
|
|
296
|
+
res.end(JSON.stringify({}));
|
|
297
|
+
})
|
|
298
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./vortex"), exports);
|
|
18
|
+
__exportStar(require("./types"), exports);
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type InvitationTarget = {
|
|
2
|
+
type: 'email' | 'sms';
|
|
3
|
+
value: string;
|
|
4
|
+
};
|
|
5
|
+
export type InvitationGroup = {
|
|
6
|
+
id: string;
|
|
7
|
+
type: string;
|
|
8
|
+
name: string;
|
|
9
|
+
};
|
|
10
|
+
export type InvitationAcceptance = {
|
|
11
|
+
id: string;
|
|
12
|
+
accountId: string;
|
|
13
|
+
projectId: string;
|
|
14
|
+
acceptedAt: string;
|
|
15
|
+
target: InvitationTarget;
|
|
16
|
+
};
|
|
17
|
+
export type InvitationResult = {
|
|
18
|
+
id: string;
|
|
19
|
+
accountId: string;
|
|
20
|
+
clickThroughs: number;
|
|
21
|
+
configurationAttributes: Record<string, any> | null;
|
|
22
|
+
attributes: Record<string, any> | null;
|
|
23
|
+
createdAt: string;
|
|
24
|
+
deactivated: boolean;
|
|
25
|
+
deliveryCount: number;
|
|
26
|
+
deliveryTypes: ('email' | 'sms' | 'share')[];
|
|
27
|
+
foreignCreatorId: string;
|
|
28
|
+
invitationType: 'single_use' | 'multi_use';
|
|
29
|
+
modifiedAt: string | null;
|
|
30
|
+
status: 'queued' | 'sending' | 'delivered' | 'accepted' | 'shared' | 'unfurled' | 'accepted_elsewhere';
|
|
31
|
+
target: InvitationTarget[];
|
|
32
|
+
views: number;
|
|
33
|
+
widgetConfigurationId: string;
|
|
34
|
+
projectId: string;
|
|
35
|
+
groups: InvitationGroup[];
|
|
36
|
+
accepts: InvitationAcceptance[];
|
|
37
|
+
};
|
|
38
|
+
export type AcceptInvitationRequest = {
|
|
39
|
+
invitationIds: string[];
|
|
40
|
+
target: InvitationTarget;
|
|
41
|
+
};
|
|
42
|
+
export type ApiResponseJson = InvitationResult | {
|
|
43
|
+
invitations: InvitationResult[];
|
|
44
|
+
} | {};
|
|
45
|
+
export type ApiRequestBody = AcceptInvitationRequest | null;
|
|
46
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,OAAO,GAAG,KAAK,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,gBAAgB,CAAC;CAC1B,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;IACpD,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,CAAC,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC,EAAE,CAAC;IAC7C,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,YAAY,GAAG,WAAW,CAAC;IAC3C,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,oBAAoB,CAAC;IACvG,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB,EAAE,MAAM,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,OAAO,EAAE,oBAAoB,EAAE,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,gBAAgB,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,gBAAgB,GAAG;IAAE,WAAW,EAAE,gBAAgB,EAAE,CAAA;CAAE,GAAG,EAAE,CAAC;AAE1F,MAAM,MAAM,cAAc,GAAG,uBAAuB,GAAG,IAAI,CAAC"}
|
package/dist/types.js
ADDED
package/dist/vortex.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ApiRequestBody, ApiResponseJson, InvitationResult } from './types';
|
|
2
|
+
export declare class Vortex {
|
|
3
|
+
private apiKey;
|
|
4
|
+
constructor(apiKey: string);
|
|
5
|
+
generateJwt({ userId, identifiers, groups, role, }: {
|
|
6
|
+
userId: string;
|
|
7
|
+
identifiers: {
|
|
8
|
+
type: 'email' | 'sms';
|
|
9
|
+
value: string;
|
|
10
|
+
}[];
|
|
11
|
+
groups: {
|
|
12
|
+
type: string;
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
}[];
|
|
16
|
+
role?: string;
|
|
17
|
+
}): string;
|
|
18
|
+
vortexApiRequest(options: {
|
|
19
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
20
|
+
path: string;
|
|
21
|
+
body?: ApiRequestBody;
|
|
22
|
+
queryParams?: Record<string, string | number | boolean>;
|
|
23
|
+
}): Promise<ApiResponseJson>;
|
|
24
|
+
getInvitationsByTarget(targetType: 'email' | 'username' | 'phoneNumber', targetValue: string): Promise<InvitationResult[]>;
|
|
25
|
+
getInvitation(invitationId: string): Promise<InvitationResult>;
|
|
26
|
+
revokeInvitation(invitationId: string): Promise<{}>;
|
|
27
|
+
acceptInvitations(invitationIds: string[], target: {
|
|
28
|
+
type: 'email' | 'username' | 'phoneNumber';
|
|
29
|
+
value: string;
|
|
30
|
+
}): Promise<InvitationResult>;
|
|
31
|
+
deleteInvitationsByGroup(groupType: string, groupId: string): Promise<{}>;
|
|
32
|
+
getInvitationsByGroup(groupType: string, groupId: string): Promise<InvitationResult[]>;
|
|
33
|
+
reinvite(invitationId: string): Promise<InvitationResult>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=vortex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vortex.d.ts","sourceRoot":"","sources":["../src/vortex.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,gBAAgB,EAA2B,MAAM,SAAS,CAAC;AAErG,qBAAa,MAAM;IACL,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IAElC,WAAW,CAAC,EACV,MAAM,EACN,WAAW,EACX,MAAM,EACN,IAAI,GACL,EAAE;QACD,MAAM,EAAE,MAAM,CAAC;QACf,WAAW,EAAE;YAAE,IAAI,EAAE,OAAO,GAAG,KAAK,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QACxD,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QACrD,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,GAAG,MAAM;IA4CJ,gBAAgB,CAAC,OAAO,EAAE;QAC9B,MAAM,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;QAC1C,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,cAAc,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;KACzD,GAAG,OAAO,CAAC,eAAe,CAAC;IAuBtB,sBAAsB,CAAC,UAAU,EAAE,OAAO,GAAG,UAAU,GAAG,aAAa,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAY1H,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAO9D,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC;IAOnD,iBAAiB,CACrB,aAAa,EAAE,MAAM,EAAE,EACvB,MAAM,EAAE;QAAE,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,aAAa,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GACpE,OAAO,CAAC,gBAAgB,CAAC;IAYtB,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC;IAOzE,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAQtF,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAMhE"}
|
package/dist/vortex.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
13
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
+
};
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.Vortex = void 0;
|
|
43
|
+
var node_crypto_1 = __importDefault(require("node:crypto"));
|
|
44
|
+
var uuid_1 = require("uuid");
|
|
45
|
+
var Vortex = /** @class */ (function () {
|
|
46
|
+
function Vortex(apiKey) {
|
|
47
|
+
this.apiKey = apiKey;
|
|
48
|
+
}
|
|
49
|
+
Vortex.prototype.generateJwt = function (_a) {
|
|
50
|
+
var userId = _a.userId, identifiers = _a.identifiers, groups = _a.groups, role = _a.role;
|
|
51
|
+
var _b = this.apiKey.split('.'), prefix = _b[0], encodedId = _b[1], key = _b[2]; // prefix is just VRTX
|
|
52
|
+
if (!prefix || !encodedId || !key) {
|
|
53
|
+
throw new Error('Invalid API key format');
|
|
54
|
+
}
|
|
55
|
+
if (prefix !== 'VRTX') {
|
|
56
|
+
throw new Error('Invalid API key prefix');
|
|
57
|
+
}
|
|
58
|
+
var id = (0, uuid_1.stringify)(Buffer.from(encodedId, 'base64url'));
|
|
59
|
+
var expires = Math.floor(Date.now() / 1000) + 3600;
|
|
60
|
+
// 🔐 Step 1: Derive signing key from API key + ID
|
|
61
|
+
var signingKey = node_crypto_1.default.createHmac('sha256', key).update(id).digest(); // <- raw Buffer
|
|
62
|
+
// 🧱 Step 2: Build header + payload
|
|
63
|
+
var header = {
|
|
64
|
+
iat: Math.floor(Date.now() / 1000),
|
|
65
|
+
alg: 'HS256',
|
|
66
|
+
typ: 'JWT',
|
|
67
|
+
kid: id,
|
|
68
|
+
};
|
|
69
|
+
var payload = {
|
|
70
|
+
userId: userId,
|
|
71
|
+
groups: groups,
|
|
72
|
+
role: role,
|
|
73
|
+
expires: expires,
|
|
74
|
+
identifiers: identifiers,
|
|
75
|
+
};
|
|
76
|
+
// 🧱 Step 3: Base64URL encode
|
|
77
|
+
var headerB64 = Buffer.from(JSON.stringify(header)).toString('base64url');
|
|
78
|
+
var payloadB64 = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
79
|
+
// 🧾 Step 4: Sign
|
|
80
|
+
var toSign = "".concat(headerB64, ".").concat(payloadB64);
|
|
81
|
+
var signature = Buffer.from(node_crypto_1.default.createHmac('sha256', signingKey).update(toSign).digest()).toString('base64url');
|
|
82
|
+
var jwt = "".concat(toSign, ".").concat(signature);
|
|
83
|
+
return jwt;
|
|
84
|
+
};
|
|
85
|
+
Vortex.prototype.vortexApiRequest = function (options) {
|
|
86
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
87
|
+
var method, path, body, queryParams, url, results, errorBody;
|
|
88
|
+
return __generator(this, function (_a) {
|
|
89
|
+
switch (_a.label) {
|
|
90
|
+
case 0:
|
|
91
|
+
method = options.method, path = options.path, body = options.body, queryParams = options.queryParams;
|
|
92
|
+
url = new URL("".concat(process.env.VORTEX_API_BASE_URL || 'https://api.vortexsoftware.com').concat(path));
|
|
93
|
+
if (queryParams) {
|
|
94
|
+
Object.entries(queryParams).forEach(function (_a) {
|
|
95
|
+
var key = _a[0], value = _a[1];
|
|
96
|
+
url.searchParams.append(key, String(value));
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
return [4 /*yield*/, fetch(url.toString(), {
|
|
100
|
+
method: method,
|
|
101
|
+
headers: {
|
|
102
|
+
'Content-Type': 'application/json',
|
|
103
|
+
'x-api-key': this.apiKey,
|
|
104
|
+
},
|
|
105
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
106
|
+
})];
|
|
107
|
+
case 1:
|
|
108
|
+
results = _a.sent();
|
|
109
|
+
if (!!results.ok) return [3 /*break*/, 3];
|
|
110
|
+
return [4 /*yield*/, results.text()];
|
|
111
|
+
case 2:
|
|
112
|
+
errorBody = _a.sent();
|
|
113
|
+
throw new Error("Vortex API request failed: ".concat(results.status, " ").concat(results.statusText, " - ").concat(errorBody));
|
|
114
|
+
case 3: return [2 /*return*/, results.json()];
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
Vortex.prototype.getInvitationsByTarget = function (targetType, targetValue) {
|
|
120
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
121
|
+
var response;
|
|
122
|
+
return __generator(this, function (_a) {
|
|
123
|
+
switch (_a.label) {
|
|
124
|
+
case 0: return [4 /*yield*/, this.vortexApiRequest({
|
|
125
|
+
method: 'GET',
|
|
126
|
+
path: '/api/v1/invitations?targetType',
|
|
127
|
+
queryParams: {
|
|
128
|
+
targetType: targetType,
|
|
129
|
+
targetValue: targetValue,
|
|
130
|
+
}
|
|
131
|
+
})];
|
|
132
|
+
case 1:
|
|
133
|
+
response = _a.sent();
|
|
134
|
+
return [2 /*return*/, response.invitations];
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
Vortex.prototype.getInvitation = function (invitationId) {
|
|
140
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
141
|
+
return __generator(this, function (_a) {
|
|
142
|
+
return [2 /*return*/, this.vortexApiRequest({
|
|
143
|
+
method: 'GET',
|
|
144
|
+
path: "/api/v1/invitations/".concat(invitationId),
|
|
145
|
+
})];
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
Vortex.prototype.revokeInvitation = function (invitationId) {
|
|
150
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
151
|
+
return __generator(this, function (_a) {
|
|
152
|
+
return [2 /*return*/, this.vortexApiRequest({
|
|
153
|
+
method: 'DELETE',
|
|
154
|
+
path: "/api/v1/invitations/".concat(invitationId),
|
|
155
|
+
})];
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
Vortex.prototype.acceptInvitations = function (invitationIds, target) {
|
|
160
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
161
|
+
var response;
|
|
162
|
+
return __generator(this, function (_a) {
|
|
163
|
+
switch (_a.label) {
|
|
164
|
+
case 0: return [4 /*yield*/, this.vortexApiRequest({
|
|
165
|
+
method: 'POST',
|
|
166
|
+
body: {
|
|
167
|
+
invitationIds: invitationIds,
|
|
168
|
+
target: target,
|
|
169
|
+
},
|
|
170
|
+
path: "/api/v1/invitations/accept",
|
|
171
|
+
})];
|
|
172
|
+
case 1:
|
|
173
|
+
response = _a.sent();
|
|
174
|
+
return [2 /*return*/, response];
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
Vortex.prototype.deleteInvitationsByGroup = function (groupType, groupId) {
|
|
180
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
181
|
+
return __generator(this, function (_a) {
|
|
182
|
+
return [2 /*return*/, this.vortexApiRequest({
|
|
183
|
+
method: 'DELETE',
|
|
184
|
+
path: "/api/v1/invitations/by-group/".concat(groupType, "/").concat(groupId),
|
|
185
|
+
})];
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
Vortex.prototype.getInvitationsByGroup = function (groupType, groupId) {
|
|
190
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
191
|
+
var response;
|
|
192
|
+
return __generator(this, function (_a) {
|
|
193
|
+
switch (_a.label) {
|
|
194
|
+
case 0: return [4 /*yield*/, this.vortexApiRequest({
|
|
195
|
+
method: 'GET',
|
|
196
|
+
path: "/api/v1/invitations/by-group/".concat(groupType, "/").concat(groupId),
|
|
197
|
+
})];
|
|
198
|
+
case 1:
|
|
199
|
+
response = _a.sent();
|
|
200
|
+
return [2 /*return*/, response.invitations];
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
Vortex.prototype.reinvite = function (invitationId) {
|
|
206
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
207
|
+
return __generator(this, function (_a) {
|
|
208
|
+
return [2 /*return*/, this.vortexApiRequest({
|
|
209
|
+
method: 'POST',
|
|
210
|
+
path: "/api/v1/invitations/".concat(invitationId, "/reinvite"),
|
|
211
|
+
})];
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
return Vortex;
|
|
216
|
+
}());
|
|
217
|
+
exports.Vortex = Vortex;
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@teamvortexsoftware/vortex-node-22-sdk",
|
|
3
|
+
"description": "Vortex Node 22 SDK",
|
|
4
|
+
"author": "@teamvortexsoftware",
|
|
5
|
+
"version": "0.0.3",
|
|
6
|
+
"main": "./dist/cjs/index.js",
|
|
7
|
+
"module": "./dist/esm/index.js",
|
|
8
|
+
"types": "./dist/esm/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/esm/index.js",
|
|
12
|
+
"require": "./dist/cjs/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist/"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"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'",
|
|
20
|
+
"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",
|
|
21
|
+
"publish:stg": "pnpm run prepublish:stg && npm publish --userconfig ../../.npmrc --access restricted ./dist.d/stg",
|
|
22
|
+
"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'",
|
|
23
|
+
"prepublish:prod": "cp ./LICENSE ./README.md ./dist.d/prod/ && jq 'del(.scripts.prepack)' ./package.json > ./dist.d/prod/package.json",
|
|
24
|
+
"publish:prod": "pnpm run prepublish:prod && npm publish --userconfig ../../.npmrc --access public ./dist.d/prod",
|
|
25
|
+
"check-types": "tsc --noEmit",
|
|
26
|
+
"distclean": "rm -rf ./dist ./dist.d",
|
|
27
|
+
"clean": "rm -rf ./dist",
|
|
28
|
+
"dev": "",
|
|
29
|
+
"test": "jest",
|
|
30
|
+
"package": "pnpm run build && cd dist/vortex-node-22-sdk && npm pack",
|
|
31
|
+
"build": "tsc -b tsconfig.json"
|
|
32
|
+
},
|
|
33
|
+
"jest": {
|
|
34
|
+
"rootDir": "__tests__",
|
|
35
|
+
"testRegex": ".*\\.spec\\.ts$",
|
|
36
|
+
"transform": {
|
|
37
|
+
"^.+\\.(t|j)s$": "ts-jest"
|
|
38
|
+
},
|
|
39
|
+
"collectCoverageFrom": [
|
|
40
|
+
"**/*.(t|j)s"
|
|
41
|
+
],
|
|
42
|
+
"coverageDirectory": "coverage",
|
|
43
|
+
"testEnvironment": "node"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@eslint/js": "^9.24.0",
|
|
47
|
+
"@jest/globals": "29.7.0",
|
|
48
|
+
"@teamvortexsoftware/eslint-config": "workspace:*",
|
|
49
|
+
"@teamvortexsoftware/typescript-config": "workspace:*",
|
|
50
|
+
"eslint": "^9.24.0",
|
|
51
|
+
"jest": "29.7.0",
|
|
52
|
+
"typescript-eslint": "^8.30.1"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"uuid": "^13.0.0"
|
|
56
|
+
}
|
|
57
|
+
}
|