@pitcher/js-api 1.27.3 → 1.28.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/js-api.esm.js +256 -2
- package/js-api.esm.js.map +1 -1
- package/js-api.umd.min.js +11 -11
- package/js-api.umd.min.js.map +1 -1
- package/lib/apps/canvas-builder/components/ui/DynamicContent/dynamicContent.util.d.ts +36 -0
- package/lib/apps/canvas-builder/components/ui/DynamicContent/dynamicContent.util.spec.d.ts +1 -0
- package/lib/apps/canvas-builder/composables/useCanvas.d.ts +63 -0
- package/lib/apps/canvas-builder/composables/useCanvasBlocks.d.ts +14 -0
- package/lib/apps/canvas-builder/composables/useCanvasHistory.d.ts +14 -0
- package/lib/apps/canvas-builder/composables/usePopupApps.d.ts +28 -0
- package/lib/apps/canvas-builder/types/canvas.d.ts +7 -0
- package/lib/apps/canvas-builder/util/canvas.util.d.ts +11 -0
- package/lib/components/CFileViewer/pdf/CFileViewer.pdf.util.d.ts +17 -0
- package/lib/components/CFileViewer/pdf/CFileViewer.pdf.util.spec.d.ts +1 -0
- package/lib/components/CRichTextEditor/components/CTextTypeSelect.test.d.ts +1 -0
- package/lib/composables/useCourseOnlyApps.d.ts +11 -0
- package/lib/sdk/api/HighLevelApi.d.ts +4 -0
- package/lib/sdk/api/modules/appsDb.d.ts +57 -0
- package/lib/sdk/api/modules/appsDb.spec.d.ts +1 -0
- package/lib/sdk/api/modules/index.d.ts +1 -0
- package/lib/sdk/main.d.ts +12 -0
- package/lib/sdk/payload.types.d.ts +73 -0
- package/lib/types/app.d.ts +14 -0
- package/package.json +1 -1
package/js-api.esm.js
CHANGED
|
@@ -2511,6 +2511,251 @@ async function piaSearchAnswer(payload) {
|
|
|
2511
2511
|
}
|
|
2512
2512
|
}
|
|
2513
2513
|
|
|
2514
|
+
const APPS_DB_BASE_PATH = "/core/api/protected/appsdb";
|
|
2515
|
+
async function fetchAppsDbContext() {
|
|
2516
|
+
const env = await highLevelApi.API.request("get_env");
|
|
2517
|
+
const claimsDomain = env?.pitcher?.token_claims?.["https://pitcher.com/claims/urls"]?.custom_domain;
|
|
2518
|
+
const metadataDomain = env?.pitcher?.organization?.metadata?.custom_domain;
|
|
2519
|
+
let origin = claimsDomain || metadataDomain || "";
|
|
2520
|
+
if (origin && !origin.startsWith("http")) origin = `https://${origin}`;
|
|
2521
|
+
return {
|
|
2522
|
+
isIos: (env?.mode ?? env?.pitcher?.mode) === "IOS",
|
|
2523
|
+
origin,
|
|
2524
|
+
accessToken: env?.pitcher?.access_token ?? "",
|
|
2525
|
+
instanceId: env?.pitcher?.instance?.id
|
|
2526
|
+
};
|
|
2527
|
+
}
|
|
2528
|
+
const CONTEXT_TTL_MS = 6e4;
|
|
2529
|
+
let contextCache = null;
|
|
2530
|
+
function getAppsDbContext() {
|
|
2531
|
+
if (contextCache && Date.now() - contextCache.fetchedAt < CONTEXT_TTL_MS) {
|
|
2532
|
+
return contextCache.promise;
|
|
2533
|
+
}
|
|
2534
|
+
const promise = fetchAppsDbContext();
|
|
2535
|
+
contextCache = { promise, fetchedAt: Date.now() };
|
|
2536
|
+
promise.catch(() => {
|
|
2537
|
+
if (contextCache?.promise === promise) contextCache = null;
|
|
2538
|
+
});
|
|
2539
|
+
return promise;
|
|
2540
|
+
}
|
|
2541
|
+
async function appsDbRestFetch(ctx, path, init) {
|
|
2542
|
+
const base = ctx.origin || (typeof window !== "undefined" ? window.location.origin : "");
|
|
2543
|
+
const url = new URL(`${base}${APPS_DB_BASE_PATH}${path}`);
|
|
2544
|
+
Object.entries(init.params ?? {}).forEach(([key, value]) => {
|
|
2545
|
+
if (value !== void 0 && value !== null) url.searchParams.set(key, String(value));
|
|
2546
|
+
});
|
|
2547
|
+
const response = await fetch(url.toString(), {
|
|
2548
|
+
method: init.method,
|
|
2549
|
+
credentials: "include",
|
|
2550
|
+
headers: {
|
|
2551
|
+
"Content-Type": "application/json",
|
|
2552
|
+
Authorization: `Bearer ${ctx.accessToken}`,
|
|
2553
|
+
...ctx.instanceId ? { "x-instance-id": ctx.instanceId } : {}
|
|
2554
|
+
},
|
|
2555
|
+
...init.body !== void 0 ? { body: JSON.stringify(init.body) } : {}
|
|
2556
|
+
});
|
|
2557
|
+
if (!response.ok) {
|
|
2558
|
+
let errorBody;
|
|
2559
|
+
try {
|
|
2560
|
+
errorBody = await response.json();
|
|
2561
|
+
} catch {
|
|
2562
|
+
errorBody = void 0;
|
|
2563
|
+
}
|
|
2564
|
+
const error = new Error(
|
|
2565
|
+
`AppsDB request failed: ${response.status}${errorBody?.error ? ` — ${errorBody.error}` : ""}`
|
|
2566
|
+
);
|
|
2567
|
+
error.status = response.status;
|
|
2568
|
+
throw error;
|
|
2569
|
+
}
|
|
2570
|
+
if (response.status === 204 || response.status === 205) return void 0;
|
|
2571
|
+
const text = await response.text();
|
|
2572
|
+
return text ? JSON.parse(text) : void 0;
|
|
2573
|
+
}
|
|
2574
|
+
function hasErrorCode(error, code) {
|
|
2575
|
+
return error?.error_code === code || error?.errorCode === code || error?.code === code || typeof error?.reason === "string" && error.reason.includes(code) || typeof error?.message === "string" && error.message.includes(code);
|
|
2576
|
+
}
|
|
2577
|
+
function isTypeNotSyncedError(error) {
|
|
2578
|
+
return hasErrorCode(error, "type_not_synced");
|
|
2579
|
+
}
|
|
2580
|
+
function isBridgeUnsupportedError(error) {
|
|
2581
|
+
return hasErrorCode(error, "requestTypeDoesNotExists");
|
|
2582
|
+
}
|
|
2583
|
+
function parsePsqlLiteral(raw) {
|
|
2584
|
+
if (/^'.*'$/.test(raw)) return raw.slice(1, -1).replace(/''/g, "'");
|
|
2585
|
+
if (/^-?\d+(\.\d+)?$/.test(raw)) return Number(raw);
|
|
2586
|
+
if (/^true$/i.test(raw)) return true;
|
|
2587
|
+
if (/^false$/i.test(raw)) return false;
|
|
2588
|
+
if (/^null$/i.test(raw)) return null;
|
|
2589
|
+
return void 0;
|
|
2590
|
+
}
|
|
2591
|
+
function stripQuotedLiterals(where) {
|
|
2592
|
+
return where.replace(/'(?:[^']|'')*'/g, "''");
|
|
2593
|
+
}
|
|
2594
|
+
function splitTopLevelAnd(where) {
|
|
2595
|
+
const parts = [];
|
|
2596
|
+
let current = "";
|
|
2597
|
+
let inQuote = false;
|
|
2598
|
+
let i = 0;
|
|
2599
|
+
while (i < where.length) {
|
|
2600
|
+
const char = where[i];
|
|
2601
|
+
if (char === "'") {
|
|
2602
|
+
if (inQuote && where[i + 1] === "'") {
|
|
2603
|
+
current += "''";
|
|
2604
|
+
i += 2;
|
|
2605
|
+
continue;
|
|
2606
|
+
}
|
|
2607
|
+
inQuote = !inQuote;
|
|
2608
|
+
current += char;
|
|
2609
|
+
i += 1;
|
|
2610
|
+
continue;
|
|
2611
|
+
}
|
|
2612
|
+
if (!inQuote) {
|
|
2613
|
+
const separator = where.slice(i).match(/^ AND /i);
|
|
2614
|
+
if (separator) {
|
|
2615
|
+
parts.push(current);
|
|
2616
|
+
current = "";
|
|
2617
|
+
i += separator[0].length;
|
|
2618
|
+
continue;
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
current += char;
|
|
2622
|
+
i += 1;
|
|
2623
|
+
}
|
|
2624
|
+
if (inQuote) return null;
|
|
2625
|
+
parts.push(current);
|
|
2626
|
+
return parts;
|
|
2627
|
+
}
|
|
2628
|
+
function parseSimplePsql(query) {
|
|
2629
|
+
const normalized = query.replace(/\s+/g, " ").trim();
|
|
2630
|
+
const match = normalized.match(/^SELECT \* FROM ([A-Za-z0-9_]+)( WHERE (.+))?$/i);
|
|
2631
|
+
if (!match) return null;
|
|
2632
|
+
const type = match[1];
|
|
2633
|
+
const where = match[3];
|
|
2634
|
+
if (!where) return { type, conditions: [] };
|
|
2635
|
+
if (/\bOR\b|\bNOT\b|\bLIKE\b|\bIN\b|[<>]|!=/i.test(stripQuotedLiterals(where))) return null;
|
|
2636
|
+
const rawConditions = splitTopLevelAnd(where);
|
|
2637
|
+
if (!rawConditions) return null;
|
|
2638
|
+
const conditions = [];
|
|
2639
|
+
for (const part of rawConditions) {
|
|
2640
|
+
const condition = part.trim().match(/^([A-Za-z0-9_.]+) ?= ?(.+)$/);
|
|
2641
|
+
if (!condition) return null;
|
|
2642
|
+
const value = parsePsqlLiteral(condition[2].trim());
|
|
2643
|
+
if (value === void 0) return null;
|
|
2644
|
+
conditions.push({ path: condition[1], value });
|
|
2645
|
+
}
|
|
2646
|
+
return { type, conditions };
|
|
2647
|
+
}
|
|
2648
|
+
function entryMatches(entry, conditions) {
|
|
2649
|
+
return conditions.every(({ path, value }) => {
|
|
2650
|
+
const actual = path.split(".").reduce((acc, key) => acc == null ? void 0 : acc[key], entry);
|
|
2651
|
+
if (value === null) return actual === null || actual === void 0;
|
|
2652
|
+
if (actual === null || actual === void 0) return false;
|
|
2653
|
+
return String(actual) === String(value);
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
2656
|
+
async function appsDbGetEntries(payload) {
|
|
2657
|
+
if (!payload?.type || typeof payload.type !== "string") {
|
|
2658
|
+
return Promise.reject(new Error("type is required and must be a non-empty string"));
|
|
2659
|
+
}
|
|
2660
|
+
const ctx = await getAppsDbContext();
|
|
2661
|
+
if (ctx.isIos) {
|
|
2662
|
+
try {
|
|
2663
|
+
return await highLevelApi.API.request("appsdb_get_entries", payload);
|
|
2664
|
+
} catch (error) {
|
|
2665
|
+
if (!isTypeNotSyncedError(error) && !isBridgeUnsupportedError(error)) throw error;
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
return appsDbRestFetch(ctx, "", { method: "GET", params: payload });
|
|
2669
|
+
}
|
|
2670
|
+
async function appsDbUpsertEntry(payload) {
|
|
2671
|
+
if (!payload?.data || typeof payload.data !== "object") {
|
|
2672
|
+
return Promise.reject(new Error("data is required and must be an object"));
|
|
2673
|
+
}
|
|
2674
|
+
if (!payload.id && !payload.type) {
|
|
2675
|
+
return Promise.reject(new Error("type is required when creating an entry (no id provided)"));
|
|
2676
|
+
}
|
|
2677
|
+
const ctx = await getAppsDbContext();
|
|
2678
|
+
if (ctx.isIos) {
|
|
2679
|
+
try {
|
|
2680
|
+
return await highLevelApi.API.request("appsdb_upsert_entry", payload);
|
|
2681
|
+
} catch (error) {
|
|
2682
|
+
if (!isBridgeUnsupportedError(error)) throw error;
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
if (payload.id) {
|
|
2686
|
+
return appsDbRestFetch(ctx, `/${encodeURIComponent(payload.id)}`, { method: "PUT", body: { data: payload.data } });
|
|
2687
|
+
}
|
|
2688
|
+
return appsDbRestFetch(ctx, "", { method: "POST", body: payload });
|
|
2689
|
+
}
|
|
2690
|
+
async function appsDbDeleteEntry(payload) {
|
|
2691
|
+
if (!payload?.id || typeof payload.id !== "string") {
|
|
2692
|
+
return Promise.reject(new Error("id is required and must be a non-empty string"));
|
|
2693
|
+
}
|
|
2694
|
+
const ctx = await getAppsDbContext();
|
|
2695
|
+
if (ctx.isIos) {
|
|
2696
|
+
try {
|
|
2697
|
+
return await highLevelApi.API.request("appsdb_delete_entry", payload);
|
|
2698
|
+
} catch (error) {
|
|
2699
|
+
if (!isBridgeUnsupportedError(error)) throw error;
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
await appsDbRestFetch(ctx, `/${encodeURIComponent(payload.id)}`, { method: "DELETE" });
|
|
2703
|
+
}
|
|
2704
|
+
async function isDeviceOffline() {
|
|
2705
|
+
try {
|
|
2706
|
+
return Boolean(await highLevelApi.API.request("is_offline"));
|
|
2707
|
+
} catch {
|
|
2708
|
+
return false;
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
const MIRROR_PAGE_LIMIT = 1e3;
|
|
2712
|
+
async function psqlFromMirror(parsed) {
|
|
2713
|
+
try {
|
|
2714
|
+
const entries = [];
|
|
2715
|
+
let hasMore = true;
|
|
2716
|
+
while (hasMore) {
|
|
2717
|
+
const result = await highLevelApi.API.request("appsdb_get_entries", {
|
|
2718
|
+
type: parsed.type,
|
|
2719
|
+
limit: MIRROR_PAGE_LIMIT,
|
|
2720
|
+
offset: entries.length
|
|
2721
|
+
});
|
|
2722
|
+
const page = result?.entries ?? [];
|
|
2723
|
+
entries.push(...page);
|
|
2724
|
+
hasMore = Boolean(result?.hasMore) && page.length > 0;
|
|
2725
|
+
}
|
|
2726
|
+
const matched = entries.filter((entry) => entryMatches(entry, parsed.conditions));
|
|
2727
|
+
return { entries: matched, totalCount: matched.length, hasMore: false };
|
|
2728
|
+
} catch (error) {
|
|
2729
|
+
if (isTypeNotSyncedError(error) || isBridgeUnsupportedError(error)) return null;
|
|
2730
|
+
throw error;
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
async function appsDbPsql(payload) {
|
|
2734
|
+
if (!payload?.query || typeof payload.query !== "string") {
|
|
2735
|
+
return Promise.reject(new Error("query is required and must be a non-empty string"));
|
|
2736
|
+
}
|
|
2737
|
+
const ctx = await getAppsDbContext();
|
|
2738
|
+
if (ctx.isIos) {
|
|
2739
|
+
const parsed = parseSimplePsql(payload.query);
|
|
2740
|
+
let mirrorUnavailable = false;
|
|
2741
|
+
if (parsed && await isDeviceOffline()) {
|
|
2742
|
+
const local = await psqlFromMirror(parsed);
|
|
2743
|
+
if (local) return local;
|
|
2744
|
+
mirrorUnavailable = true;
|
|
2745
|
+
}
|
|
2746
|
+
try {
|
|
2747
|
+
return await appsDbRestFetch(ctx, "/psql", { method: "POST", body: payload });
|
|
2748
|
+
} catch (error) {
|
|
2749
|
+
if (parsed && !mirrorUnavailable && error instanceof TypeError) {
|
|
2750
|
+
const local = await psqlFromMirror(parsed);
|
|
2751
|
+
if (local) return local;
|
|
2752
|
+
}
|
|
2753
|
+
throw error;
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
return appsDbRestFetch(ctx, "/psql", { method: "POST", body: payload });
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2514
2759
|
function open$2(payload = {}) {
|
|
2515
2760
|
return this.API.request("open", payload);
|
|
2516
2761
|
}
|
|
@@ -3519,6 +3764,10 @@ const modules = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
|
3519
3764
|
__proto__: null,
|
|
3520
3765
|
aiComplete,
|
|
3521
3766
|
aiGetCapabilities,
|
|
3767
|
+
appsDbDeleteEntry,
|
|
3768
|
+
appsDbGetEntries,
|
|
3769
|
+
appsDbPsql,
|
|
3770
|
+
appsDbUpsertEntry,
|
|
3522
3771
|
assignCanvasTheme,
|
|
3523
3772
|
close: close$2,
|
|
3524
3773
|
createCanvas: createCanvas$1,
|
|
@@ -4148,7 +4397,10 @@ function getTopPitcherWindow(current = window) {
|
|
|
4148
4397
|
}
|
|
4149
4398
|
|
|
4150
4399
|
const TRUNCATE_LENGTH_TRIGGER = 10;
|
|
4400
|
+
const RAW_RESPONSE_METHODS = ["appsdb_get_entries", "appsdb_upsert_entry", "appsdb_delete_entry"];
|
|
4151
4401
|
const RAW_PAYLOAD_METHODS = [
|
|
4402
|
+
// AppsDB `data` is an opaque app-owned blob — snake-casing its keys would corrupt stored payloads
|
|
4403
|
+
"appsdb_upsert_entry",
|
|
4152
4404
|
"crm_create",
|
|
4153
4405
|
"crm_upsert",
|
|
4154
4406
|
"crm_describe",
|
|
@@ -4210,7 +4462,7 @@ class LowLevelApi extends EventEmitter {
|
|
|
4210
4462
|
this.options.logLevel === "debug" && console.log(`Callback ${id} response:`, res);
|
|
4211
4463
|
if (res.response.status === "ok") {
|
|
4212
4464
|
resolve(
|
|
4213
|
-
this.options.casing === "camel" ? camelCaseKeys(res.response.body) : res.response.body
|
|
4465
|
+
this.options.casing === "camel" && !RAW_RESPONSE_METHODS.includes(type) ? camelCaseKeys(res.response.body) : res.response.body
|
|
4214
4466
|
);
|
|
4215
4467
|
if (type === "get_env") updateOnlineHandlersEnv(res.response.body);
|
|
4216
4468
|
} else if (res.response.status === "error") {
|
|
@@ -4221,7 +4473,9 @@ class LowLevelApi extends EventEmitter {
|
|
|
4221
4473
|
js_api_response: JSON.stringify(truncateObject(res, 3, TRUNCATE_LENGTH_TRIGGER))
|
|
4222
4474
|
});
|
|
4223
4475
|
}
|
|
4224
|
-
reject(
|
|
4476
|
+
reject(
|
|
4477
|
+
this.options.casing === "camel" && !RAW_RESPONSE_METHODS.includes(type) ? camelCaseKeys(res.response.body) : res.response.body
|
|
4478
|
+
);
|
|
4225
4479
|
} else {
|
|
4226
4480
|
throw new Error("unsupported response status");
|
|
4227
4481
|
}
|