@koloseum/utils 0.1.17 → 0.2.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/dist/utils.d.ts +67 -44
- package/dist/utils.js +473 -80
- package/package.json +13 -13
package/dist/utils.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { Database } from "@koloseum/types/database";
|
|
2
|
-
import type { CustomError, Microservice, MicroserviceGroup, MicroserviceObject,
|
|
3
|
-
import type { PronounsCheckboxes, SocialMediaPlatform } from "@koloseum/types/public-auth";
|
|
2
|
+
import type { CustomError, Microservice, MicroserviceGroup, MicroserviceObject, UserWithCustomMetadata } from "@koloseum/types/general";
|
|
3
|
+
import type { IdentityType, PronounsCheckboxes, PronounsItem, Sex, SocialMediaPlatform } from "@koloseum/types/public-auth";
|
|
4
4
|
import type { Page } from "@sveltejs/kit";
|
|
5
|
-
import type { SupabaseClient, PostgrestError } from "@supabase/supabase-js";
|
|
6
|
-
import type { suprsend } from "@suprsend/node-sdk";
|
|
5
|
+
import type { SupabaseClient, FunctionInvokeOptions, PostgrestError } from "@supabase/supabase-js";
|
|
7
6
|
import type { IOptions } from "@suprsend/web-components/dist/types/interface.d.ts";
|
|
8
7
|
import type { MobilePhoneLocale } from "validator";
|
|
9
8
|
export declare const Data: {
|
|
@@ -15,7 +14,7 @@ export declare const Data: {
|
|
|
15
14
|
* @param {string} identityId - A default identity ID; defaults to a random UUID
|
|
16
15
|
* @returns A generic authenticated user.
|
|
17
16
|
*/
|
|
18
|
-
authenticatedUser: (id?: string, date?: Date, phone?: string, identityId?: string) =>
|
|
17
|
+
authenticatedUser: (id?: string, date?: Date, phone?: string, identityId?: string) => UserWithCustomMetadata;
|
|
19
18
|
};
|
|
20
19
|
export declare const Status: {
|
|
21
20
|
/**
|
|
@@ -35,12 +34,12 @@ export declare const Utility: {
|
|
|
35
34
|
/**
|
|
36
35
|
* Calls a Supabase Edge function.
|
|
37
36
|
* @param {boolean} browser - Whether the function is being called in the browser
|
|
38
|
-
* @param {SupabaseClient} supabase - The Supabase client
|
|
37
|
+
* @param {SupabaseClient<Database>} supabase - The Supabase client
|
|
39
38
|
* @param {string} path - The path to the Edge function
|
|
40
|
-
* @param {
|
|
39
|
+
* @param {FunctionInvokeOptions} [options] - The options to use for the function invocation; defaults to `{ method: "GET" }`
|
|
41
40
|
* @returns The response from the Edge function
|
|
42
41
|
*/
|
|
43
|
-
callEdgeFunction: (browser: boolean, supabase: SupabaseClient<Database>, path: string,
|
|
42
|
+
callEdgeFunction: (browser: boolean, supabase: SupabaseClient<Database>, path: string, options?: FunctionInvokeOptions) => Promise<{
|
|
44
43
|
code: number;
|
|
45
44
|
data?: any;
|
|
46
45
|
error?: string;
|
|
@@ -52,6 +51,12 @@ export declare const Utility: {
|
|
|
52
51
|
* @returns The capitalised word
|
|
53
52
|
*/
|
|
54
53
|
capitalise: (word: string) => string;
|
|
54
|
+
/**
|
|
55
|
+
* Cleans a URL by removing the protocol and trailing slashes.
|
|
56
|
+
* @param {string} url - The URL to clean
|
|
57
|
+
* @returns {string} The cleaned URL
|
|
58
|
+
*/
|
|
59
|
+
cleanUrl: (url: string) => string;
|
|
55
60
|
/**
|
|
56
61
|
* Returns a custom error object.
|
|
57
62
|
* @param {number} code - The error code; defaults to `500` if not provided or invalid
|
|
@@ -85,12 +90,34 @@ export declare const Utility: {
|
|
|
85
90
|
*/
|
|
86
91
|
getAge: (dateOfBirth: string) => number;
|
|
87
92
|
/**
|
|
88
|
-
*
|
|
93
|
+
* Formats a date as a locale-specific string.
|
|
94
|
+
* @param {Date | string} date - Date to be formatted
|
|
95
|
+
* @param {boolean} withTime - Specify whether the date should include time; defaults to `false`
|
|
96
|
+
* @param {string} timeZone - The time zone to use for formatting; defaults to `Africa/Nairobi`
|
|
97
|
+
* @returns The formatted date string, or `null` if invalid input
|
|
98
|
+
*/
|
|
99
|
+
getDateString: (date: Date | string | null, withTime?: boolean, timeZone?: string) => string | null;
|
|
100
|
+
/**
|
|
101
|
+
* Formats an identity type for display.
|
|
102
|
+
* @param {IdentityType} identityType - The identity type to be formatted
|
|
103
|
+
* @returns The formatted identity type string
|
|
104
|
+
*/
|
|
105
|
+
getIdentityTypeString: (identityType: IdentityType) => string;
|
|
106
|
+
/**
|
|
107
|
+
* Returns a pronoun given pronouns and a type.
|
|
108
|
+
* @param {PronounsItem | null} pronouns - The pronouns to be formatted
|
|
109
|
+
* @param {"subject" | "object" | "possessive" | "reflexive"} type - The type of pronoun to be returned
|
|
110
|
+
* @param {Sex} sex - The user's sex; defaults to `undefined`
|
|
111
|
+
* @returns The formatted pronoun, or `null` if the input is invalid
|
|
112
|
+
*/
|
|
113
|
+
getPronoun: (pronouns: PronounsItem | null, type: "subject" | "object" | "possessive" | "reflexive", sex?: Sex) => string | null;
|
|
114
|
+
/**
|
|
115
|
+
* Generate a SuprSend notification inbox configuration object for a user.
|
|
89
116
|
* @param {string} userId - The user ID to generate the configuration for.
|
|
90
117
|
* @param {string} publicApiKey - The public API key to use for SuprSend.
|
|
91
|
-
* @returns The SuprSend configuration object.
|
|
118
|
+
* @returns The SuprSend notification inbox configuration object.
|
|
92
119
|
*/
|
|
93
|
-
|
|
120
|
+
getSuprSendInboxConfig: (userId: string, publicApiKey: string) => IOptions;
|
|
94
121
|
/**
|
|
95
122
|
* Handles the click event for pronouns checkboxes.
|
|
96
123
|
* @param {MouseEvent} e - The click event
|
|
@@ -98,17 +125,29 @@ export declare const Utility: {
|
|
|
98
125
|
*/
|
|
99
126
|
handlePronounsCheckboxClick: (e: MouseEvent, checkboxes: PronounsCheckboxes) => void;
|
|
100
127
|
/**
|
|
101
|
-
*
|
|
102
|
-
* -
|
|
103
|
-
*
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
*
|
|
108
|
-
* -
|
|
109
|
-
*
|
|
110
|
-
*/
|
|
111
|
-
|
|
128
|
+
* Checks if a user is authorised to access a microservice.
|
|
129
|
+
* @param {SupabaseClient<Database>} supabase - The Supabase client.
|
|
130
|
+
* @param {UserWithCustomMetadata} user - The user to check.
|
|
131
|
+
* @param {Object} options - The options for the function.
|
|
132
|
+
* @param {MicroserviceGroup} options.microserviceGroup - The microservice group.
|
|
133
|
+
* @param {Microservice<MicroserviceGroup> | string} options.microservice - The microservice.
|
|
134
|
+
* @param {string} options.playersUrl - The URL to the Players microservice.
|
|
135
|
+
* @param {(role: string) => string} options.requestedPermission - A function that returns the requested permission for a given role.
|
|
136
|
+
* @returns {Promise<{ isAuthorised?: boolean; redirect?: { code: number; url: string }; error?: CustomError }>} The result of the function.
|
|
137
|
+
*/
|
|
138
|
+
isUserAuthorised: (supabase: SupabaseClient<Database>, user: UserWithCustomMetadata, options: {
|
|
139
|
+
microserviceGroup: MicroserviceGroup;
|
|
140
|
+
microservice: Microservice<MicroserviceGroup> | string;
|
|
141
|
+
playersUrl: string;
|
|
142
|
+
requestedPermission: (role: string) => string;
|
|
143
|
+
}) => Promise<{
|
|
144
|
+
isAuthorised?: boolean;
|
|
145
|
+
redirect?: {
|
|
146
|
+
code: number;
|
|
147
|
+
url: string;
|
|
148
|
+
};
|
|
149
|
+
error?: CustomError;
|
|
150
|
+
}>;
|
|
112
151
|
/**
|
|
113
152
|
* A reference of microservices available on the platform, represented as an object with each user group (i.e. `public`, `players`, `lounges`, and `backroom`) having its own array of microservices. Each microservice in a list is an object with the following properties:
|
|
114
153
|
* - `name`: The name of the microservice.
|
|
@@ -130,13 +169,13 @@ export declare const Utility: {
|
|
|
130
169
|
* @param base - The base path of the application; defaults to an empty string.
|
|
131
170
|
* @returns `true` if the page is active, `false` otherwise.
|
|
132
171
|
*/
|
|
133
|
-
pageIsActive: (page: Page, slug: string, microservice?: Microservice<MicroserviceGroup>, base?: string) => boolean;
|
|
172
|
+
pageIsActive: (page: Page, slug: string | undefined, microservice?: Microservice<MicroserviceGroup>, base?: string) => boolean;
|
|
134
173
|
/**
|
|
135
174
|
* Parses a `CustomError` object within a page/layout server load function and returns an error to be thrown.
|
|
136
175
|
* @param {CustomError} error - The error object
|
|
137
176
|
* @returns A new `Error` object if the error is unexpected, or a Svelte error otherwise
|
|
138
177
|
*/
|
|
139
|
-
parseLoadError: (error: CustomError) => Error;
|
|
178
|
+
parseLoadError: (error: CustomError) => Error | never;
|
|
140
179
|
/**
|
|
141
180
|
* Parses a `PostgrestError` object and returns a custom error object if any has occurred.
|
|
142
181
|
* @param {PostgrestError | null} postgrestError - The `PostgrestError` object, or `null` if no error occurred
|
|
@@ -155,11 +194,11 @@ export declare const Utility: {
|
|
|
155
194
|
*/
|
|
156
195
|
passwordRegex: RegExp;
|
|
157
196
|
/**
|
|
158
|
-
*
|
|
159
|
-
* -
|
|
160
|
-
*
|
|
197
|
+
* Returns a regular expression for a Koloseum platform ID.
|
|
198
|
+
* @param {string} type - The type of platform ID to return
|
|
199
|
+
* @returns A regular expression for the platform ID, or `null` if the type is invalid
|
|
161
200
|
*/
|
|
162
|
-
|
|
201
|
+
platformIdRegex: (type: "lounge" | "loungeBranch" | "player") => RegExp | null;
|
|
163
202
|
/**
|
|
164
203
|
* Sanitises any potential HTML injected into user input.
|
|
165
204
|
* @param {string} input - The input to be sanitised
|
|
@@ -185,20 +224,4 @@ export declare const Utility: {
|
|
|
185
224
|
* @returns `true` if the handle is valid; `false` otherwise
|
|
186
225
|
*/
|
|
187
226
|
validateSocialMediaHandle: (handle: string) => boolean;
|
|
188
|
-
/**
|
|
189
|
-
* Validates a SuprSend subscriber. If the subscriber does not exist, it will be created with the provided person data.
|
|
190
|
-
* @param {suprsend.Suprsend} suprSend - The SuprSend instance.
|
|
191
|
-
* @param {string} userId - The user ID.
|
|
192
|
-
* @param {Object} personData - The person data; required if the subscriber does not exist.
|
|
193
|
-
* @returns {Promise<{ error?: CustomError; success?: true }>} A promise that resolves to an object with an error if the subscriber validation fails, or indicating success otherwise.
|
|
194
|
-
*/
|
|
195
|
-
validateSuprSendSubscriber: (suprSend: suprsend.Suprsend, userId: string, personData?: {
|
|
196
|
-
firstName: string;
|
|
197
|
-
lastName: string;
|
|
198
|
-
phone: string;
|
|
199
|
-
pseudonym?: string;
|
|
200
|
-
}) => Promise<{
|
|
201
|
-
error?: CustomError;
|
|
202
|
-
success?: true;
|
|
203
|
-
}>;
|
|
204
227
|
};
|
package/dist/utils.js
CHANGED
|
@@ -38,7 +38,13 @@ export const Data = {
|
|
|
38
38
|
user_metadata: {
|
|
39
39
|
email_verified: false,
|
|
40
40
|
phone_verified: false,
|
|
41
|
-
sub: id
|
|
41
|
+
sub: id,
|
|
42
|
+
backroom: {
|
|
43
|
+
welcome_notification_sent: true
|
|
44
|
+
},
|
|
45
|
+
players: {
|
|
46
|
+
welcome_notification_sent: true
|
|
47
|
+
}
|
|
42
48
|
},
|
|
43
49
|
identities: [
|
|
44
50
|
{
|
|
@@ -82,33 +88,27 @@ export const Utility = {
|
|
|
82
88
|
/**
|
|
83
89
|
* Calls a Supabase Edge function.
|
|
84
90
|
* @param {boolean} browser - Whether the function is being called in the browser
|
|
85
|
-
* @param {SupabaseClient} supabase - The Supabase client
|
|
91
|
+
* @param {SupabaseClient<Database>} supabase - The Supabase client
|
|
86
92
|
* @param {string} path - The path to the Edge function
|
|
87
|
-
* @param {
|
|
93
|
+
* @param {FunctionInvokeOptions} [options] - The options to use for the function invocation; defaults to `{ method: "GET" }`
|
|
88
94
|
* @returns The response from the Edge function
|
|
89
95
|
*/
|
|
90
|
-
callEdgeFunction: async (browser, supabase, path,
|
|
96
|
+
callEdgeFunction: async (browser, supabase, path, options = { method: "GET" }) => {
|
|
91
97
|
try {
|
|
92
|
-
// Set up headers with safe origin handling for SSR
|
|
93
|
-
const headers = {
|
|
94
|
-
"Content-Type": "application/json",
|
|
95
|
-
Accept: "application/json"
|
|
96
|
-
};
|
|
97
98
|
// Only add Origin header in browser environment
|
|
98
99
|
if (browser)
|
|
99
|
-
headers
|
|
100
|
+
options.headers = { ...options.headers, Origin: window.location.origin };
|
|
100
101
|
// Fetch response with additional options for better cross-origin handling
|
|
101
|
-
const { data, error } = await supabase.functions.invoke(path,
|
|
102
|
-
method,
|
|
103
|
-
headers
|
|
104
|
-
});
|
|
102
|
+
const { data, error } = await supabase.functions.invoke(path, options);
|
|
105
103
|
// Handle different error types
|
|
106
104
|
if (error) {
|
|
107
105
|
// Define context
|
|
108
106
|
const context = {};
|
|
109
107
|
// Define error code and message
|
|
110
108
|
const code = Number(error.context.status) || 500;
|
|
111
|
-
let message =
|
|
109
|
+
let message = error.context.statusText === "Internal Server Error"
|
|
110
|
+
? Status.ERROR
|
|
111
|
+
: error.context.statusText || Status.ERROR;
|
|
112
112
|
// Handle HTTP errors
|
|
113
113
|
if (error instanceof FunctionsHttpError) {
|
|
114
114
|
// Get error data
|
|
@@ -132,8 +132,8 @@ export const Utility = {
|
|
|
132
132
|
if (path.includes("verify-id") && data.attemptId)
|
|
133
133
|
context.attemptId = data.attemptId;
|
|
134
134
|
}
|
|
135
|
-
catch
|
|
136
|
-
|
|
135
|
+
catch {
|
|
136
|
+
return { code: 500, error: Status.ERROR };
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
// Handle relay and fetch errors
|
|
@@ -145,7 +145,7 @@ export const Utility = {
|
|
|
145
145
|
// Return response
|
|
146
146
|
return { code: 200, data };
|
|
147
147
|
}
|
|
148
|
-
catch
|
|
148
|
+
catch {
|
|
149
149
|
return { code: 500, error: Status.ERROR };
|
|
150
150
|
}
|
|
151
151
|
},
|
|
@@ -160,6 +160,12 @@ export const Utility = {
|
|
|
160
160
|
.split("")
|
|
161
161
|
.map((c) => c.toLowerCase())
|
|
162
162
|
.join(""),
|
|
163
|
+
/**
|
|
164
|
+
* Cleans a URL by removing the protocol and trailing slashes.
|
|
165
|
+
* @param {string} url - The URL to clean
|
|
166
|
+
* @returns {string} The cleaned URL
|
|
167
|
+
*/
|
|
168
|
+
cleanUrl: (url) => url.replace(/^https?:\/\//, "").replace(/\/+$/, ""),
|
|
163
169
|
/**
|
|
164
170
|
* Returns a custom error object.
|
|
165
171
|
* @param {number} code - The error code; defaults to `500` if not provided or invalid
|
|
@@ -201,7 +207,7 @@ export const Utility = {
|
|
|
201
207
|
return formattedDate;
|
|
202
208
|
}
|
|
203
209
|
catch (error) {
|
|
204
|
-
console.
|
|
210
|
+
console.error(error);
|
|
205
211
|
return null;
|
|
206
212
|
}
|
|
207
213
|
},
|
|
@@ -238,12 +244,157 @@ export const Utility = {
|
|
|
238
244
|
return monthDiff < 0 || (monthDiff === 0 && currentDate.getDate() < birthDate.getDate()) ? --age : age;
|
|
239
245
|
},
|
|
240
246
|
/**
|
|
241
|
-
*
|
|
247
|
+
* Formats a date as a locale-specific string.
|
|
248
|
+
* @param {Date | string} date - Date to be formatted
|
|
249
|
+
* @param {boolean} withTime - Specify whether the date should include time; defaults to `false`
|
|
250
|
+
* @param {string} timeZone - The time zone to use for formatting; defaults to `Africa/Nairobi`
|
|
251
|
+
* @returns The formatted date string, or `null` if invalid input
|
|
252
|
+
*/
|
|
253
|
+
getDateString: (date, withTime = false, timeZone = "Africa/Nairobi") => {
|
|
254
|
+
// Return null if no date is provided
|
|
255
|
+
if (!date)
|
|
256
|
+
return null;
|
|
257
|
+
// Validate date to format
|
|
258
|
+
let dateToFormat;
|
|
259
|
+
if (typeof date === "string") {
|
|
260
|
+
if (date === "" || isNaN(Date.parse(date)))
|
|
261
|
+
return null;
|
|
262
|
+
dateToFormat = new Date(date);
|
|
263
|
+
}
|
|
264
|
+
else if (date instanceof Date) {
|
|
265
|
+
if (isNaN(date.getTime()))
|
|
266
|
+
return null;
|
|
267
|
+
dateToFormat = date;
|
|
268
|
+
}
|
|
269
|
+
else
|
|
270
|
+
return null;
|
|
271
|
+
// Format date
|
|
272
|
+
return dateToFormat.toLocaleString("en-KE", {
|
|
273
|
+
timeZone,
|
|
274
|
+
year: "numeric",
|
|
275
|
+
month: "long",
|
|
276
|
+
day: "numeric",
|
|
277
|
+
hour: withTime ? "numeric" : undefined,
|
|
278
|
+
minute: withTime ? "2-digit" : undefined,
|
|
279
|
+
hour12: withTime ? true : undefined
|
|
280
|
+
});
|
|
281
|
+
},
|
|
282
|
+
/**
|
|
283
|
+
* Formats an identity type for display.
|
|
284
|
+
* @param {IdentityType} identityType - The identity type to be formatted
|
|
285
|
+
* @returns The formatted identity type string
|
|
286
|
+
*/
|
|
287
|
+
getIdentityTypeString: (identityType) => {
|
|
288
|
+
if (identityType === "national")
|
|
289
|
+
return "National ID";
|
|
290
|
+
if (identityType === "alien")
|
|
291
|
+
return "Alien ID";
|
|
292
|
+
if (identityType === "passport")
|
|
293
|
+
return "Passport";
|
|
294
|
+
if (identityType === "driver_licence")
|
|
295
|
+
return "Driver's licence";
|
|
296
|
+
return "";
|
|
297
|
+
},
|
|
298
|
+
/**
|
|
299
|
+
* Returns a pronoun given pronouns and a type.
|
|
300
|
+
* @param {PronounsItem | null} pronouns - The pronouns to be formatted
|
|
301
|
+
* @param {"subject" | "object" | "possessive" | "reflexive"} type - The type of pronoun to be returned
|
|
302
|
+
* @param {Sex} sex - The user's sex; defaults to `undefined`
|
|
303
|
+
* @returns The formatted pronoun, or `null` if the input is invalid
|
|
304
|
+
*/
|
|
305
|
+
getPronoun: (pronouns, type, sex) => {
|
|
306
|
+
// Get pronoun from pronouns item
|
|
307
|
+
if (pronouns) {
|
|
308
|
+
// Return subject pronoun
|
|
309
|
+
if (type === "subject") {
|
|
310
|
+
if (pronouns === "he/him/his/himself")
|
|
311
|
+
return "he";
|
|
312
|
+
if (pronouns === "she/her/hers/herself")
|
|
313
|
+
return "she";
|
|
314
|
+
if (pronouns === "they/them/their/themself")
|
|
315
|
+
return "they";
|
|
316
|
+
if (pronouns === "name_only")
|
|
317
|
+
return "";
|
|
318
|
+
}
|
|
319
|
+
// Return object pronoun
|
|
320
|
+
else if (type === "object") {
|
|
321
|
+
if (pronouns === "he/him/his/himself")
|
|
322
|
+
return "him";
|
|
323
|
+
if (pronouns === "she/her/hers/herself")
|
|
324
|
+
return "her";
|
|
325
|
+
if (pronouns === "they/them/their/themself")
|
|
326
|
+
return "them";
|
|
327
|
+
if (pronouns === "name_only")
|
|
328
|
+
return "";
|
|
329
|
+
}
|
|
330
|
+
// Return possessive pronoun
|
|
331
|
+
else if (type === "possessive") {
|
|
332
|
+
if (pronouns === "he/him/his/himself")
|
|
333
|
+
return "his";
|
|
334
|
+
if (pronouns === "she/her/hers/herself")
|
|
335
|
+
return "her";
|
|
336
|
+
if (pronouns === "they/them/their/themself")
|
|
337
|
+
return "their";
|
|
338
|
+
if (pronouns === "name_only")
|
|
339
|
+
return "";
|
|
340
|
+
}
|
|
341
|
+
// Return reflexive pronoun
|
|
342
|
+
else if (type === "reflexive") {
|
|
343
|
+
if (pronouns === "he/him/his/himself")
|
|
344
|
+
return "himself";
|
|
345
|
+
if (pronouns === "she/her/hers/herself")
|
|
346
|
+
return "herself";
|
|
347
|
+
if (pronouns === "they/them/their/themself")
|
|
348
|
+
return "themself";
|
|
349
|
+
if (pronouns === "name_only")
|
|
350
|
+
return "";
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Assume pronoun for sex if no pronouns are provided
|
|
354
|
+
else if (sex) {
|
|
355
|
+
// Return subject pronoun
|
|
356
|
+
if (type === "subject") {
|
|
357
|
+
if (sex === "male" || sex === "intersex_man")
|
|
358
|
+
return "he";
|
|
359
|
+
if (sex === "female" || sex === "intersex_woman")
|
|
360
|
+
return "she";
|
|
361
|
+
return "they";
|
|
362
|
+
}
|
|
363
|
+
// Return object pronoun
|
|
364
|
+
else if (type === "object") {
|
|
365
|
+
if (sex === "male" || sex === "intersex_man")
|
|
366
|
+
return "him";
|
|
367
|
+
if (sex === "female" || sex === "intersex_woman")
|
|
368
|
+
return "her";
|
|
369
|
+
return "them";
|
|
370
|
+
}
|
|
371
|
+
// Return possessive pronoun
|
|
372
|
+
else if (type === "possessive") {
|
|
373
|
+
if (sex === "male" || sex === "intersex_man")
|
|
374
|
+
return "his";
|
|
375
|
+
if (sex === "female" || sex === "intersex_woman")
|
|
376
|
+
return "her";
|
|
377
|
+
return "their";
|
|
378
|
+
}
|
|
379
|
+
// Return reflexive pronoun
|
|
380
|
+
else if (type === "reflexive") {
|
|
381
|
+
if (sex === "male" || sex === "intersex_man")
|
|
382
|
+
return "himself";
|
|
383
|
+
if (sex === "female" || sex === "intersex_woman")
|
|
384
|
+
return "herself";
|
|
385
|
+
return "themself";
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Return null
|
|
389
|
+
return null;
|
|
390
|
+
},
|
|
391
|
+
/**
|
|
392
|
+
* Generate a SuprSend notification inbox configuration object for a user.
|
|
242
393
|
* @param {string} userId - The user ID to generate the configuration for.
|
|
243
394
|
* @param {string} publicApiKey - The public API key to use for SuprSend.
|
|
244
|
-
* @returns The SuprSend configuration object.
|
|
395
|
+
* @returns The SuprSend notification inbox configuration object.
|
|
245
396
|
*/
|
|
246
|
-
|
|
397
|
+
getSuprSendInboxConfig: (userId, publicApiKey) => ({
|
|
247
398
|
distinctId: userId,
|
|
248
399
|
publicApiKey,
|
|
249
400
|
inbox: {
|
|
@@ -431,17 +582,104 @@ export const Utility = {
|
|
|
431
582
|
}
|
|
432
583
|
},
|
|
433
584
|
/**
|
|
434
|
-
*
|
|
435
|
-
* -
|
|
436
|
-
*
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
*
|
|
441
|
-
* -
|
|
442
|
-
*
|
|
585
|
+
* Checks if a user is authorised to access a microservice.
|
|
586
|
+
* @param {SupabaseClient<Database>} supabase - The Supabase client.
|
|
587
|
+
* @param {UserWithCustomMetadata} user - The user to check.
|
|
588
|
+
* @param {Object} options - The options for the function.
|
|
589
|
+
* @param {MicroserviceGroup} options.microserviceGroup - The microservice group.
|
|
590
|
+
* @param {Microservice<MicroserviceGroup> | string} options.microservice - The microservice.
|
|
591
|
+
* @param {string} options.playersUrl - The URL to the Players microservice.
|
|
592
|
+
* @param {(role: string) => string} options.requestedPermission - A function that returns the requested permission for a given role.
|
|
593
|
+
* @returns {Promise<{ isAuthorised?: boolean; redirect?: { code: number; url: string }; error?: CustomError }>} The result of the function.
|
|
443
594
|
*/
|
|
444
|
-
|
|
595
|
+
isUserAuthorised: async (supabase, user, options) => {
|
|
596
|
+
// Destructure options
|
|
597
|
+
const { microserviceGroup, microservice, playersUrl } = options;
|
|
598
|
+
// Return true if microservice group is public
|
|
599
|
+
if (microserviceGroup === "public")
|
|
600
|
+
return { isAuthorised: true };
|
|
601
|
+
// Get user's roles and the role prefix
|
|
602
|
+
const roles = [];
|
|
603
|
+
const rolePrefix = microserviceGroup === "backroom" ? "backroom" : microserviceGroup.slice(0, -1);
|
|
604
|
+
for (const role of user.app_metadata.roles) {
|
|
605
|
+
if (role === "player")
|
|
606
|
+
continue;
|
|
607
|
+
if (role.startsWith(`${rolePrefix}_`))
|
|
608
|
+
roles.push(role.replace(`${rolePrefix}_`, ""));
|
|
609
|
+
}
|
|
610
|
+
// Redirect to Players microservices if user does not have any roles for the microservice group
|
|
611
|
+
if (roles.length === 0)
|
|
612
|
+
return { redirect: { code: 307, url: playersUrl } };
|
|
613
|
+
// Destructure role
|
|
614
|
+
const [role] = roles;
|
|
615
|
+
let isAuthorised = false;
|
|
616
|
+
// Grant access if superuser or account microservice
|
|
617
|
+
const isSuperuser = (microserviceGroup === "backroom" || microserviceGroup === "lounges") && role === "superuser";
|
|
618
|
+
if (isSuperuser || microservice === "account")
|
|
619
|
+
isAuthorised = true;
|
|
620
|
+
// Evaluate access
|
|
621
|
+
else if (microserviceGroup !== "players") {
|
|
622
|
+
// Get user's role and requested app permission
|
|
623
|
+
const requestedPermission = options.requestedPermission(role);
|
|
624
|
+
// Check if user has the requested permission
|
|
625
|
+
const { data, error: isAuthorisedError } = await supabase
|
|
626
|
+
.schema("compliance")
|
|
627
|
+
.rpc("authorise", { requested_permission: requestedPermission });
|
|
628
|
+
if (isAuthorisedError)
|
|
629
|
+
return Utility.parsePostgrestError(isAuthorisedError);
|
|
630
|
+
// Grant access if user has the requested permission
|
|
631
|
+
isAuthorised = data;
|
|
632
|
+
}
|
|
633
|
+
// Validate SuprSend subscriber
|
|
634
|
+
let personData;
|
|
635
|
+
if (user.app_metadata.person_data) {
|
|
636
|
+
const { first_name: firstName, last_name: lastName, pseudonym } = user.app_metadata.person_data;
|
|
637
|
+
personData = { firstName, lastName, phone: user.phone, pseudonym };
|
|
638
|
+
}
|
|
639
|
+
const { code, error: validationError } = await Utility.callEdgeFunction(false, supabase, `suprsend/validate-subscriber?user-id=${user.id}`, { method: "POST", body: personData });
|
|
640
|
+
if (validationError)
|
|
641
|
+
return { error: Utility.customError(code, validationError) };
|
|
642
|
+
// Initialise user metadata if not already done
|
|
643
|
+
if (!user.user_metadata.backroom) {
|
|
644
|
+
// Update user metadata in database
|
|
645
|
+
const { error: updateError } = await supabase.auth.updateUser({
|
|
646
|
+
data: { backroom: { welcome_notification_sent: false } }
|
|
647
|
+
});
|
|
648
|
+
if (updateError)
|
|
649
|
+
return { error: Utility.customError(updateError.status ?? 500, updateError.message) };
|
|
650
|
+
// Update user object in memory
|
|
651
|
+
user.user_metadata.backroom = { welcome_notification_sent: false };
|
|
652
|
+
}
|
|
653
|
+
// Send welcome notification if user is new
|
|
654
|
+
if (!user.user_metadata.backroom.welcome_notification_sent) {
|
|
655
|
+
// Define workflow body
|
|
656
|
+
const workflowBody = {
|
|
657
|
+
name: "welcome-to-backroom",
|
|
658
|
+
template: "welcome-to-backroom",
|
|
659
|
+
notification_category: "system",
|
|
660
|
+
users: [
|
|
661
|
+
{
|
|
662
|
+
distinct_id: user.id,
|
|
663
|
+
$skip_create: true
|
|
664
|
+
}
|
|
665
|
+
]
|
|
666
|
+
};
|
|
667
|
+
// Trigger workflow
|
|
668
|
+
const { code, error: workflowError } = await Utility.callEdgeFunction(false, supabase, `suprsend/trigger-workflow?user-id=${user.id}`, { method: "POST", body: workflowBody });
|
|
669
|
+
if (workflowError)
|
|
670
|
+
return { error: Utility.customError(code, workflowError) };
|
|
671
|
+
// Update user metadata in database
|
|
672
|
+
const { error: updateError } = await supabase.auth.updateUser({
|
|
673
|
+
data: { backroom: { welcome_notification_sent: true } }
|
|
674
|
+
});
|
|
675
|
+
if (updateError)
|
|
676
|
+
return { error: Utility.customError(updateError.status ?? 500, updateError.message) };
|
|
677
|
+
// Update user object in memory
|
|
678
|
+
user.user_metadata.backroom.welcome_notification_sent = true;
|
|
679
|
+
}
|
|
680
|
+
// Return result
|
|
681
|
+
return { isAuthorised };
|
|
682
|
+
},
|
|
445
683
|
/**
|
|
446
684
|
* A reference of microservices available on the platform, represented as an object with each user group (i.e. `public`, `players`, `lounges`, and `backroom`) having its own array of microservices. Each microservice in a list is an object with the following properties:
|
|
447
685
|
* - `name`: The name of the microservice.
|
|
@@ -459,13 +697,186 @@ export const Utility = {
|
|
|
459
697
|
features: [
|
|
460
698
|
{
|
|
461
699
|
name: "Players",
|
|
462
|
-
description: "
|
|
463
|
-
slug: "players"
|
|
700
|
+
description: "Search, review, and manage Player data.",
|
|
701
|
+
slug: "players",
|
|
702
|
+
tabs: [
|
|
703
|
+
{
|
|
704
|
+
name: "Profile",
|
|
705
|
+
description: "Review basic Player data.",
|
|
706
|
+
root: true
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
name: "Documents",
|
|
710
|
+
description: "Review Player documents.",
|
|
711
|
+
slug: "documents"
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
name: "Data updates",
|
|
715
|
+
description: "Review Player data updates.",
|
|
716
|
+
slug: "data-updates",
|
|
717
|
+
tabs: [
|
|
718
|
+
{
|
|
719
|
+
name: "Requests",
|
|
720
|
+
description: "Review Player data update requests.",
|
|
721
|
+
root: true
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
name: "History",
|
|
725
|
+
description: "Review Player data update history.",
|
|
726
|
+
slug: "history"
|
|
727
|
+
}
|
|
728
|
+
]
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
name: "Credits",
|
|
732
|
+
description: "Review Koloseum Credits data for the Player.",
|
|
733
|
+
slug: "credits",
|
|
734
|
+
tabs: [
|
|
735
|
+
{
|
|
736
|
+
name: "Transactions",
|
|
737
|
+
description: "Review Player transactions with Koloseum Credits.",
|
|
738
|
+
root: true
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
name: "Transfers",
|
|
742
|
+
description: "Review Player transfers with Koloseum Credits.",
|
|
743
|
+
slug: "transfers"
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
name: "Subscriptions",
|
|
747
|
+
description: "Review Player subscriptions with Koloseum Credits.",
|
|
748
|
+
slug: "subscriptions"
|
|
749
|
+
}
|
|
750
|
+
]
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
name: "Misconduct",
|
|
754
|
+
description: "Review Player reports and sanctions.",
|
|
755
|
+
slug: "misconduct",
|
|
756
|
+
tabs: [
|
|
757
|
+
{
|
|
758
|
+
name: "Reports",
|
|
759
|
+
description: "Review Player reports.",
|
|
760
|
+
root: true
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
name: "Sanctions",
|
|
764
|
+
description: "Review Player sanctions.",
|
|
765
|
+
slug: "sanctions"
|
|
766
|
+
}
|
|
767
|
+
]
|
|
768
|
+
},
|
|
769
|
+
{
|
|
770
|
+
name: "Points / KXP",
|
|
771
|
+
description: "Review Koloseum Experience Points (KXP) transactions for the Player.",
|
|
772
|
+
slug: "points"
|
|
773
|
+
}
|
|
774
|
+
]
|
|
464
775
|
},
|
|
465
776
|
{
|
|
466
777
|
name: "Lounges",
|
|
467
|
-
description: "
|
|
468
|
-
slug: "lounges"
|
|
778
|
+
description: "Search, review, and manage Lounge data.",
|
|
779
|
+
slug: "lounges",
|
|
780
|
+
tabs: [
|
|
781
|
+
{
|
|
782
|
+
name: "Profile",
|
|
783
|
+
description: "Review basic Lounge data.",
|
|
784
|
+
root: true
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
name: "Documents",
|
|
788
|
+
description: "Review Lounge documents.",
|
|
789
|
+
slug: "documents"
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
name: "Staff",
|
|
793
|
+
description: "Search, review, and manage Lounge staff data.",
|
|
794
|
+
slug: "staff"
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
name: "Branches",
|
|
798
|
+
description: "Review Lounge branches.",
|
|
799
|
+
slug: "branches",
|
|
800
|
+
tabs: [
|
|
801
|
+
{
|
|
802
|
+
name: "Profile",
|
|
803
|
+
description: "Review basic Lounge branch data.",
|
|
804
|
+
root: true
|
|
805
|
+
},
|
|
806
|
+
{
|
|
807
|
+
name: "Amenities",
|
|
808
|
+
description: "Review Lounge branch amenities.",
|
|
809
|
+
slug: "amenities"
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
name: "Staff",
|
|
813
|
+
description: "Search, review, and manage Lounge branchstaff data.",
|
|
814
|
+
slug: "staff"
|
|
815
|
+
}
|
|
816
|
+
]
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
name: "Data updates",
|
|
820
|
+
description: "Review Lounge data updates.",
|
|
821
|
+
slug: "data-updates",
|
|
822
|
+
tabs: [
|
|
823
|
+
{
|
|
824
|
+
name: "Requests",
|
|
825
|
+
description: "Review Lounge data update requests.",
|
|
826
|
+
root: true
|
|
827
|
+
},
|
|
828
|
+
{
|
|
829
|
+
name: "History",
|
|
830
|
+
description: "Review Lounge data update history.",
|
|
831
|
+
slug: "history"
|
|
832
|
+
}
|
|
833
|
+
]
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
name: "Credits",
|
|
837
|
+
description: "Review Koloseum Credits data for the Lounge.",
|
|
838
|
+
slug: "credits",
|
|
839
|
+
tabs: [
|
|
840
|
+
{
|
|
841
|
+
name: "Transactions",
|
|
842
|
+
description: "Review Lounge transactions with Koloseum Credits.",
|
|
843
|
+
root: true
|
|
844
|
+
},
|
|
845
|
+
{
|
|
846
|
+
name: "Transfers",
|
|
847
|
+
description: "Review Lounge transfers with Koloseum Credits.",
|
|
848
|
+
slug: "transfers"
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
name: "Subscriptions",
|
|
852
|
+
description: "Review Lounge subscriptions with Koloseum Credits.",
|
|
853
|
+
slug: "subscriptions"
|
|
854
|
+
}
|
|
855
|
+
]
|
|
856
|
+
},
|
|
857
|
+
{
|
|
858
|
+
name: "Misconduct",
|
|
859
|
+
description: "Review Lounge reports and sanctions.",
|
|
860
|
+
slug: "misconduct",
|
|
861
|
+
tabs: [
|
|
862
|
+
{
|
|
863
|
+
name: "Reports",
|
|
864
|
+
description: "Review Lounge reports.",
|
|
865
|
+
root: true
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
name: "Sanctions",
|
|
869
|
+
description: "Review Lounge sanctions.",
|
|
870
|
+
slug: "sanctions"
|
|
871
|
+
}
|
|
872
|
+
]
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
name: "Points / KXP",
|
|
876
|
+
description: "Review Koloseum Experience Points (KXP) transactions for the Lounge.",
|
|
877
|
+
slug: "points"
|
|
878
|
+
}
|
|
879
|
+
]
|
|
469
880
|
}
|
|
470
881
|
],
|
|
471
882
|
roles: [
|
|
@@ -642,7 +1053,19 @@ export const Utility = {
|
|
|
642
1053
|
* @param base - The base path of the application; defaults to an empty string.
|
|
643
1054
|
* @returns `true` if the page is active, `false` otherwise.
|
|
644
1055
|
*/
|
|
645
|
-
pageIsActive: (page, slug, microservice, base = "") =>
|
|
1056
|
+
pageIsActive: (page, slug, microservice, base = "") => {
|
|
1057
|
+
// Return true if microservice is provided and matches slug
|
|
1058
|
+
if (microservice)
|
|
1059
|
+
return slug === microservice;
|
|
1060
|
+
// Remove trailing slash from base for consistency
|
|
1061
|
+
const cleanBase = base.replace(/\/$/, "");
|
|
1062
|
+
// Match exactly the base path if slug is falsy (i.e. root microservice feature)
|
|
1063
|
+
if (!slug)
|
|
1064
|
+
return page.url.pathname === cleanBase || page.url.pathname === `${cleanBase}/`;
|
|
1065
|
+
// Match exact or subpath otherwise
|
|
1066
|
+
const target = `${cleanBase}/${slug}`;
|
|
1067
|
+
return page.url.pathname === target || page.url.pathname.startsWith(`${target}/`);
|
|
1068
|
+
},
|
|
646
1069
|
/**
|
|
647
1070
|
* Parses a `CustomError` object within a page/layout server load function and returns an error to be thrown.
|
|
648
1071
|
* @param {CustomError} error - The error object
|
|
@@ -717,11 +1140,19 @@ export const Utility = {
|
|
|
717
1140
|
*/
|
|
718
1141
|
passwordRegex: /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()-_+=|{}[\]:;'<>,.?/~]).{8,}$/,
|
|
719
1142
|
/**
|
|
720
|
-
*
|
|
721
|
-
* -
|
|
722
|
-
*
|
|
1143
|
+
* Returns a regular expression for a Koloseum platform ID.
|
|
1144
|
+
* @param {string} type - The type of platform ID to return
|
|
1145
|
+
* @returns A regular expression for the platform ID, or `null` if the type is invalid
|
|
723
1146
|
*/
|
|
724
|
-
|
|
1147
|
+
platformIdRegex: (type) => {
|
|
1148
|
+
if (type === "lounge")
|
|
1149
|
+
return /^KL\d{7}$/;
|
|
1150
|
+
if (type === "loungeBranch")
|
|
1151
|
+
return /^KLB\d{7}$/;
|
|
1152
|
+
if (type === "player")
|
|
1153
|
+
return /^KP\d{7}$/;
|
|
1154
|
+
return null;
|
|
1155
|
+
},
|
|
725
1156
|
/**
|
|
726
1157
|
* Sanitises any potential HTML injected into user input.
|
|
727
1158
|
* @param {string} input - The input to be sanitised
|
|
@@ -748,43 +1179,5 @@ export const Utility = {
|
|
|
748
1179
|
*/
|
|
749
1180
|
validateSocialMediaHandle: (handle) => !isURL(handle, { require_protocol: false }) &&
|
|
750
1181
|
!handle.startsWith("@") &&
|
|
751
|
-
Boolean(handle.match(Utility.socialMediaHandleRegex))
|
|
752
|
-
/**
|
|
753
|
-
* Validates a SuprSend subscriber. If the subscriber does not exist, it will be created with the provided person data.
|
|
754
|
-
* @param {suprsend.Suprsend} suprSend - The SuprSend instance.
|
|
755
|
-
* @param {string} userId - The user ID.
|
|
756
|
-
* @param {Object} personData - The person data; required if the subscriber does not exist.
|
|
757
|
-
* @returns {Promise<{ error?: CustomError; success?: true }>} A promise that resolves to an object with an error if the subscriber validation fails, or indicating success otherwise.
|
|
758
|
-
*/
|
|
759
|
-
validateSuprSendSubscriber: async (suprSend, userId, personData) => {
|
|
760
|
-
// Check if subscriber exists
|
|
761
|
-
try {
|
|
762
|
-
await suprSend.users.get(userId);
|
|
763
|
-
}
|
|
764
|
-
catch (_err) {
|
|
765
|
-
// If person data is not provided, return an error
|
|
766
|
-
if (!personData)
|
|
767
|
-
return { error: Utility.customError(400, "Person data is required to create a SuprSend subscriber.") };
|
|
768
|
-
// Destructure person data
|
|
769
|
-
const { firstName, lastName, phone, pseudonym } = personData;
|
|
770
|
-
// Create subscriber
|
|
771
|
-
const subscriber = suprSend.user.get_instance(userId);
|
|
772
|
-
if (!subscriber)
|
|
773
|
-
return { error: Utility.customError(500, Status.ERROR) };
|
|
774
|
-
// Set subscriber details
|
|
775
|
-
subscriber.set_preferred_language("en");
|
|
776
|
-
subscriber.set_timezone("Africa/Nairobi");
|
|
777
|
-
subscriber.set("firstName", firstName);
|
|
778
|
-
subscriber.set("lastName", lastName);
|
|
779
|
-
subscriber.set("phone", `+${phone}`);
|
|
780
|
-
if (pseudonym)
|
|
781
|
-
subscriber.set("pseudonym", pseudonym);
|
|
782
|
-
// Save subscriber
|
|
783
|
-
const response = await subscriber.save();
|
|
784
|
-
if (!response.success)
|
|
785
|
-
return { error: Utility.customError(500, Status.ERROR) };
|
|
786
|
-
}
|
|
787
|
-
// Return success
|
|
788
|
-
return { success: true };
|
|
789
|
-
}
|
|
1182
|
+
Boolean(handle.match(Utility.socialMediaHandleRegex))
|
|
790
1183
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koloseum/utils",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"author": "Koloseum Technologies Limited",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Utility logic for use across Koloseum web apps (TypeScript)",
|
|
@@ -29,21 +29,21 @@
|
|
|
29
29
|
"test": "vitest --run"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@supabase/supabase-js": "^2.
|
|
33
|
-
"@sveltejs/kit": "^2.
|
|
34
|
-
"sanitize-html": "^2.
|
|
35
|
-
"
|
|
32
|
+
"@supabase/supabase-js": "^2.50.1",
|
|
33
|
+
"@sveltejs/kit": "^2.22.0",
|
|
34
|
+
"sanitize-html": "^2.17.0",
|
|
35
|
+
"uuid": "^11.1.0",
|
|
36
|
+
"validator": "^13.15.15"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
|
-
"@koloseum/types": "^0.1
|
|
39
|
-
"@playwright/test": "^1.
|
|
40
|
-
"@suprsend/node-sdk": "^1.13.0",
|
|
39
|
+
"@koloseum/types": "^0.2.1",
|
|
40
|
+
"@playwright/test": "^1.53.1",
|
|
41
41
|
"@suprsend/web-components": "^0.2.1",
|
|
42
|
-
"@types/sanitize-html": "^2.
|
|
42
|
+
"@types/sanitize-html": "^2.16.0",
|
|
43
43
|
"@types/uuid": "^10.0.0",
|
|
44
|
-
"@types/validator": "^13.
|
|
45
|
-
"prettier": "^3.
|
|
46
|
-
"typescript": "^5.
|
|
47
|
-
"vitest": "^3.
|
|
44
|
+
"@types/validator": "^13.15.2",
|
|
45
|
+
"prettier": "^3.6.0",
|
|
46
|
+
"typescript": "^5.8.3",
|
|
47
|
+
"vitest": "^3.2.4"
|
|
48
48
|
}
|
|
49
49
|
}
|