test: combine and modernize private-network checks; remove legacy disabled variants

This commit is contained in:
Scott Idem
2026-02-24 16:38:36 -05:00
parent 7f9f93765d
commit f2c426b595
14 changed files with 877 additions and 714 deletions

View File

@@ -0,0 +1,132 @@
# Aether API V3 Frontend Integration Guide (Svelte/TypeScript)
This guide defines the standards for interacting with the **Aether API V3 CRUD** and **Action** endpoints.
---
## 1. Authentication and Security (Mandatory)
V3 architecture enforces strict **Multi-Tenant Isolation** and **Machine Authorization**. Requests require two levels of validation.
### A. The "Entry Ticket" (API Key)
**Mandatory for all requests.** identifies the application or client.
* **Header:** `x-aether-api-key: <your_app_key>`
* **Status Code:** `403 Forbidden` if missing or invalid.
### B. The "Visa" (Account Context)
Required for any non-public data (Journals, Badges, Users, etc.).
1. **Standard Access**: Provide the `x-account-id` (the random string ID).
* **Header:** `x-account-id: <account_id>`
2. **Administrative Bypass**: For authorized scripts needing global access.
* **Header:** `x-no-account-id: bypass`
3. **Token Access**: Provide a **JWT** in the query string.
* **Query Param:** `?jwt=<token>`
> [!CAUTION]
> **UNSUPPORTED HEADERS:** The header `x-aether-api-token` is **NOT recognized** by the V3 API. If you send it, the backend will treat you as a guest and block access to private data.
---
## 2. Bootstrapping (The FQDN Handshake)
When the frontend first loads and doesn't know the `account_id`, it performs a "handshake" using its domain name.
**Endpoint:** `POST /v3/crud/site_domain/search`
**Body:**
```json
{
"and": [
{ "field": "fqdn", "op": "eq", "value": "demo.oneskyit.com" }
]
}
```
**Results:**
* Returns 200 + a list containing the `account_id` (random string ID) and `site_id` (random string ID).
* ** デザイン Choice:** If the domain is not found, it returns **200 OK with an empty list `[]`**. It is NOT a 404.
---
## 3. Standard CRUD Patterns
### A. GET by ID
Used when the ID is known.
* **Endpoint:** `GET /v3/crud/{obj_type}/{id}`
* **Security:** Returns 403 if the record doesn't belong to your `x-account-id`.
### B. POST Search
The primary way to retrieve data.
* **Endpoint:** `POST /v3/crud/{obj_type}/search`
* **Security:** Automatically filters results to only show records belonging to your `x-account-id`. If no account context is provided, it will return **0 records** for private objects.
### C. POST Create / PATCH Update
Modify data in the system.
* **Endpoints:**
* `POST /v3/crud/{obj_type}/`
* `PATCH /v3/crud/{obj_type}/{id}`
* **Strict Mode (Default):** The API validates your payload against the Pydantic model. If you send fields that do not exist in the model, the database might return a 400 "Unknown column" error.
* **Permissive Mode (Header):** To allow the frontend to send "extra" fields (like local UI state) without causing errors, use the following header:
* **Header:** `x-ae-ignore-extra-fields: true`
* **Behavior:** When set to `true`, the backend will automatically strip any fields from the payload that are not defined in the object's model before attempting to save to the database.
---
## 4. V3 Uniform Lookup System
The V3 Lookup system provides a hierarchical, deduplicated interface for standardized tables (Countries, Timezones, etc.). It supports global defaults, account overrides, and site-specific whitelisting.
### A. List Lookups
Retrieve a ranked and filtered list of lookup items.
* **Endpoint:** `GET /v3/lookup/{lu_type}/list`
* **Available Types:** `country`, `country_subdivision`, `time_zone`
* **Parameters:**
* `site_id` (Optional): Random ID of the site to apply a **Whitelist Policy**.
* `only_priority` (Optional): Set to `true` to return only high-priority items (e.g., common time zones).
* `for_type` / `for_id` (Optional): Context for object-specific overrides.
* `include_disabled` (Optional): Set to `true` to see shadowed/disabled records.
### B. Resolve Identity
Resolves a string (code, group, or name) to a single record.
* **Endpoint:** `GET /v3/lookup/{lu_type}/resolve?q=VALUE`
* **Usage:** Use this when you have an external code (e.g., ISO "US") and need the full Aether record.
### C. Site Whitelist Policy
To limit lookups for a specific site, add a `lookup_policy` to the `site.cfg_json` field.
**Schema:**
```json
{
"lookup_policy": {
"country": ["US", "CA", "GB"],
"time_zone": ["America/New_York"]
}
}
```
*Note: Whitelist values must match the `group` field in the database.*
---
## 5. Event File Data Retrieval (Hosted Files)
Every Event File (`event_file`) **must** have a linked Hosted File (`hosted_file`). The Hosted File itself is a metadata record for binary content (files), which is accessed via separate Action endpoints (e.g., `/v3/action/hosted_file/download`). This API endpoint provides metadata about the associated hosted file. To retrieve this additional metadata:
* **Endpoint:** `GET /v3/crud/event_file/{event_file_id}`
* **Query Parameter:** Add `inc_hosted_file=true`
* Example: `/v3/crud/event_file/<event_file_id>?inc_hosted_file=true`
**Response Impact:**
1. **Top-Level Convenience Fields:** The response will include top-level fields for commonly needed hosted file data. These are populated directly from the SQL view via JOINs.
* `hosted_file_hash_sha256` (string)
* `hosted_file_subdirectory_path` (string)
* `hosted_file_content_type` (string)
* `hosted_file_size` (string - in bytes)
2. **Nested Hosted File Object:** A full `hosted_file` object will be nested under the `hosted_file` key. This object (`Hosted_File_Base` model) will contain all its standard fields, including `id` (random string ID), `hash_sha256`, `content_type`, `size`, etc.
---
## 5. Troubleshooting 403 Forbidden
If you receive a 403 on a valid ID:
1. Verify `x-aether-api-key` is correct.
2. Ensure you are sending `x-account-id` and NOT `x-aether-api-token`.
3. Verify the record actually belongs to the account ID you are sending.
4. Check if the object is marked `public_read: True` in the registry. (Posts and Archive Content allow guest access; Journals and Badges do not).

View File

@@ -170,13 +170,12 @@ export async function create_ae_obj__archive_content({
return null;
}
const result = await api.create_ae_obj_v3({
const result = await api.create_nested_obj_v3({
api_cfg,
obj_type: 'archive_content',
fields: {
archive_id_random: archive_id,
...data_kv
},
parent_type: 'archive',
parent_id: archive_id,
child_type: 'archive_content',
fields: data_kv,
params,
log_lvl
});

View File

@@ -241,108 +241,115 @@ export async function load_ae_obj_li__event_badge_template({
// Updated 2026-01-20 to V3
export async function create_ae_obj__event_badge_template({
api_cfg,
event_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.create_ae_obj_v3({
api_cfg,
obj_type: 'event_badge_template',
fields: {
event_id: event_id,
...data_kv
},
log_lvl
});
const result = await api.create_nested_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_badge_template',
fields: { ...data_kv },
log_lvl
});
if (result && try_cache) {
const processed_obj_li = await process_ae_badge_template_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'badge_template',
obj_li: processed_obj_li,
properties_to_save,
log_lvl
});
}
return result;
if (result && try_cache) {
const processed_obj_li = await process_ae_badge_template_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'badge_template',
obj_li: processed_obj_li,
properties_to_save,
log_lvl
});
}
return result;
}
// Updated 2026-01-20 to V3
export async function delete_ae_obj_id__event_badge_template({
api_cfg,
event_badge_template_id,
method = 'delete',
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
event_badge_template_id,
method = 'delete',
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_badge_template_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id: string;
event_badge_template_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.delete_ae_obj_v3({
api_cfg,
obj_type: 'event_badge_template',
obj_id: event_badge_template_id,
method,
log_lvl
});
const result = await api.delete_nested_ae_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_badge_template',
obj_id: event_badge_template_id,
method,
log_lvl
});
if (try_cache) {
await db_events.badge_template.delete(event_badge_template_id);
}
return result;
if (try_cache) {
await db_events.badge_template.delete(event_badge_template_id);
}
return result;
}
// Updated 2026-01-20 to V3
export async function update_ae_obj__event_badge_template({
api_cfg,
event_badge_template_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
event_badge_template_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_badge_template_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id: string;
event_badge_template_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.update_ae_obj_v3({
api_cfg,
obj_type: 'event_badge_template',
obj_id: event_badge_template_id,
fields: data_kv,
log_lvl
});
const result = await api.update_nested_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_badge_template',
obj_id: event_badge_template_id,
fields: data_kv,
log_lvl
});
if (result && try_cache) {
const processed_obj_li = await process_ae_badge_template_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'badge_template',
obj_li: processed_obj_li,
properties_to_save,
log_lvl
});
}
return result;
if (result && try_cache) {
const processed_obj_li = await process_ae_badge_template_props({
obj_li: [result],
log_lvl
});
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'badge_template',
obj_li: processed_obj_li,
properties_to_save,
log_lvl
});
}
return result;
}
// Updated 2026-01-20 to V3

View File

@@ -178,133 +178,140 @@ export async function load_ae_obj_li__event_device({
// Updated 2026-01-20 to V3
export async function create_ae_obj__event_device({
api_cfg,
event_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventDevice | null> {
if (log_lvl) {
console.log(`*** create_ae_obj__event_device() *** [V3] event_id=${event_id}`);
}
if (log_lvl) {
console.log(`*** create_ae_obj__event_device() *** [V3] event_id=${event_id}`);
}
const result = await api.create_ae_obj_v3({
api_cfg,
obj_type: 'event_device',
fields: {
event_id,
...data_kv
},
log_lvl
});
const result = await api.create_nested_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_device',
fields: { ...data_kv },
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_device_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (result) {
const processed = await process_ae_obj__event_device_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'device',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'device',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
return null;
}
// Updated 2026-01-20 to V3
export async function delete_ae_obj_id__event_device({
api_cfg,
event_device_id,
method = 'delete',
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
event_device_id,
method = 'delete',
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_device_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id: string;
event_device_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
}) {
if (log_lvl) {
console.log(`*** delete_ae_obj_id__event_device() *** [V3] id=${event_device_id}`);
}
if (log_lvl) {
console.log(`*** delete_ae_obj_id__event_device() *** [V3] id=${event_device_id}`);
}
const result = await api.delete_ae_obj_v3({
api_cfg,
obj_type: 'event_device',
obj_id: event_device_id,
method,
log_lvl
});
const result = await api.delete_nested_ae_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_device',
obj_id: event_device_id,
method,
log_lvl
});
if (try_cache) {
await db_events.device.delete(event_device_id);
}
if (try_cache) {
await db_events.device.delete(event_device_id);
}
return result;
return result;
}
// Updated 2026-01-20 to V3
export async function update_ae_obj__event_device({
api_cfg,
event_device_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
event_device_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_device_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id: string;
event_device_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventDevice | null> {
if (log_lvl) {
console.log(`*** update_ae_obj__event_device() *** [V3] id=${event_device_id}`);
}
if (log_lvl) {
console.log(`*** update_ae_obj__event_device() *** [V3] id=${event_device_id}`);
}
const result = await api.update_ae_obj_v3({
api_cfg,
obj_type: 'event_device',
obj_id: event_device_id,
fields: data_kv,
log_lvl
});
const result = await api.update_nested_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_device',
obj_id: event_device_id,
fields: data_kv,
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_device_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (result) {
const processed = await process_ae_obj__event_device_props({
obj_li: [result],
log_lvl
});
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'device',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'device',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
return null;
}
// Updated 2026-01-20 to V3

View File

@@ -177,78 +177,113 @@ async function _handle_nested_loads(location_obj: any, { api_cfg, inc_file_li, i
// Updated 2026-01-20 to V3
export async function create_ae_obj__event_location({
api_cfg,
event_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventLocation | null> {
const result = await api.create_ae_obj_v3({
api_cfg, obj_type: 'event_location',
fields: { event_id, ...data_kv },
log_lvl
});
const result = await api.create_nested_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_location',
fields: { ...data_kv },
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_location_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'location', obj_li: [processed_obj], properties_to_save, log_lvl });
}
return processed_obj;
}
return null;
if (result) {
const processed = await process_ae_obj__event_location_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'location',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
}
// Updated 2026-01-20 to V3
export async function delete_ae_obj_id__event_location({
api_cfg,
event_location_id,
method = 'delete',
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
event_location_id,
method = 'delete',
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_location_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id: string;
event_location_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.delete_ae_obj_v3({ api_cfg, obj_type: 'event_location', obj_id: event_location_id, method, log_lvl });
if (try_cache) await db_events.location.delete(event_location_id);
return result;
const result = await api.delete_nested_ae_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_location',
obj_id: event_location_id,
method,
log_lvl
});
if (try_cache) await db_events.location.delete(event_location_id);
return result;
}
// Updated 2026-01-20 to V3
export async function update_ae_obj__event_location({
api_cfg,
event_location_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_id,
event_location_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_location_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_id: string;
event_location_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventLocation | null> {
const result = await api.update_ae_obj_v3({ api_cfg, obj_type: 'event_location', obj_id: event_location_id, fields: data_kv, log_lvl });
if (result) {
const processed = await process_ae_obj__event_location_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'location', obj_li: [processed_obj], properties_to_save, log_lvl });
}
return processed_obj;
}
return null;
const result = await api.update_nested_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_location',
obj_id: event_location_id,
fields: data_kv,
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_location_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'location',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
}
// Updated 2026-01-20 to V3

View File

@@ -197,35 +197,42 @@ async function _refresh_presentation_li_background({ api_cfg, for_obj_type, for_
// Updated 2026-01-20 to V3
export async function create_ae_obj__event_presentation({
api_cfg,
event_id,
event_session_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_session_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_id: string;
event_session_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_session_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventPresentation | null> {
const result = await api.create_ae_obj_v3({
api_cfg, obj_type: 'event_presentation',
fields: { event_id, event_session_id, ...data_kv },
log_lvl
});
const result = await api.create_nested_obj_v3({
api_cfg,
for_obj_type: 'event_session',
for_obj_id: event_session_id,
obj_type: 'event_presentation',
fields: { ...data_kv },
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_presentation_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'presentation', obj_li: [processed_obj], properties_to_save, log_lvl });
}
return processed_obj;
}
return null;
if (result) {
const processed = await process_ae_obj__event_presentation_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'presentation',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
}
// Updated 2026-01-20 to V3

View File

@@ -173,82 +173,113 @@ async function _refresh_presenter_li_background({ api_cfg, for_obj_type, for_obj
// Updated 2026-01-20 to V3
export async function create_ae_obj__event_presenter({
api_cfg,
event_id,
event_session_id,
event_presentation_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_presentation_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_id: string;
event_session_id: string;
event_presentation_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_presentation_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventPresenter | null> {
const result = await api.create_ae_obj_v3({
api_cfg, obj_type: 'event_presenter',
fields: { event_id, event_session_id, event_presentation_id, ...data_kv },
log_lvl
});
const result = await api.create_nested_obj_v3({
api_cfg,
for_obj_type: 'event_presentation',
for_obj_id: event_presentation_id,
obj_type: 'event_presenter',
fields: { ...data_kv },
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_presenter_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'presenter', obj_li: [processed_obj], properties_to_save, log_lvl });
}
return processed_obj;
}
return null;
if (result) {
const processed = await process_ae_obj__event_presenter_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'presenter',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
}
// Updated 2026-01-20 to V3
export async function delete_ae_obj_id__event_presenter({
api_cfg,
event_presenter_id,
method = 'delete',
try_cache = true,
log_lvl = 0
api_cfg,
event_presentation_id,
event_presenter_id,
method = 'delete',
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_presenter_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_presentation_id: string;
event_presenter_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.delete_ae_obj_v3({ api_cfg, obj_type: 'event_presenter', obj_id: event_presenter_id, method, log_lvl });
if (try_cache) await db_events.presenter.delete(event_presenter_id);
return result;
const result = await api.delete_nested_ae_obj_v3({
api_cfg,
for_obj_type: 'event_presentation',
for_obj_id: event_presentation_id,
obj_type: 'event_presenter',
obj_id: event_presenter_id,
method,
log_lvl
});
if (try_cache) await db_events.presenter.delete(event_presenter_id);
return result;
}
// Updated 2026-01-20 to V3
export async function update_ae_obj__event_presenter({
api_cfg,
event_presenter_id,
data_kv,
try_cache = true,
log_lvl = 0
api_cfg,
event_presentation_id,
event_presenter_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
event_presenter_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
event_presentation_id: string;
event_presenter_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventPresenter | null> {
const result = await api.update_ae_obj_v3({ api_cfg, obj_type: 'event_presenter', obj_id: event_presenter_id, fields: data_kv, log_lvl });
if (result) {
const processed = await process_ae_obj__event_presenter_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'presenter', obj_li: [processed_obj], properties_to_save, log_lvl });
}
return processed_obj;
}
return null;
const result = await api.update_nested_obj_v3({
api_cfg,
for_obj_type: 'event_presentation',
for_obj_id: event_presentation_id,
obj_type: 'event_presenter',
obj_id: event_presenter_id,
fields: data_kv,
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_presenter_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'presenter',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
}
// Updated 2026-01-21 to Restore Full Aether Search Logic

View File

@@ -249,47 +249,110 @@ async function _refresh_session_li_background({ api_cfg, for_obj_type, for_obj_i
}
export async function create_ae_obj__event_session({
api_cfg, event_id, data_kv, try_cache = true, log_lvl = 0
api_cfg,
event_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any; event_id: string; data_kv: key_val; try_cache?: boolean; log_lvl?: number;
api_cfg: any;
event_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventSession | null> {
const result = await api.create_ae_obj_v3({ api_cfg, obj_type: 'event_session', fields: { event_id, ...data_kv }, log_lvl });
if (result) {
const processed = await process_ae_obj__event_session_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'session', obj_li: [processed_obj], properties_to_save, log_lvl });
}
return processed_obj;
}
return null;
const result = await api.create_nested_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_session',
fields: { ...data_kv },
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_session_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'session',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
}
export async function delete_ae_obj_id__event_session({
api_cfg, event_session_id, method = 'delete', try_cache = true, log_lvl = 0
api_cfg,
event_id,
event_session_id,
method = 'delete',
try_cache = true,
log_lvl = 0
}: {
api_cfg: any; event_session_id: string; method?: 'delete' | 'soft_delete' | 'disable' | 'hide'; try_cache?: boolean; log_lvl?: number;
api_cfg: any;
event_id: string;
event_session_id: string;
method?: 'delete' | 'soft_delete' | 'disable' | 'hide';
try_cache?: boolean;
log_lvl?: number;
}) {
const result = await api.delete_ae_obj_v3({ api_cfg, obj_type: 'event_session', obj_id: event_session_id, method, log_lvl });
if (try_cache) await db_events.session.delete(event_session_id);
return result;
const result = await api.delete_nested_ae_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_session',
obj_id: event_session_id,
method,
log_lvl
});
if (try_cache) await db_events.session.delete(event_session_id);
return result;
}
export async function update_ae_obj__event_session({
api_cfg, event_session_id, data_kv, try_cache = true, log_lvl = 0
api_cfg,
event_id,
event_session_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any; event_session_id: string; data_kv: key_val; try_cache?: boolean; log_lvl?: number;
api_cfg: any;
event_id: string;
event_session_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventSession | null> {
const result = await api.update_ae_obj_v3({ api_cfg, obj_type: 'event_session', obj_id: event_session_id, fields: data_kv, log_lvl });
if (result) {
const processed = await process_ae_obj__event_session_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({ db_instance: db_events, table_name: 'session', obj_li: [processed_obj], properties_to_save, log_lvl });
}
return processed_obj;
}
return null;
const result = await api.update_nested_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_session',
obj_id: event_session_id,
fields: data_kv,
log_lvl
});
if (result) {
const processed = await process_ae_obj__event_session_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'session',
obj_li: [processed_obj],
properties_to_save,
log_lvl
});
}
return processed_obj;
}
return null;
}
export async function search__event_session({

View File

@@ -295,65 +295,87 @@ async function _refresh_exhibit_li_background({ api_cfg, event_id, enabled, hidd
* Exhibit Create (V3)
*/
export async function create_ae_obj__exhibit({
api_cfg, event_id, data_kv, try_cache = true, log_lvl = 0
api_cfg,
event_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any; event_id: string; data_kv: key_val; try_cache?: boolean; log_lvl?: number;
api_cfg: any;
event_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventExhibit | null> {
const result = await api.create_ae_obj_v3({
api_cfg,
obj_type: 'event_exhibit',
fields: { event_id: event_id, ...data_kv },
log_lvl
});
const result = await api.create_nested_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_exhibit',
fields: { ...data_kv },
log_lvl
});
if (result) {
const processed = await process_ae_obj__exhibit_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'exhibit',
obj_li: [processed_obj],
properties_to_save: properties_to_save_exhibit,
log_lvl
});
}
return processed_obj;
}
return null;
if (result) {
const processed = await process_ae_obj__exhibit_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'exhibit',
obj_li: [processed_obj],
properties_to_save: properties_to_save_exhibit,
log_lvl
});
}
return processed_obj;
}
return null;
}
/**
* Exhibit Update (V3)
*/
export async function update_ae_obj__exhibit({
api_cfg, exhibit_id, data_kv, try_cache = true, log_lvl = 0
api_cfg,
event_id,
exhibit_id,
data_kv,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any; exhibit_id: string; data_kv: key_val; try_cache?: boolean; log_lvl?: number;
api_cfg: any;
event_id: string;
exhibit_id: string;
data_kv: key_val;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventExhibit | null> {
const result = await api.update_ae_obj_v3({
api_cfg,
obj_type: 'event_exhibit',
obj_id: exhibit_id,
fields: data_kv,
log_lvl
});
const result = await api.update_nested_obj_v3({
api_cfg,
for_obj_type: 'event',
for_obj_id: event_id,
obj_type: 'event_exhibit',
obj_id: exhibit_id,
fields: data_kv,
log_lvl
});
if (result) {
const processed = await process_ae_obj__exhibit_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'exhibit',
obj_li: [processed_obj],
properties_to_save: properties_to_save_exhibit,
log_lvl
});
}
return processed_obj;
}
return null;
if (result) {
const processed = await process_ae_obj__exhibit_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'exhibit',
obj_li: [processed_obj],
properties_to_save: properties_to_save_exhibit,
log_lvl
});
}
return processed_obj;
}
return null;
}
/**

View File

@@ -308,90 +308,95 @@ async function _refresh_tracking_li_background({ api_cfg, exhibit_id, enabled, h
* Lead Capture (V3)
*/
export async function create_ae_obj__exhibit_tracking({
api_cfg,
exhibit_id,
event_badge_id,
external_person_id,
group,
try_cache = true,
log_lvl = 0
api_cfg,
exhibit_id,
event_badge_id,
external_person_id,
group,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
exhibit_id: string;
event_badge_id: string;
external_person_id: string;
group?: string;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
exhibit_id: string;
event_badge_id: string;
external_person_id: string;
group?: string;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventExhibitTracking | null> {
const result = await api.create_ae_obj_v3({
api_cfg,
obj_type: 'event_exhibit_tracking',
fields: {
event_exhibit_id: exhibit_id,
event_badge_id: event_badge_id,
external_person_id,
group
},
log_lvl
});
const result = await api.create_nested_obj_v3({
api_cfg,
for_obj_type: 'event_exhibit',
for_obj_id: exhibit_id,
obj_type: 'event_exhibit_tracking',
fields: {
event_badge_id: event_badge_id,
external_person_id,
group
},
log_lvl
});
if (result) {
const processed = await process_ae_obj__exhibit_tracking_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'exhibit_tracking',
obj_li: [processed_obj],
properties_to_save: properties_to_save_exhibit_tracking,
log_lvl
});
}
return processed_obj;
}
return null;
if (result) {
const processed = await process_ae_obj__exhibit_tracking_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'exhibit_tracking',
obj_li: [processed_obj],
properties_to_save: properties_to_save_exhibit_tracking,
log_lvl
});
}
return processed_obj;
}
return null;
}
/**
* Lead Update (V3)
*/
export async function update_ae_obj__exhibit_tracking({
api_cfg,
exhibit_tracking_id,
data,
try_cache = true,
log_lvl = 0
api_cfg,
exhibit_id,
exhibit_tracking_id,
data,
try_cache = true,
log_lvl = 0
}: {
api_cfg: any;
exhibit_tracking_id: string;
data: any;
try_cache?: boolean;
log_lvl?: number;
api_cfg: any;
exhibit_id: string;
exhibit_tracking_id: string;
data: any;
try_cache?: boolean;
log_lvl?: number;
}): Promise<ae_EventExhibitTracking | null> {
const result = await api.update_ae_obj_v3({
api_cfg,
obj_type: 'event_exhibit_tracking',
obj_id: exhibit_tracking_id,
fields: data,
log_lvl
});
const result = await api.update_nested_obj_v3({
api_cfg,
for_obj_type: 'event_exhibit',
for_obj_id: exhibit_id,
obj_type: 'event_exhibit_tracking',
obj_id: exhibit_tracking_id,
fields: data,
log_lvl
});
if (result) {
const processed = await process_ae_obj__exhibit_tracking_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'exhibit_tracking',
obj_li: [processed_obj],
properties_to_save: properties_to_save_exhibit_tracking,
log_lvl
});
}
return processed_obj;
}
return null;
if (result) {
const processed = await process_ae_obj__exhibit_tracking_props({ obj_li: [result], log_lvl });
const processed_obj = processed[0];
if (try_cache) {
await db_save_ae_obj_li__ae_obj({
db_instance: db_events,
table_name: 'exhibit_tracking',
obj_li: [processed_obj],
properties_to_save: properties_to_save_exhibit_tracking,
log_lvl
});
}
return processed_obj;
}
return null;
}
/**

View File

@@ -1,62 +0,0 @@
import { test, expect } from '@playwright/test';
// This test attaches to the Chrome DevTools Protocol to capture low-level
// network events (including those from service workers). Run this with the
// real Chrome binary (channel: 'chrome') to reproduce the exact browser
// behavior that shows the PNA prompt.
test('CDP: detect private/local network requests and PNA preflights', async ({ page }) => {
const privateRequests: Array<any> = [];
function isPrivateHostname(hostname: string) {
if (!hostname) return false;
if (hostname === 'localhost' || hostname.endsWith('.localhost')) return true;
if (/^(127)\.|^(10)\.|^(192\.168)\.|^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname)) return true;
return false;
}
// Create a CDP session for the page to listen to Network events
const client = await page.context().newCDPSession(page);
await client.send('Network.enable');
client.on('Network.requestWillBeSent', (params) => {
try {
const url = params.request.url;
const hostname = new URL(url).hostname;
const headers = params.request.headers || {};
const pna = headers['access-control-request-private-network'] === 'true' || headers['Access-Control-Request-Private-Network'] === 'true';
if (isPrivateHostname(hostname) || pna) {
privateRequests.push({
url,
method: params.request.method,
initiator: params.initiator?.type,
pnaPreflight: pna,
headers
});
}
} catch (e) {}
});
// Also capture console messages to surface the same errors you see in Chrome.
const consoleMessages: string[] = [];
page.on('console', (msg) => {
consoleMessages.push(`${msg.type()}: ${msg.text()}`);
});
// Navigate to the site that triggers the behavior in your environment.
await page.goto('http://demo.localhost:5173/');
await page.waitForLoadState('networkidle');
if (privateRequests.length > 0) {
console.error('CDP detected private/local requests or PNA preflights:');
for (const r of privateRequests) console.error(JSON.stringify(r, null, 2));
}
if (consoleMessages.length) {
console.log('Captured console messages:');
consoleMessages.forEach((m) => console.log(m));
}
expect(privateRequests.length, 'No private/local network requests or PNA preflights should be made').toBe(0);
});

View File

@@ -0,0 +1,84 @@
import { test, expect } from '@playwright/test';
// Combined private-network detection test.
// Keeps both high-level request observation and low-level CDP capture in one
// place so you can run this when troubleshooting PNA / localhost access issues.
function isPrivateHostname(hostname: string) {
if (!hostname) return false;
if (hostname === 'localhost' || hostname.endsWith('.localhost')) return true;
if (/^(127)\.|^(10)\.|^(192\.168)\.|^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname)) return true;
return false;
}
test('detect private/local network requests and PNA preflights (combined)', async ({ page }) => {
const privateRequests: Array<any> = [];
const cdpPrivateRequests: Array<any> = [];
const consoleMessages: string[] = [];
page.on('console', (msg) => consoleMessages.push(`${msg.type()}: ${msg.text()}`));
page.on('request', (request) => {
try {
const url = new URL(request.url());
const headers = request.headers();
const hostname = url.hostname;
const pnaHeader = (headers['access-control-request-private-network'] || headers['Access-Control-Request-Private-Network']);
if (isPrivateHostname(hostname) || pnaHeader === 'true') {
privateRequests.push({ url: request.url(), method: request.method(), resourceType: request.resourceType(), pnaPreflight: pnaHeader === 'true', headers });
}
} catch (e) {
// ignore parse errors
}
});
page.on('requestfailed', (r) => {
try {
const url = new URL(r.url());
if (isPrivateHostname(url.hostname)) {
privateRequests.push({ url: r.url(), method: r.method(), failure: r.failure() });
}
} catch (e) {}
});
// CDP capture (may not be available in some browser channels)
try {
const client = await page.context().newCDPSession(page);
await client.send('Network.enable');
client.on('Network.requestWillBeSent', (params) => {
try {
const url = params.request.url;
const hostname = new URL(url).hostname;
const headers = params.request.headers || {};
const pna = headers['access-control-request-private-network'] === 'true' || headers['Access-Control-Request-Private-Network'] === 'true';
if (isPrivateHostname(hostname) || pna) {
cdpPrivateRequests.push({ url, method: params.request.method, initiator: params.initiator?.type, pnaPreflight: pna, headers });
}
} catch (e) {}
});
} catch (e) {
// CDP not available in this context; continue with high-level capture
}
await page.goto('http://demo.localhost:5173/');
await page.waitForLoadState('networkidle');
// Report findings for easier debugging
if (privateRequests.length > 0 || cdpPrivateRequests.length > 0 || consoleMessages.length > 0) {
if (consoleMessages.length) {
console.log('Console messages:');
consoleMessages.forEach((m) => console.log(m));
}
if (privateRequests.length) {
console.error('Detected private/local requests or PNA preflights (high-level):');
for (const r of privateRequests) console.error(JSON.stringify(r, null, 2));
}
if (cdpPrivateRequests.length) {
console.error('Detected private/local requests or PNA preflights (CDP):');
for (const r of cdpPrivateRequests) console.error(JSON.stringify(r, null, 2));
}
}
expect(privateRequests.length + cdpPrivateRequests.length, 'No private/local network requests or PNA preflights should be made').toBe(0);
});

View File

@@ -1,62 +0,0 @@
import { test, expect } from '@playwright/test';
// Detect requests to private/local address space or PNA preflight headers.
test('detect private/local network requests and PNA preflights', async ({ page }) => {
const privateRequests: Array<any> = [];
function isPrivateHostname(hostname: string) {
if (!hostname) return false;
// hostnames that resolve to local addresses or explicitly localhost
if (hostname === 'localhost' || hostname.endsWith('.localhost')) return true;
// IPv4 private ranges
if (/^(127)\.|^(10)\.|^(192\.168)\.|^172\.(1[6-9]|2[0-9]|3[0-1])\./.test(hostname)) return true;
return false;
}
page.on('request', (request) => {
try {
const url = new URL(request.url());
const headers = request.headers();
const hostname = url.hostname;
const pnaHeader = (headers['access-control-request-private-network'] || headers['Access-Control-Request-Private-Network']);
if (isPrivateHostname(hostname) || pnaHeader === 'true') {
privateRequests.push({
url: request.url(),
method: request.method(),
resourceType: request.resourceType(),
pnaPreflight: pnaHeader === 'true',
headers
});
}
} catch (e) {
// ignore parse errors
}
});
page.on('requestfailed', (r) => {
// capture failed requests that might be private and blocked
try {
const url = new URL(r.url());
if (isPrivateHostname(url.hostname)) {
privateRequests.push({ url: r.url(), method: r.method(), failure: r.failure() });
}
} catch (e) {}
});
// Navigate to the dev site used in other tests.
await page.goto('http://demo.localhost:5173/');
// Wait for network to settle.
await page.waitForLoadState('networkidle');
if (privateRequests.length > 0) {
console.error('Detected private/local requests or PNA preflights:');
for (const r of privateRequests) {
console.error(JSON.stringify(r, null, 2));
}
}
expect(privateRequests.length, 'No private/local network requests or PNA preflights should be made').toBe(0);
});

View File

@@ -1,105 +0,0 @@
import { test, expect } from '@playwright/test';
import { ae_app_local_data_defaults } from '../_helpers/ae_defaults';
test.describe('V3 API Header Integrity', () => {
test.setTimeout(7000);
test.beforeEach(async ({ page }) => {
// Log browser console errors to the terminal for easier debugging.
page.on('pageerror', (err) => console.error(`BROWSER ERROR: ${err.message}`));
page.on('console', (msg) => {
if (msg.type() === 'error' || msg.type() === 'warn') {
console.error(`BROWSER [${msg.type().toUpperCase()}]: ${msg.text()}`);
}
});
// Mock all local /v3/ API requests so tests are deterministic.
await page.route('**/v3/**', async (route) => {
const req = route.request();
const url = req.url();
// Handshake mock so app boots predictably
if (url.includes('site_domain/search')) {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
data: [
{
id: 'test-site-domain-id',
account_id: 'test-account-id',
site_id: 'test-site-id',
account_name: 'Test Account',
enable: '1',
cfg_json: {}
}
]
})
});
}
// Provide lightweight responses for endpoints we assert on
if (url.includes('/v3/lookup/country/list')) {
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [] }) });
}
if (url.includes('/v3/crud/user/search')) {
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [] }) });
}
// Default handler
return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ data: [] }) });
});
});
test('Verify lookup requests include the unauthenticated bypass header', async ({ page }) => {
// Prepare the browser's localStorage with the necessary state for this test.
await page.addInitScript((defaults) => {
const testData = { ...defaults, account_id: 'test-account-id', manager_access: true };
window.localStorage.setItem('ae_loc', JSON.stringify(testData));
}, ae_app_local_data_defaults);
// Start waiting for the lookup request *before* navigating.
const requestPromise = page.waitForRequest((request) =>
request.url().includes('/v3/lookup/country/list')
);
// Navigate to the page that triggers the lookup.
await page.goto('/core/lookups');
// Wait for the request to be captured.
const request = await requestPromise;
const headers = request.headers();
// Assert that the correct bypass headers were used.
expect(headers['x-no-account-id']).toBe('Nothing to See Here');
expect(headers['x-aether-api-key']).toBeDefined();
});
test('Verify Account ID Scavenging from localStorage on CRUD requests', async ({ page }) => {
const testAccountId = 'scavenged-account-id-123';
// Prepare the browser's localStorage with a specific account ID.
await page.addInitScript(({ defaults, id }) => {
const testData = { ...defaults, account_id: id, manager_access: true };
window.localStorage.setItem('ae_loc', JSON.stringify(testData));
},{ defaults: ae_app_local_data_defaults, id: testAccountId });
// Start waiting for the CRUD request.
const requestPromise = page.waitForRequest((request) => {
const url = request.url();
// The /core/users page triggers a 'user' search on load.
return url.includes('/v3/crud/user/search');
});
// Navigate to a page that is guaranteed to make a standard CRUD call.
await page.goto('/core/users');
// Wait for the request to be captured.
const request = await requestPromise;
const headers = request.headers();
// Assert that the scavenged account ID was correctly included in the header.
expect(headers['x-account-id']).toBe(testAccountId);
});
});