@salad-labs/loopz-typescript
Version:
The Official Loopz TypeScript SDK
958 lines • 52.2 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { Client } from "./core/client";
import { Crypto } from "./core";
import { CLIENT_DB_KEY_LAST_USER_LOGGED } from "./constants/app";
import { getAccessToken } from "@privy-io/react-auth";
import { Account, Chat, Serpens } from ".";
/**
* Represents an authentication client that interacts with a backend server for user authentication.
*/
export class Auth {
static set authToken(authToken) {
Auth._authToken = authToken;
Auth._realtimeAuthorizationToken = authToken
? `${Auth._apiKey}##${authToken}`
: null;
}
static set account(account) {
Auth._account = account;
if (account)
Auth._emit("__onAccountReady");
}
static get realtimeAuthorizationToken() {
return Auth._realtimeAuthorizationToken;
}
static get apiKey() {
return Auth._apiKey;
}
static get authToken() {
return Auth._authToken;
}
static get account() {
return Auth._account;
}
static get MAX_ATTEMPTS_REALTIME_FETCH_AUTH_TOKEN() {
return 2;
}
constructor() {
if (!Auth._config)
throw new Error("Auth must be configured before getting the instance");
Auth._storage = Auth._config.storage;
Auth._apiKey = Auth._config.apiKey;
Auth._client = new Client(Auth._config.devMode);
//OAuth providers like Google, Instagram etc bring the user from the current web application page to
//their authentication pages. When the user is redirect from their auth pages to the web application page again
//this event is fired.
this.on("__onOAuthAuthenticatedDesktop", Auth._callBackendAuthAfterOAuthRedirect);
// same but for linking an account to a user already registered
this.on("__onOAuthLinkAuthenticatedDesktop", Auth._callBackendLinkAfterOAuthRedirect);
//OAuth providers login error handling
this.on("__onLoginError", (error) => {
Auth._emit("onAuthError", error);
});
this.on("__onLinkAccountError", (error) => {
Auth._emit("onLinkError", error);
});
Auth._instance = this;
}
/** static methods */
static _generateKeys() {
return __awaiter(this, void 0, void 0, function* () {
try {
const keys = yield Crypto.generateKeys("HIGH");
if (!keys)
throw new Error("Error during generation of public/private keys.");
return keys;
}
catch (error) {
throw error;
}
});
}
static _getOrCreateUserKeys(did, organizationId) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
Serpens.addAction(() => {
Auth._storage.user
.where("[did+organizationId]")
.equals([did, organizationId])
.first()
.then((existingUser) => __awaiter(this, void 0, void 0, function* () {
if (existingUser) {
resolve({
e2ePublicKeyPem: existingUser.e2ePublicKey,
e2eEncryptedPrivateKey: existingUser.e2eEncryptedPrivateKey,
e2ePrivateKeyPem: null,
});
}
else {
const keys = yield Auth._generateKeys();
const publicKeyPem = Crypto.convertRSAPublicKeyToPem(keys.publicKey);
const privateKeyPem = Crypto.convertRSAPrivateKeyToPem(keys.privateKey);
resolve({
e2ePublicKeyPem: publicKeyPem,
e2ePrivateKeyPem: privateKeyPem,
e2eEncryptedPrivateKey: null,
});
}
}))
.catch(reject);
});
});
});
}
static _storeUserOnLocalDB(keys) {
return __awaiter(this, void 0, void 0, function* () {
try {
if (!Auth._account)
throw new Error("Account is not set");
const existingUser = yield new Promise((resolve, reject) => {
Serpens.addAction(() => {
if (!Auth._account)
throw new Error("Account is not setup correctly.");
Auth._storage.user
.where("[did+organizationId]")
.equals([Auth._account.did, Auth._account.organizationId])
.first()
.then(resolve)
.catch(reject);
});
});
if (existingUser) {
if (existingUser.firstLogin) {
yield new Promise((resolve, reject) => {
Serpens.addAction(() => {
if (!Auth._account)
throw new Error("Account is not setup correctly.");
Auth._storage.user
.update(existingUser, {
firstLogin: false,
})
.then(resolve)
.catch(reject);
});
});
}
return;
}
//save all the data related to this user into the db
yield new Promise((resolve, reject) => {
Serpens.addAction(() => {
if (!Auth._account)
throw new Error("Account is not setup correctly.");
Auth._storage.user
.add({
did: Auth._account.did,
organizationId: Auth._account.organizationId,
dynamoDBUserID: Auth._account.dynamoDBUserID,
username: Auth._account.username,
email: Auth._account.email,
bio: Auth._account.bio,
instagramPublicUrl: Auth._account.instagramPublicUrl,
xPublicUrl: Auth._account.xPublicUrl,
tiktokPublicUrl: Auth._account.tiktokPublicUrl,
personalWebsiteUrl: Auth._account.personalWebsiteUrl,
avatarUrl: Auth._account.avatarUrl,
bannerImageUrl: Auth._account.bannerImageUrl,
imageSettings: Auth._account.imageSettings
? Auth._account.imageSettings
: null,
city: Auth._account.city,
country: Auth._account.country,
lat: Auth._account.lat,
lng: Auth._account.lng,
isCreator: Auth._account.isCreator,
gender: Auth._account.gender,
isVerified: Auth._account.isVerified,
signupCompleted: Auth._account.signupCompleted,
wallet: {
address: Auth._account.walletAddress,
connectorType: Auth._account.walletConnectorType,
imported: Auth._account.walletImported,
recoveryMethod: Auth._account.walletRecoveryMethod,
clientType: Auth._account.walletClientType,
},
apple: Auth._account.appleSubject
? {
subject: Auth._account.appleSubject,
email: Auth._account.email,
}
: null,
discord: Auth._account.discordSubject
? {
subject: Auth._account.discordSubject,
email: Auth._account.discordEmail,
username: Auth._account.username,
}
: null,
farcaster: Auth._account.farcasterFid
? {
fid: Auth._account.farcasterFid,
displayName: Auth._account.farcasterDisplayName,
ownerAddress: Auth._account.farcasterOwnerAddress,
pfp: Auth._account.farcasterPfp,
username: Auth._account.farcasterUsername,
signerPublicKey: Auth._account.farcasterSignerPublicKey,
}
: null,
github: Auth._account.githubSubject
? {
subject: Auth._account.githubSubject,
email: Auth._account.githubEmail,
name: Auth._account.githubName,
username: Auth._account.githubUsername,
}
: null,
google: Auth._account.googleSubject
? {
subject: Auth._account.googleSubject,
email: Auth._account.googleEmail,
name: Auth._account.googleName,
}
: null,
instagram: Auth._account.instagramSubject
? {
subject: Auth._account.instagramSubject,
username: Auth._account.instagramUsername,
}
: null,
linkedin: Auth._account.linkedinSubject
? {
subject: Auth._account.linkedinSubject,
email: Auth._account.linkedinEmail,
name: Auth._account.linkedinName,
vanityName: Auth._account.linkedinVanityName,
}
: null,
spotify: Auth._account.spotifySubject
? {
subject: Auth._account.spotifySubject,
email: Auth._account.spotifyEmail,
name: Auth._account.spotifyName,
}
: null,
telegram: Auth._account.telegramUserId
? {
firstName: Auth._account.telegramFirstName,
lastName: Auth._account.telegramLastName,
photoUrl: Auth._account.telegramPhotoUrl,
userId: Auth._account.telegramUserId,
username: Auth._account.telegramUsername,
}
: null,
tiktok: Auth._account.tiktokSubject
? {
name: Auth._account.tiktokName,
subject: Auth._account.tiktokSubject,
username: Auth._account.tiktokUsername,
}
: null,
twitter: Auth._account.twitterSubject
? {
name: Auth._account.twitterName,
subject: Auth._account.twitterSubject,
profilePictureUrl: Auth._account.twitterProfilePictureUrl,
username: Auth._account.twitterUsername,
}
: null,
firstLogin: Auth._account.firstLogin,
proposalNotificationPush: Auth._account.proposalNotificationPush,
proposalNotificationSystem: Auth._account.proposalNotificationSystem,
orderNotificationPush: Auth._account.orderNotificationPush,
orderNotificationSystem: Auth._account.orderNotificationSystem,
followNotificationPush: Auth._account.followNotificationPush,
followNotificationSystem: Auth._account.followNotificationSystem,
collectionNotificationPush: Auth._account.collectionNotificationPush,
collectionNotificationSystem: Auth._account.collectionNotificationSystem,
generalNotificationPush: Auth._account.generalNotificationPush,
generalNotificationSystem: Auth._account.generalNotificationSystem,
accountSuspended: Auth._account.accountSuspended,
allowNotification: Auth._account.allowNotification,
allowNotificationSound: Auth._account.allowNotificationSound,
visibility: Auth._account.visibility,
onlineStatus: Auth._account.onlineStatus,
allowReadReceipt: Auth._account.allowReadReceipt,
allowReceiveMessageFrom: Auth._account.allowReceiveMessageFrom,
allowAddToGroupsFrom: Auth._account.allowAddToGroupsFrom,
allowGroupsSuggestion: Auth._account.allowGroupsSuggestion,
e2ePublicKey: keys.e2ePublicKeyPem,
e2eEncryptedPrivateKey: keys.e2eEncryptedPrivateKey
? keys.e2eEncryptedPrivateKey
: "",
createdAt: Auth._account.createdAt,
updatedAt: Auth._account.updatedAt,
lastSyncAt: null,
})
.then(resolve)
.catch(reject);
});
});
}
catch (error) {
console.error(error);
throw new Error("Error during setup of the local keys. Check the console to have more information.");
}
});
}
static _callBackendAuthAfterOAuthRedirect(authInfo) {
return __awaiter(this, void 0, void 0, function* () {
if (!Auth._config || !Auth._instance || !Auth._client)
throw new Error("Auth has not been configured");
Auth.authToken = authInfo.authToken;
try {
const keys = yield Auth._getOrCreateUserKeys(authInfo.user.id, Auth._apiKey);
const { response } = yield Auth._client.fetch(Auth._client.backendUrl("/auth"), {
method: "POST",
body: Object.assign(Object.assign({}, Auth._formatAuthParams(authInfo)), { e2ePublicKey: keys.e2ePublicKeyPem }),
});
if (!response || !response.data)
return Auth._emit("onAuthError", new Error("No response from backend during authentication."));
const { user } = response.data[0];
if (!user)
return Auth._emit("onAuthError", new Error("Access not granted."));
//let's check if it's the first login of the user.
//this is needed to understand if the user is doing the login on a different device
//this is needed to understand if the user is doing the login on a different device
if (user.e2ePublicKey.toLowerCase() === keys.e2ePublicKeyPem.toLowerCase()) {
//this means the user is accessing from the same device/the devices it owns share the same keys OR he's/she's doing the signup
if (keys.e2eEncryptedPrivateKey) {
//it means i've recovered the private key from the local database. So the user has the possibility to have chats
Chat.getInstance().setCanChat(true);
//so now we have the following situation:
//keys.e2eEncryptedPrivateKey = string
//keys.e2ePrivateKeyPem = string
//keys.e2ePublicKeyPem = string
}
else {
//in the case of a signup, i generate now the private key so the user can chat
Chat.getInstance().setCanChat(true);
const encryptedPrivateKey = Crypto.encryptAES_CBC(keys.e2ePrivateKeyPem, Buffer.from(user.e2eSecret, "hex").toString("base64"), Buffer.from(user.e2eSecretIV, "hex").toString("base64"));
keys.e2eEncryptedPrivateKey = encryptedPrivateKey;
//so now we have the following situation:
//keys.e2eEncryptedPrivateKey = string
//keys.e2ePrivateKeyPem = string
//keys.e2ePublicKeyPem = string
}
}
else {
//this means the user is accessing from another device, so the keys needs to be overwritten
Chat.getInstance().setCanChat(false);
keys.e2ePublicKeyPem = user.e2ePublicKey;
keys.e2ePrivateKeyPem = null;
//so now we have the following situation:
//keys.e2eEncryptedPrivateKey = null
//keys.e2ePrivateKeyPem = null
//keys.e2ePublicKeyPem = string
}
Auth._isAuthenticated = true;
Auth.account = new Account(Object.assign({ enableDevMode: Auth._config.devMode, storage: Auth._config.storage }, user));
//store the key of the last user logged in the local storage, this allow to recover the user and rebuild the account object when
//the user refresh the page
Auth._account.storeLastUserLoggedKey();
//generation of the table and local keys for e2e encryption
yield Auth._storeUserOnLocalDB(keys);
//clear all the internal callbacks connected to the authentication...
Auth._clearEventsCallbacks([
"__onOAuthAuthenticatedDesktop",
"__onLoginError",
]);
}
catch (error) {
//clear all the internal callbacks connected to the authentication...
this._clearEventsCallbacks([
"__onOAuthAuthenticatedDesktop",
"__onLoginError",
]);
yield Auth._instance.logout();
Auth._emit("onAuthError", error);
}
});
}
static _callBackendAuth(authInfo) {
return __awaiter(this, void 0, void 0, function* () {
if (!Auth._config || !Auth._instance || !Auth._client)
throw new Error("Auth has not been configured");
Auth.authToken = authInfo.authToken;
try {
const keys = yield Auth._getOrCreateUserKeys(authInfo.user.id, Auth._apiKey);
const { response } = yield Auth._client.fetch(Auth._client.backendUrl("/auth"), {
method: "POST",
body: Object.assign(Object.assign({}, Auth._formatAuthParams(authInfo)), { e2ePublicKey: keys.e2ePublicKeyPem }),
});
if (!response || !response.data)
throw new Error("Invalid response.");
const { user } = response.data[0];
if (!user)
throw new Error("Access not granted.");
//this is needed to understand if the user is doing the login on a different device
if (user.e2ePublicKey.toLowerCase() === keys.e2ePublicKeyPem.toLowerCase()) {
//this means the user is accessing from the same device/the devices it owns share the same keys OR he's/she's doing the signup
if (keys.e2eEncryptedPrivateKey) {
//it means i've recovered the private key from the local database. So the user has the possibility to chat
Chat.getInstance().setCanChat(true);
//so now we have the following situation:
//keys.e2eEncryptedPrivateKey = string
//keys.e2ePrivateKeyPem = string
//keys.e2ePublicKeyPem = string
}
else {
//in the case of a signup, i generate now the private key so the user can chat
Chat.getInstance().setCanChat(true);
const encryptedPrivateKey = Crypto.encryptAES_CBC(keys.e2ePrivateKeyPem, Buffer.from(user.e2eSecret, "hex").toString("base64"), Buffer.from(user.e2eSecretIV, "hex").toString("base64"));
keys.e2eEncryptedPrivateKey = encryptedPrivateKey;
//so now we have the following situation:
//keys.e2eEncryptedPrivateKey = string
//keys.e2ePrivateKeyPem = string
//keys.e2ePublicKeyPem = string
}
}
else {
//this means the user is accessing from another device, so the keys need to be overwritten
Chat.getInstance().setCanChat(false);
keys.e2ePublicKeyPem = user.e2ePublicKey;
keys.e2ePrivateKeyPem = null;
//so now we have the following situation:
//keys.e2eEncryptedPrivateKey = null
//keys.e2ePrivateKeyPem = null
//keys.e2ePublicKeyPem = string
}
Auth._isAuthenticated = true;
Auth._authInfo = Object.assign({ isConnected: true }, authInfo);
Auth.account = new Account(Object.assign({ enableDevMode: Auth._config.devMode, storage: Auth._config.storage }, user));
//store the key of the last user logged in the local storage, this allow to recover the user and rebuild the account object when
//the user refresh the page
Auth._account.storeLastUserLoggedKey();
//generation of the record in user table and local keys for e2e encryption
yield Auth._storeUserOnLocalDB(keys);
//clear all the internal callbacks connected to the authentication...
Auth._clearEventsCallbacks(["__onLoginComplete", "__onLoginError"]);
return {
auth: Auth._authInfo,
account: Auth._account,
};
}
catch (error) {
console.error(error);
//clear all the internal callbacks connected to the authentication...
Auth._clearEventsCallbacks(["__onLoginComplete", "__onLoginError"]);
yield Auth._instance.logout();
Auth._emit("onAuthError", error);
throw error;
}
});
}
static _callBackendLinkAfterOAuthRedirect(authInfo) {
return __awaiter(this, void 0, void 0, function* () {
if (!Auth._config || !Auth._instance || !Auth._client)
throw new Error("Auth has not been configured");
Auth.authToken = authInfo.authToken;
try {
const { response } = yield Auth._client.fetch(Auth._client.backendUrl("/user/link/account"), {
method: "POST",
body: Object.assign({}, Auth._formatAuthParams(authInfo)),
});
if (!response || !response.data)
throw new Error("Invalid response.");
const { link } = response.data[0];
const { status } = link;
if (!link || !status)
throw new Error("An error occured while updating the account.");
//clear all the internal callbacks connected to the authentication...
Auth._clearEventsCallbacks([
"__onOAuthLinkAuthenticatedDesktop",
"__onLinkAccountError",
]);
Auth._emit("link", authInfo);
}
catch (error) {
console.error(error);
if ("statusCode" in error && error.statusCode === 401) {
Auth._clearEventsCallbacks([
"__onOAuthLinkAuthenticatedDesktop",
"__onLinkAccountError",
"__onLoginComplete",
"__onLoginError",
]);
Auth._emit("onLinkError", error);
yield Auth._instance.logout();
Auth._emit("onAuthError", error);
}
else {
Auth._clearEventsCallbacks([
"__onOAuthLinkAuthenticatedDesktop",
"__onLinkAccountError",
]);
Auth._emit("onLinkError", error);
}
}
});
}
static _callBackendLink(authInfo) {
return __awaiter(this, void 0, void 0, function* () {
if (!Auth._config || !Auth._instance || !Auth._client)
throw new Error("Auth has not been configured");
Auth.authToken = authInfo.authToken;
try {
const { response } = yield Auth._client.fetch(Auth._client.backendUrl("/user/link/account"), {
method: "POST",
body: Object.assign({}, Auth._formatAuthParams(authInfo)),
});
if (!response || !response.data)
throw new Error("Invalid response.");
const { link } = response.data[0];
const { status } = link;
if (!link || !status)
throw new Error("An error occured while updating the account.");
//clear all the internal callbacks connected to the link...
Auth._clearEventsCallbacks([
"__onLinkAccountComplete",
"__onLinkAccountError",
]);
return authInfo;
}
catch (error) {
if ("statusCode" in error && error.statusCode === 401) {
Auth._clearEventsCallbacks([
"__onLinkAccountComplete",
"__onLinkAccountError",
"__onLoginComplete",
"__onLoginError",
]);
yield Auth._instance.logout();
Auth._emit("onAuthError", error);
}
else {
Auth._clearEventsCallbacks([
"__onLinkAccountComplete",
"__onLinkAccountError",
]);
}
throw error;
}
});
}
static _formatAuthParams(authInfo) {
return {
did: authInfo.user.id,
walletAddress: authInfo.user.wallet.address,
walletConnectorType: authInfo.user.wallet.connectorType,
walletImported: authInfo.user.wallet.imported
? authInfo.user.wallet.imported
: false,
walletRecoveryMethod: authInfo.user.wallet.recoveryMethod
? authInfo.user.wallet.recoveryMethod
: "",
walletClientType: authInfo.user.wallet.walletClientType
? authInfo.user.wallet.walletClientType
: "",
appleSubject: authInfo.user.apple ? authInfo.user.apple.subject : null,
appleEmail: authInfo.user.apple ? authInfo.user.apple.email : null,
discordSubject: authInfo.user.discord
? authInfo.user.discord.subject
: null,
discordEmail: authInfo.user.discord ? authInfo.user.discord.email : null,
discordUsername: authInfo.user.discord
? authInfo.user.discord.username
: null,
farcasterFid: authInfo.user.farcaster
? authInfo.user.farcaster.fid
: null,
farcasterDisplayName: authInfo.user.farcaster
? authInfo.user.farcaster.displayName
: null,
farcasterOwnerAddress: authInfo.user.farcaster
? authInfo.user.farcaster.ownerAddress
: null,
farcasterPfp: authInfo.user.farcaster
? authInfo.user.farcaster.pfp
: null,
farcasterSignerPublicKey: authInfo.user.farcaster
? authInfo.user.farcaster.signerPublicKey
: null,
farcasterUrl: authInfo.user.farcaster
? authInfo.user.farcaster.url
: null,
farcasterUsername: authInfo.user.farcaster
? authInfo.user.farcaster.username
: null,
githubSubject: authInfo.user.github ? authInfo.user.github.subject : null,
githubEmail: authInfo.user.github ? authInfo.user.github.email : null,
githubName: authInfo.user.github ? authInfo.user.github.name : null,
githubUsername: authInfo.user.github
? authInfo.user.github.username
: null,
googleEmail: authInfo.user.google ? authInfo.user.google.email : null,
googleName: authInfo.user.google ? authInfo.user.google.name : null,
googleSubject: authInfo.user.google ? authInfo.user.google.subject : null,
instagramSubject: authInfo.user.instagram
? authInfo.user.instagram.subject
: null,
instagramUsername: authInfo.user.instagram
? authInfo.user.instagram.username
: null,
linkedinEmail: authInfo.user.linkedin
? authInfo.user.linkedin.email
: null,
linkedinName: authInfo.user.linkedin ? authInfo.user.linkedin.name : null,
linkedinSubject: authInfo.user.linkedin
? authInfo.user.linkedin.subject
: null,
linkedinVanityName: authInfo.user.linkedin
? authInfo.user.linkedin.vanityName
: null,
spotifyEmail: authInfo.user.spotify ? authInfo.user.spotify.email : null,
spotifyName: authInfo.user.spotify ? authInfo.user.spotify.name : null,
spotifySubject: authInfo.user.spotify
? authInfo.user.spotify.subject
: null,
telegramFirstName: authInfo.user.telegram
? authInfo.user.telegram.firstName
: null,
telegramLastName: authInfo.user.telegram
? authInfo.user.telegram.lastName
: null,
telegramPhotoUrl: authInfo.user.telegram
? authInfo.user.telegram.photoUrl
: null,
telegramUserId: authInfo.user.telegram
? authInfo.user.telegram.telegramUserId
: null,
telegramUsername: authInfo.user.telegram
? authInfo.user.telegram.username
: null,
tiktokName: authInfo.user.tiktok ? authInfo.user.tiktok.name : null,
tiktokSubject: authInfo.user.tiktok ? authInfo.user.tiktok.subject : null,
tiktokUsername: authInfo.user.tiktok
? authInfo.user.tiktok.username
: null,
twitterName: authInfo.user.twitter ? authInfo.user.twitter.name : null,
twitterSubject: authInfo.user.twitter
? authInfo.user.twitter.subject
: null,
twitterProfilePictureUrl: authInfo.user.twitter
? authInfo.user.twitter.profilePictureUrl
: null,
twitterUsername: authInfo.user.twitter
? authInfo.user.twitter.username
: null,
phone: authInfo.user.phone ? authInfo.user.phone.number : null,
email: authInfo.user.email ? authInfo.user.email.address : null,
};
}
static _clearEventsCallbacks(events) {
for (const event of events) {
const index = Auth._eventsCallbacks.findIndex((item) => item.event === event);
if (index < 0)
return;
Auth._eventsCallbacks[index].callbacks = [];
}
}
static _handleDesktopAuthentication() {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
if (!Auth._config || !Auth._instance)
throw new Error("Auth has not been configured");
Auth._instance.on("__onLoginComplete", (authInfo) => {
Auth._callBackendAuth(authInfo).then(resolve).catch(reject);
});
Auth._instance.on("__onLoginError", reject);
Auth._emit("__authenticate");
});
});
}
static _handleDesktopLink(method) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
if (!Auth._config || !Auth._instance)
throw new Error("Auth has not been configured");
Auth._instance.on("__onLinkAccountComplete", (authInfo) => {
Auth._callBackendLink(authInfo).then(resolve).catch(reject);
});
Auth._instance.on("__onLinkAccountError", reject);
Auth._emit("__link", method);
});
});
}
static _emit(event, params) {
const index = Auth._eventsCallbacks.findIndex((item) => item.event === event);
if (index < 0)
return;
Auth._eventsCallbacks[index].callbacks.forEach((callback) => callback(params));
}
static config(config) {
if (Auth._config)
throw new Error("Auth already configured");
Auth._config = config;
}
static getInstance() {
var _a;
return (_a = Auth._instance) !== null && _a !== void 0 ? _a : new Auth();
}
/** public instance methods */
/**
* Add a new event and the associated callback.
* @param event - The event to listen.
* @param callback - The callback related to this event.
* @param onlyOnce - An optional flag, it allows the adding of only one callback associated to this event.
*/
on(event, callback, onlyOnce) {
const index = Auth._eventsCallbacks.findIndex((item) => item.event === event);
if (index < 0)
Auth._eventsCallbacks.push({
event,
callbacks: [callback],
});
else {
// this is wrong, if i flag onlyOnce: true i still want my callback to be executed, just once tho
if (onlyOnce)
return;
Auth._eventsCallbacks[index].callbacks.push(callback);
}
}
/**
* Remove an event and the associated callback or all the callbacks associated to that event.
* @param event - The event to unlisten.
* @param callback - The callback related to this event.
* @returns None
*/
off(event, callback) {
const index = Auth._eventsCallbacks.findIndex((item) => {
return item.event === event;
});
if (index < 0)
return;
Auth._eventsCallbacks[index].callbacks = callback
? Auth._eventsCallbacks[index].callbacks.filter((cb) => cb !== callback)
: [];
}
authenticate() {
return Auth._handleDesktopAuthentication();
}
sendEmailOTPCode(email) {
return new Promise((resolve, reject) => {
this.on("__onEmailOTPCodeSent", (email) => resolve({ email }));
this.on("__onEmailOTPCodeSentError", reject);
Auth._emit("__sendEmailOTPCode", email);
});
}
sendPhoneOTPCode(phone) {
return new Promise((resolve, reject) => {
this.on("__onSMSOTPCodeSent", (phone) => resolve({ phone }));
this.on("__onSMSOTPCodeSentError", reject);
Auth._emit("__sendSMSOTPCode", phone);
});
}
sendEmailOTPCodeAfterAuth(email) {
return new Promise((resolve, reject) => {
this.on("__onEmailOTPCodeAfterAuthSent", (email) => resolve({ email }));
this.on("__onEmailOTPCodeAfterAuthSentError", reject);
Auth._emit("__sendEmailOTPCodeAfterAuth", email);
});
}
sendPhoneOTPCodeAfterAuth(phone) {
return new Promise((resolve, reject) => {
this.on("__onSMSOTPCodeAfterAuthSent", (phone) => resolve({ phone }));
this.on("__onSMSOTPCodeSentAfterAuthError", reject);
Auth._emit("__sendSMSOTPCodeAfterAuth", phone);
});
}
/**
* call this if you want to auth the user automatically without the need of a button to authenticate.
*
* e.g. auth.ready().then(() => auth.authenticate())
*/
ready() {
return new Promise((resolve) => this.on("__onPrivyReady", () => resolve(true)));
}
logout() {
var _a, _b;
Auth._isAuthenticated = false;
Auth._clearEventsCallbacks(["__onLoginComplete", "__onLoginError"]);
(_a = Auth._account) === null || _a === void 0 ? void 0 : _a.destroyLastUserLoggedKey();
(_b = Auth._account) === null || _b === void 0 ? void 0 : _b.emptyActiveWallets();
Auth._account = null;
Auth.authToken = null;
Chat.getInstance().disconnect();
Auth._emit("__logout"); //tells to Privy to logout
Auth._emit("logout"); //tells to Loopz listeners to logout
}
isAuthenticated() {
return Auth._isAuthenticated;
}
getAuthInfo() {
return Auth._authInfo;
}
getCurrentAccount() {
return Auth.account;
}
link(method) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
Auth._handleDesktopLink(method).then(resolve).catch(reject);
});
});
}
unlink(method) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
this.on("__onUnlinkAccountComplete", (status) => {
// TODO aggiungere await per chiamata lato server per aggiornare backend
resolve(status);
});
this.on("__onUnlinkAccountError", reject);
Auth._emit("__unlink", method);
});
});
}
static recoverAccountFromLocalDB() {
return __awaiter(this, void 0, void 0, function* () {
if (!Auth._config || !Auth._instance || !Auth._client)
throw new Error("Auth has not been configured");
if (Auth._account)
return;
const lastUserLoggedKey = window.localStorage.getItem(CLIENT_DB_KEY_LAST_USER_LOGGED);
if (!lastUserLoggedKey)
return;
try {
const { did, organizationId, token } = JSON.parse(lastUserLoggedKey);
const user = yield Auth._storage.get("user", "[did+organizationId]", [
did,
organizationId,
]);
Chat.getInstance().setCanChat(user.e2ePublicKey &&
user.e2ePublicKey.length > 0 &&
user.e2eEncryptedPrivateKey &&
user.e2eEncryptedPrivateKey.length > 0);
Auth.authToken = token;
const { response } = yield Auth._client.fetch(Auth._client.backendUrl("/user/secrets"));
if (!response ||
!response.data ||
typeof response.data[0] === "undefined")
return;
const { secrets } = response.data[0];
if (!secrets)
return;
const { e2eSecret, e2eSecretIV } = secrets;
Auth._isAuthenticated = true;
Auth.account = new Account({
enableDevMode: Auth._config.devMode,
storage: Auth._config.storage,
did: user.did,
organizationId: user.organizationId,
token: Auth.authToken, //we use Auth.authToken and not token because in case we have a 401 error, variable 'token' has the value of the previous token. Instead Auth.authToken has the updated value.
walletAddress: user.wallet.address,
walletConnectorType: user.wallet.connectorType,
walletImported: user.wallet.imported,
walletRecoveryMethod: user.wallet.recoveryMethod,
walletClientType: user.wallet.clientType,
appleSubject: user.apple ? user.apple.subject : null,
appleEmail: user.apple ? user.apple.email : null,
discordSubject: user.discord ? user.discord.subject : null,
discordEmail: user.discord ? user.discord.email : null,
discordUsername: user.discord ? user.discord.username : null,
farcasterFid: user.farcaster ? user.farcaster.fid : null,
farcasterDisplayName: user.farcaster
? user.farcaster.displayName
: null,
farcasterOwnerAddress: user.farcaster
? user.farcaster.ownerAddress
: null,
farcasterPfp: user.farcaster ? user.farcaster.pfp : null,
farcasterSignerPublicKey: user.farcaster
? user.farcaster.signerPublicKey
: null,
farcasterUrl: user.farcaster ? user.farcaster.url : null,
farcasterUsername: user.farcaster ? user.farcaster.username : null,
githubSubject: user.github ? user.github.subject : null,
githubEmail: user.github ? user.github.email : null,
githubName: user.github ? user.github.name : null,
githubUsername: user.github ? user.github.username : null,
googleEmail: user.google ? user.google.email : null,
googleName: user.github ? user.google.name : null,
googleSubject: user.github ? user.google.subject : null,
instagramSubject: user.instagram ? user.instagram.subject : null,
instagramUsername: user.instagram ? user.instagram.username : null,
linkedinEmail: user.linkedin ? user.linkedin.email : null,
linkedinName: user.linkedin ? user.linkedin.name : null,
linkedinSubject: user.linkedin ? user.linkedin.subject : null,
linkedinVanityName: user.linkedin ? user.linkedin.vanityName : null,
spotifyEmail: user.spotify ? user.spotify.email : null,
spotifyName: user.spotify ? user.spotify.name : null,
spotifySubject: user.spotify ? user.spotifySubject : null,
telegramFirstName: user.telegram ? user.telegram.firstName : null,
telegramLastName: user.telegram ? user.telegram.lastName : null,
telegramPhotoUrl: user.telegram ? user.telegram.photoUrl : null,
telegramUserId: user.telegram ? user.telegram.userId : null,
telegramUsername: user.telegram ? user.telegram.username : null,
tiktokName: user.tiktok ? user.tiktok.name : null,
tiktokSubject: user.tiktok ? user.tiktok.subject : null,
tiktokUsername: user.tiktok ? user.tiktok.username : null,
twitterName: user.twitter ? user.twitter.name : null,
twitterSubject: user.twitter ? user.twitter.subject : null,
twitterProfilePictureUrl: user.twitter
? user.twitter.profilePictureUrl
: null,
twitterUsername: user.twitter ? user.twitter.username : null,
dynamoDBUserID: user.dynamoDBUserID,
username: user.username,
email: user.email,
bio: user.bio,
instagramPublicUrl: user.instagramPublicUrl,
xPublicUrl: user.xPublicUrl,
tiktokPublicUrl: user.tiktokPublicUrl,
personalWebsiteUrl: user.personalWebsiteUrl,
firstLogin: user.firstLogin,
avatarUrl: user.avatarUrl,
bannerImageUrl: user.bannerImageUrl,
imageSettings: user.imageSettings,
city: user.city,
country: user.country,
lat: user.lat,
lng: user.lng,
isCreator: user.isCreator,
gender: user.gender,
phone: user.phone ? user.phone : null,
isVerified: user.isVerified,
signupCompleted: user.signupCompleted,
isPfpNft: user.isPfpNft,
pfp: user.pfp ? user.pfp : null,
proposalNotificationPush: user.proposalNotificationPush,
proposalNotificationSystem: user.proposalNotificationSystem,
orderNotificationPush: user.orderNotificationPush,
orderNotificationSystem: user.orderNotificationSystem,
followNotificationPush: user.followNotificationPush,
followNotificationSystem: user.followNotificationSystem,
collectionNotificationPush: user.collectionNotificationPush,
collectionNotificationSystem: user.collectionNotificationSystem,
generalNotificationPush: user.generalNotificationPush,
generalNotificationSystem: user.generalNotificationSystem,
accountSuspended: user.accountSuspended,
e2ePublicKey: user.e2ePublicKey,
e2eSecret, //this info comes from the backend. This data is never stored in the local DB, it exists only in memory to make harder the access to it
e2eSecretIV, //this info comes from the backend. This data is never stored in the local DB, it exists only in memory to make harder the access to it
createdAt: user.createdAt,
updatedAt: user.updatedAt ? user.updatedAt : null,
deletedAt: user.deletedAt ? user.deletedAt : null,
allowNotification: user.allowNotification,
allowNotificationSound: user.allowNotificationSound,
visibility: user.visibility,
onlineStatus: user.onlineStatus,
allowReadReceipt: user.allowReadReceipt,
allowReceiveMessageFrom: user.allowReceiveMessageFrom,
allowAddToGroupsFrom: user.allowAddToGroupsFrom,
allowGroupsSuggestion: user.allowGroupsSuggestion,
});
}
catch (error) {
console.log("Error during rebuilding phase for account.");
console.error(error);
if ("statusCode" in error && error.statusCode === 401)
yield Auth._instance.logout();