@doany-ai/sdk 0.1.0
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 +22 -0
- package/README.md +60 -0
- package/dist/client.d.ts +96 -0
- package/dist/client.js +395 -0
- package/dist/client.types.d.ts +149 -0
- package/dist/client.types.js +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +5 -0
- package/dist/modules/agents.d.ts +2 -0
- package/dist/modules/agents.js +89 -0
- package/dist/modules/agents.types.d.ts +397 -0
- package/dist/modules/agents.types.js +1 -0
- package/dist/modules/ai-gateway.d.ts +2 -0
- package/dist/modules/ai-gateway.js +13 -0
- package/dist/modules/ai-gateway.types.d.ts +88 -0
- package/dist/modules/ai-gateway.types.js +1 -0
- package/dist/modules/analytics.d.ts +20 -0
- package/dist/modules/analytics.js +284 -0
- package/dist/modules/analytics.types.d.ts +122 -0
- package/dist/modules/analytics.types.js +1 -0
- package/dist/modules/app-logs.d.ts +11 -0
- package/dist/modules/app-logs.js +27 -0
- package/dist/modules/app-logs.types.d.ts +46 -0
- package/dist/modules/app-logs.types.js +1 -0
- package/dist/modules/app.types.d.ts +142 -0
- package/dist/modules/app.types.js +1 -0
- package/dist/modules/auth.d.ts +13 -0
- package/dist/modules/auth.js +240 -0
- package/dist/modules/auth.types.d.ts +517 -0
- package/dist/modules/auth.types.js +1 -0
- package/dist/modules/connectors.d.ts +20 -0
- package/dist/modules/connectors.js +98 -0
- package/dist/modules/connectors.types.d.ts +376 -0
- package/dist/modules/connectors.types.js +1 -0
- package/dist/modules/custom-integrations.d.ts +11 -0
- package/dist/modules/custom-integrations.js +32 -0
- package/dist/modules/custom-integrations.types.d.ts +89 -0
- package/dist/modules/custom-integrations.types.js +1 -0
- package/dist/modules/entities.d.ts +20 -0
- package/dist/modules/entities.js +163 -0
- package/dist/modules/entities.types.d.ts +702 -0
- package/dist/modules/entities.types.js +1 -0
- package/dist/modules/functions.d.ts +12 -0
- package/dist/modules/functions.js +79 -0
- package/dist/modules/functions.types.d.ts +150 -0
- package/dist/modules/functions.types.js +1 -0
- package/dist/modules/integrations.d.ts +11 -0
- package/dist/modules/integrations.js +77 -0
- package/dist/modules/integrations.types.d.ts +418 -0
- package/dist/modules/integrations.types.js +1 -0
- package/dist/modules/sso.d.ts +11 -0
- package/dist/modules/sso.js +22 -0
- package/dist/modules/sso.types.d.ts +68 -0
- package/dist/modules/sso.types.js +1 -0
- package/dist/modules/types.d.ts +5 -0
- package/dist/modules/types.js +5 -0
- package/dist/modules/users.d.ts +16 -0
- package/dist/modules/users.js +23 -0
- package/dist/types.d.ts +72 -0
- package/dist/types.js +1 -0
- package/dist/utils/auth-utils.d.ts +117 -0
- package/dist/utils/auth-utils.js +189 -0
- package/dist/utils/auth-utils.types.d.ts +146 -0
- package/dist/utils/auth-utils.types.js +1 -0
- package/dist/utils/axios-client.d.ts +100 -0
- package/dist/utils/axios-client.js +202 -0
- package/dist/utils/axios-client.types.d.ts +28 -0
- package/dist/utils/axios-client.types.js +1 -0
- package/dist/utils/common.d.ts +4 -0
- package/dist/utils/common.js +11 -0
- package/dist/utils/sharedInstance.d.ts +1 -0
- package/dist/utils/sharedInstance.js +15 -0
- package/dist/utils/socket-utils.d.ts +47 -0
- package/dist/utils/socket-utils.js +170 -0
- package/package.json +54 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal
|
|
3
|
+
*/
|
|
4
|
+
export interface AppMessageContent {
|
|
5
|
+
content?: string;
|
|
6
|
+
file_urls?: string[];
|
|
7
|
+
custom_context?: unknown;
|
|
8
|
+
additional_message_params?: Record<string, unknown>;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export interface AppConversationMessage extends AppMessageContent {
|
|
15
|
+
id?: string | null;
|
|
16
|
+
role?: "user" | "assistant" | string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
export interface AppConversationLike {
|
|
22
|
+
id?: string | null;
|
|
23
|
+
messages?: AppMessageContent[] | null;
|
|
24
|
+
model?: string;
|
|
25
|
+
functions_fail_silently?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export interface DenoProjectLike {
|
|
31
|
+
project_id: string;
|
|
32
|
+
project_name: string;
|
|
33
|
+
app_id: string;
|
|
34
|
+
deployment_name_to_info: Record<string, {
|
|
35
|
+
id: string;
|
|
36
|
+
code: string;
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export interface AppLike {
|
|
43
|
+
id?: string;
|
|
44
|
+
conversation?: AppConversationLike | null;
|
|
45
|
+
app_stage?: "pending" | "product_flows" | "ready" | string;
|
|
46
|
+
created_date?: string;
|
|
47
|
+
updated_date?: string;
|
|
48
|
+
created_by?: string;
|
|
49
|
+
organization_id?: string;
|
|
50
|
+
name?: string;
|
|
51
|
+
user_description?: string;
|
|
52
|
+
entities?: Record<string, any>;
|
|
53
|
+
additional_user_data_schema?: any;
|
|
54
|
+
pages?: {
|
|
55
|
+
[key: string]: string;
|
|
56
|
+
};
|
|
57
|
+
components: {
|
|
58
|
+
[key: string]: any;
|
|
59
|
+
};
|
|
60
|
+
layout?: string;
|
|
61
|
+
globals_css?: string;
|
|
62
|
+
agents?: Record<string, any>;
|
|
63
|
+
logo_url?: string;
|
|
64
|
+
slug?: string;
|
|
65
|
+
public_settings?: "private_with_login" | "public_with_login" | "public_without_login" | "workspace_with_login" | string;
|
|
66
|
+
is_blocked?: boolean;
|
|
67
|
+
github_repo_url?: string;
|
|
68
|
+
main_page?: string;
|
|
69
|
+
installable_integrations?: any;
|
|
70
|
+
backend_project?: DenoProjectLike;
|
|
71
|
+
last_deployed_at?: string;
|
|
72
|
+
is_remixable?: boolean;
|
|
73
|
+
remixed_from_app_id?: string;
|
|
74
|
+
hide_entity_created_by?: boolean;
|
|
75
|
+
platform_version?: number;
|
|
76
|
+
enable_username_password?: boolean;
|
|
77
|
+
auth_config?: AuthConfigLike;
|
|
78
|
+
status?: {
|
|
79
|
+
state?: string;
|
|
80
|
+
details?: any;
|
|
81
|
+
last_updated_date?: string;
|
|
82
|
+
};
|
|
83
|
+
custom_instructions?: any;
|
|
84
|
+
frozen_files?: string[];
|
|
85
|
+
deep_coding_mode?: boolean;
|
|
86
|
+
needs_to_add_diff?: boolean;
|
|
87
|
+
installed_integration_context_items?: any[];
|
|
88
|
+
model?: string;
|
|
89
|
+
is_starred?: boolean;
|
|
90
|
+
agents_enabled?: boolean;
|
|
91
|
+
categories?: string[];
|
|
92
|
+
functions?: any;
|
|
93
|
+
function_names?: string[];
|
|
94
|
+
user_entity?: UserEntityLike;
|
|
95
|
+
app_code_hash?: string;
|
|
96
|
+
has_backend_functions_enabled?: boolean;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* @internal
|
|
100
|
+
*/
|
|
101
|
+
export interface UserLike {
|
|
102
|
+
id?: string | null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* @internal
|
|
106
|
+
*/
|
|
107
|
+
export interface UserEntityLike {
|
|
108
|
+
type: string;
|
|
109
|
+
name: string;
|
|
110
|
+
title?: string;
|
|
111
|
+
properties?: {
|
|
112
|
+
role?: {
|
|
113
|
+
type?: string;
|
|
114
|
+
description?: string;
|
|
115
|
+
enum?: ("admin" | "user" | string)[];
|
|
116
|
+
};
|
|
117
|
+
email?: {
|
|
118
|
+
type?: string;
|
|
119
|
+
description?: string;
|
|
120
|
+
};
|
|
121
|
+
full_name?: {
|
|
122
|
+
type?: string;
|
|
123
|
+
description?: string;
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
required: string[];
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* @internal
|
|
130
|
+
*/
|
|
131
|
+
export interface AuthConfigLike {
|
|
132
|
+
enable_username_password?: boolean;
|
|
133
|
+
enable_google_login?: boolean;
|
|
134
|
+
enable_microsoft_login?: boolean;
|
|
135
|
+
enable_facebook_login?: boolean;
|
|
136
|
+
sso_provider_name?: string;
|
|
137
|
+
enable_sso_login?: boolean;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* @internal
|
|
141
|
+
*/
|
|
142
|
+
export type LoginInfoResponse = Pick<AppLike, "id" | "name" | "slug" | "logo_url" | "user_description" | "updated_date" | "created_date" | "auth_config" | "platform_version">;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AxiosInstance } from "axios";
|
|
2
|
+
import { AuthModule, AuthModuleOptions } from "./auth.types";
|
|
3
|
+
/**
|
|
4
|
+
* Creates the auth module for the Base44 SDK.
|
|
5
|
+
*
|
|
6
|
+
* @param axios - Axios instance for API requests
|
|
7
|
+
* @param functionsAxiosClient - Axios instance for functions API requests
|
|
8
|
+
* @param appId - Application ID
|
|
9
|
+
* @param options - Configuration options including server URLs
|
|
10
|
+
* @returns Auth module with authentication and user management methods
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export declare function createAuthModule(axios: AxiosInstance, functionsAxiosClient: AxiosInstance, appId: string, options: AuthModuleOptions): AuthModule;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
function isInsideIframe() {
|
|
2
|
+
if (typeof window === "undefined")
|
|
3
|
+
return false;
|
|
4
|
+
return window !== window.parent;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Opens a URL in a centered popup and waits for the backend to postMessage
|
|
8
|
+
* the auth result back. On success, redirects the current window to
|
|
9
|
+
* redirectUrl with the token params appended, preserving the same behaviour
|
|
10
|
+
* as a normal full-page redirect flow.
|
|
11
|
+
*
|
|
12
|
+
* @param url - The login URL to open in the popup (should include popup_origin).
|
|
13
|
+
* @param redirectUrl - Where to redirect after auth (the original fromUrl).
|
|
14
|
+
* @param expectedOrigin - The origin we expect the postMessage to come from.
|
|
15
|
+
*/
|
|
16
|
+
function loginViaPopup(url, redirectUrl, expectedOrigin) {
|
|
17
|
+
const width = 500;
|
|
18
|
+
const height = 600;
|
|
19
|
+
const left = Math.round(window.screenX + (window.outerWidth - width) / 2);
|
|
20
|
+
const top = Math.round(window.screenY + (window.outerHeight - height) / 2);
|
|
21
|
+
const popup = window.open(url, "base44_auth", `width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes`);
|
|
22
|
+
if (!popup) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const cleanup = () => {
|
|
26
|
+
window.removeEventListener("message", onMessage);
|
|
27
|
+
clearInterval(pollTimer);
|
|
28
|
+
if (!popup.closed)
|
|
29
|
+
popup.close();
|
|
30
|
+
};
|
|
31
|
+
const onMessage = (event) => {
|
|
32
|
+
var _a;
|
|
33
|
+
if (event.origin !== expectedOrigin)
|
|
34
|
+
return;
|
|
35
|
+
if (event.source !== popup)
|
|
36
|
+
return;
|
|
37
|
+
if (!((_a = event.data) === null || _a === void 0 ? void 0 : _a.access_token))
|
|
38
|
+
return;
|
|
39
|
+
cleanup();
|
|
40
|
+
const callbackUrl = new URL(redirectUrl);
|
|
41
|
+
const { access_token, is_new_user } = event.data;
|
|
42
|
+
callbackUrl.searchParams.set("access_token", access_token);
|
|
43
|
+
if (is_new_user != null) {
|
|
44
|
+
callbackUrl.searchParams.set("is_new_user", String(is_new_user));
|
|
45
|
+
}
|
|
46
|
+
window.location.href = callbackUrl.toString();
|
|
47
|
+
};
|
|
48
|
+
// Only used to detect the user closing the popup before auth completes
|
|
49
|
+
const pollTimer = setInterval(() => {
|
|
50
|
+
if (popup.closed)
|
|
51
|
+
cleanup();
|
|
52
|
+
}, 500);
|
|
53
|
+
window.addEventListener("message", onMessage);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Creates the auth module for the Base44 SDK.
|
|
57
|
+
*
|
|
58
|
+
* @param axios - Axios instance for API requests
|
|
59
|
+
* @param functionsAxiosClient - Axios instance for functions API requests
|
|
60
|
+
* @param appId - Application ID
|
|
61
|
+
* @param options - Configuration options including server URLs
|
|
62
|
+
* @returns Auth module with authentication and user management methods
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
export function createAuthModule(axios, functionsAxiosClient, appId, options) {
|
|
66
|
+
return {
|
|
67
|
+
// Get current user information
|
|
68
|
+
async me() {
|
|
69
|
+
return axios.get(`/apps/${appId}/entities/User/me`);
|
|
70
|
+
},
|
|
71
|
+
// Update current user data
|
|
72
|
+
async updateMe(data) {
|
|
73
|
+
return axios.put(`/apps/${appId}/entities/User/me`, data);
|
|
74
|
+
},
|
|
75
|
+
// Redirects the user to the app's login page
|
|
76
|
+
redirectToLogin(nextUrl) {
|
|
77
|
+
// This function only works in a browser environment
|
|
78
|
+
if (typeof window === "undefined") {
|
|
79
|
+
throw new Error("Login method can only be used in a browser environment");
|
|
80
|
+
}
|
|
81
|
+
// If nextUrl is not provided, use the current URL
|
|
82
|
+
const redirectUrl = nextUrl
|
|
83
|
+
? new URL(nextUrl, window.location.origin).toString()
|
|
84
|
+
: window.location.href;
|
|
85
|
+
// Build the login URL
|
|
86
|
+
const loginUrl = `${options.appBaseUrl}/login?from_url=${encodeURIComponent(redirectUrl)}`;
|
|
87
|
+
// Redirect to the login page
|
|
88
|
+
window.location.href = loginUrl;
|
|
89
|
+
},
|
|
90
|
+
// Redirects the user to a provider's login page
|
|
91
|
+
loginWithProvider(provider, fromUrl = "/") {
|
|
92
|
+
// Build the full redirect URL
|
|
93
|
+
const redirectUrl = new URL(fromUrl, window.location.origin).toString();
|
|
94
|
+
const queryParams = `app_id=${appId}&from_url=${encodeURIComponent(redirectUrl)}`;
|
|
95
|
+
// SSO uses a different URL structure with appId in the path
|
|
96
|
+
let authPath;
|
|
97
|
+
if (provider === "sso") {
|
|
98
|
+
authPath = `/apps/${appId}/auth/sso/login`;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
// Google is the default provider, so no provider path segment needed
|
|
102
|
+
const providerPath = provider === "google" ? "" : `/${provider}`;
|
|
103
|
+
authPath = `/apps/auth${providerPath}/login`;
|
|
104
|
+
}
|
|
105
|
+
const loginUrl = `${options.appBaseUrl}/api${authPath}?${queryParams}`;
|
|
106
|
+
// When running inside an iframe, use a popup to avoid OAuth providers
|
|
107
|
+
// blocking iframe navigation.
|
|
108
|
+
if (isInsideIframe()) {
|
|
109
|
+
const popupLoginUrl = `${loginUrl}&popup_origin=${encodeURIComponent(window.location.origin)}`;
|
|
110
|
+
return loginViaPopup(popupLoginUrl, redirectUrl, window.location.origin);
|
|
111
|
+
}
|
|
112
|
+
// Default: full-page redirect
|
|
113
|
+
window.location.href = loginUrl;
|
|
114
|
+
},
|
|
115
|
+
// Logout the current user
|
|
116
|
+
logout(redirectUrl) {
|
|
117
|
+
// Remove token from axios headers (always do this)
|
|
118
|
+
delete axios.defaults.headers.common["Authorization"];
|
|
119
|
+
// Only do the rest if in a browser environment
|
|
120
|
+
if (typeof window !== "undefined") {
|
|
121
|
+
// Remove token from localStorage
|
|
122
|
+
if (window.localStorage) {
|
|
123
|
+
try {
|
|
124
|
+
window.localStorage.removeItem("base44_access_token");
|
|
125
|
+
// Remove "token" that is set by the built-in SDK of platform version 2
|
|
126
|
+
window.localStorage.removeItem("token");
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
console.error("Failed to remove token from localStorage:", e);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Determine the from_url parameter
|
|
133
|
+
const fromUrl = redirectUrl || window.location.href;
|
|
134
|
+
// Redirect to server-side logout endpoint to clear HTTP-only cookies
|
|
135
|
+
const logoutUrl = `${options.appBaseUrl}/api/apps/auth/logout?from_url=${encodeURIComponent(fromUrl)}`;
|
|
136
|
+
window.location.href = logoutUrl;
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
// Set authentication token
|
|
140
|
+
setToken(token, saveToStorage = true) {
|
|
141
|
+
if (!token)
|
|
142
|
+
return;
|
|
143
|
+
// handle token change for axios clients
|
|
144
|
+
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
|
|
145
|
+
functionsAxiosClient.defaults.headers.common["Authorization"] = `Bearer ${token}`;
|
|
146
|
+
// Save token to localStorage if requested
|
|
147
|
+
if (saveToStorage &&
|
|
148
|
+
typeof window !== "undefined" &&
|
|
149
|
+
window.localStorage) {
|
|
150
|
+
try {
|
|
151
|
+
window.localStorage.setItem("base44_access_token", token);
|
|
152
|
+
// Set "token" that is set by the built-in SDK of platform version 2
|
|
153
|
+
window.localStorage.setItem("token", token);
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
console.error("Failed to save token to localStorage:", e);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
// Login using username and password
|
|
161
|
+
async loginViaEmailPassword(email, password, turnstileToken) {
|
|
162
|
+
var _a;
|
|
163
|
+
try {
|
|
164
|
+
const response = await axios.post(`/apps/${appId}/auth/login`, {
|
|
165
|
+
email,
|
|
166
|
+
password,
|
|
167
|
+
...(turnstileToken && { turnstile_token: turnstileToken }),
|
|
168
|
+
});
|
|
169
|
+
const { access_token, user } = response;
|
|
170
|
+
if (access_token) {
|
|
171
|
+
this.setToken(access_token);
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
access_token,
|
|
175
|
+
user,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
// Handle authentication errors and cleanup
|
|
180
|
+
if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 401) {
|
|
181
|
+
await this.logout();
|
|
182
|
+
}
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
// Verify if the current token is valid
|
|
187
|
+
async isAuthenticated() {
|
|
188
|
+
try {
|
|
189
|
+
await this.me();
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
// Invite a user to the app
|
|
197
|
+
inviteUser(userEmail, role) {
|
|
198
|
+
return axios.post(`/apps/${appId}/users/invite-user`, {
|
|
199
|
+
user_email: userEmail,
|
|
200
|
+
role,
|
|
201
|
+
});
|
|
202
|
+
},
|
|
203
|
+
// Register a new user account
|
|
204
|
+
register(payload) {
|
|
205
|
+
return axios.post(`/apps/${appId}/auth/register`, payload);
|
|
206
|
+
},
|
|
207
|
+
// Verify an OTP (One-time password) code
|
|
208
|
+
verifyOtp({ email, otpCode }) {
|
|
209
|
+
return axios.post(`/apps/${appId}/auth/verify-otp`, {
|
|
210
|
+
email,
|
|
211
|
+
otp_code: otpCode,
|
|
212
|
+
});
|
|
213
|
+
},
|
|
214
|
+
// Resend an OTP code to the user's email
|
|
215
|
+
resendOtp(email) {
|
|
216
|
+
return axios.post(`/apps/${appId}/auth/resend-otp`, { email });
|
|
217
|
+
},
|
|
218
|
+
// Request a password reset
|
|
219
|
+
resetPasswordRequest(email) {
|
|
220
|
+
return axios.post(`/apps/${appId}/auth/reset-password-request`, {
|
|
221
|
+
email,
|
|
222
|
+
});
|
|
223
|
+
},
|
|
224
|
+
// Reset password using a reset token
|
|
225
|
+
resetPassword({ resetToken, newPassword }) {
|
|
226
|
+
return axios.post(`/apps/${appId}/auth/reset-password`, {
|
|
227
|
+
reset_token: resetToken,
|
|
228
|
+
new_password: newPassword,
|
|
229
|
+
});
|
|
230
|
+
},
|
|
231
|
+
// Change the user's password
|
|
232
|
+
changePassword({ userId, currentPassword, newPassword, }) {
|
|
233
|
+
return axios.post(`/apps/${appId}/auth/change-password`, {
|
|
234
|
+
user_id: userId,
|
|
235
|
+
current_password: currentPassword,
|
|
236
|
+
new_password: newPassword,
|
|
237
|
+
});
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
}
|