openxiangda 1.0.164 → 1.0.166
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/README.md +9 -7
- package/lib/change-baseline.js +74 -6
- package/lib/cli.js +2508 -167
- package/lib/sdd.js +320 -6
- package/lib/utils.js +76 -2
- package/openxiangda-skills/SKILL.md +13 -6
- package/openxiangda-skills/references/pages/publish-flow.md +15 -16
- package/openxiangda-skills/references/resource-manifest-cheatsheet.md +24 -2
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +14 -6
- package/openxiangda-skills/skills/openxiangda-form/SKILL.md +10 -9
- package/openxiangda-skills/skills/openxiangda-workflow-automation/SKILL.md +2 -0
- package/package.json +2 -1
- package/packages/sdk/dist/runtime/index.cjs +164 -48
- package/packages/sdk/dist/runtime/index.cjs.map +1 -1
- package/packages/sdk/dist/runtime/index.d.mts +1 -1
- package/packages/sdk/dist/runtime/index.d.ts +1 -1
- package/packages/sdk/dist/runtime/index.mjs +164 -48
- package/packages/sdk/dist/runtime/index.mjs.map +1 -1
- package/packages/sdk/dist/runtime/react.cjs +164 -48
- package/packages/sdk/dist/runtime/react.cjs.map +1 -1
- package/packages/sdk/dist/runtime/react.d.mts +79 -1
- package/packages/sdk/dist/runtime/react.d.ts +79 -1
- package/packages/sdk/dist/runtime/react.mjs +164 -48
- package/packages/sdk/dist/runtime/react.mjs.map +1 -1
- package/templates/openxiangda-react-spa/.cursor/rules/openxiangda-resources.mdc +1 -0
- package/templates/openxiangda-react-spa/.qoder/rules/openxiangda-resources.md +1 -0
- package/templates/openxiangda-react-spa/AGENTS.md +12 -10
- package/templates/openxiangda-react-spa/scripts/build-js-code.mjs +20 -2
- package/templates/openxiangda-react-spa/scripts/deploy.mjs +2 -2
- package/templates/sy-lowcode-app-workspace/.cursor/rules/openxiangda-resources.mdc +2 -0
- package/templates/sy-lowcode-app-workspace/.qoder/rules/openxiangda-resources.md +1 -0
- package/templates/sy-lowcode-app-workspace/AGENTS.md +5 -3
- package/templates/sy-lowcode-app-workspace/scripts/build-js-code.mjs +20 -2
|
@@ -572,13 +572,18 @@ JS_CODE 调用:`ctx.notification.sendByType({ ... })`。允许的 channels:`
|
|
|
572
572
|
{
|
|
573
573
|
"code": "reservation_reminder_summary",
|
|
574
574
|
"name": "Reservation Reminder Summary",
|
|
575
|
+
"secretRefs": [
|
|
576
|
+
{ "name": "notification_provider_token", "required": false }
|
|
577
|
+
],
|
|
575
578
|
"resources": {
|
|
576
579
|
"forms": ["reservation_order"],
|
|
577
580
|
"dataViews": ["reservation_order_overview"],
|
|
578
581
|
"connectors": ["crm"]
|
|
579
582
|
},
|
|
580
583
|
"definitionJson": {
|
|
584
|
+
"version": "function_v2",
|
|
581
585
|
"runtimeMode": "trusted_node",
|
|
586
|
+
"runtimeContractVersion": "trusted_node_v2",
|
|
582
587
|
"timeout": 30000,
|
|
583
588
|
"runtimeInvoke": {
|
|
584
589
|
"audience": {
|
|
@@ -595,12 +600,19 @@ JS_CODE 调用:`ctx.notification.sendByType({ ... })`。允许的 channels:`
|
|
|
595
600
|
|
|
596
601
|
```ts
|
|
597
602
|
// src/functions/reservation_reminder_summary/index.ts
|
|
598
|
-
import type {
|
|
603
|
+
import type { AppFunctionContextV2 } from "openxiangda/runtime";
|
|
599
604
|
|
|
600
605
|
export default async function reservationReminderSummary(
|
|
601
|
-
ctx:
|
|
606
|
+
ctx: AppFunctionContextV2,
|
|
602
607
|
input: { scope?: string },
|
|
603
608
|
) {
|
|
609
|
+
// Only resolve a name declared in top-level secretRefs. Never log or return it.
|
|
610
|
+
const providerToken = await ctx.secrets.get("notification_provider_token");
|
|
611
|
+
const providerResponse = await ctx.utils.http.post(
|
|
612
|
+
"https://api.dingtalk.com/v1.0/example/operation",
|
|
613
|
+
{ requestId: input.scope || "default" },
|
|
614
|
+
{ headers: { authorization: `Bearer ${providerToken}` } },
|
|
615
|
+
);
|
|
604
616
|
const roleCodes = ctx.operator?.roleCodes || ctx.permissions?.roleCodes || [];
|
|
605
617
|
const canManage =
|
|
606
618
|
ctx.operator?.hasFullAccess === true ||
|
|
@@ -626,6 +638,7 @@ export default async function reservationReminderSummary(
|
|
|
626
638
|
});
|
|
627
639
|
|
|
628
640
|
return {
|
|
641
|
+
providerStatus: providerResponse.status,
|
|
629
642
|
pendingCount: orders.totalCount || orders.data?.length || 0,
|
|
630
643
|
detail,
|
|
631
644
|
overview: overview.data || [],
|
|
@@ -634,6 +647,15 @@ export default async function reservationReminderSummary(
|
|
|
634
647
|
}
|
|
635
648
|
```
|
|
636
649
|
|
|
650
|
+
`ctx.utils.http` is the typed, server-controlled public HTTPS bridge for
|
|
651
|
+
`trusted_node_v2`; it never forwards the platform Runtime bearer token and
|
|
652
|
+
rejects redirects plus private/reserved network targets. For whole-app atomic
|
|
653
|
+
activation, stage the exact backend scope with
|
|
654
|
+
`openxiangda resource publish function --only <code> --stage-only`, combine its
|
|
655
|
+
verified `stagedResource` with the other changed staged children, and pass that
|
|
656
|
+
overlay to `openxiangda release app-finalize --staged-resources-json ...`.
|
|
657
|
+
Already-active Backend Releases return `activeResource`, not a staged descriptor.
|
|
658
|
+
|
|
637
659
|
调用方式:
|
|
638
660
|
|
|
639
661
|
```ts
|
|
@@ -26,6 +26,8 @@ Run `openxiangda update check --json` once per substantial task, on a suspected
|
|
|
26
26
|
|
|
27
27
|
Every write/release command must include `--profile <name>`. In a shared checkout, create an isolated Git worktree and named feature branch before editing. `sdd propose|quick|context` claims `.openxiangda/worktree-owner.json` for the current `CODEX_THREAD_ID`; a foreign owner is a hard stop. `.openxiangda/state.json` maps profile-local IDs; it is not a collaboration lock. The task is not complete until the exact frozen publish SHA is merged without squash/rebase and pushed to the authoritative default branch.
|
|
28
28
|
|
|
29
|
+
SDD is permissive during coding and strict at release: `sdd verify --stage implementation` does not require unfinished checklist/evidence prose, and an omitted stage means prepublish rather than postpublish/archive. Exact command, child scope, and mainline gates apply only when staging or activating writes.
|
|
30
|
+
|
|
29
31
|
First-class direct configuration writes (form/page/menu/role/permission/workflow/connector/notification/auth/route/public-access/scope/data-view/storage/settings) also require `--change <id>` or a complete stored session created by `openxiangda release begin --change <id>`. Missing context fails locally with `PUBLISH_CONTEXT_REQUIRED` before any HTTP write. Dry-run, schema planning, validation, query/invoke/export/submit/execution/upload, and other data-plane actions do not acquire a lease. Direct Workflow publish/unpublish/delete additionally requires the frozen `--expected-revision`; never fetch the latest revision merely to replay an older payload.
|
|
30
32
|
|
|
31
33
|
## Exact planning
|
|
@@ -55,7 +57,13 @@ openxiangda resource publish function --only function_a,function_b --profile <na
|
|
|
55
57
|
|
|
56
58
|
Selectors apply before manifest parsing, source dependency analysis, and JS_CODE typecheck/build. A scoped Function/Automation plan must touch only the selected targets plus their transitive/shared/ambient dependencies; an unscoped plan intentionally retains full-workspace behavior. Resource commands use the canonical scoped builder packaged with the installed CLI for standard workspaces, so old checked-in builders still receive the optimization; refresh/bootstrap the workspace script only for equivalent manual `pnpm build-js-code` behavior. Nonstandard custom builders remain an explicit compatibility fallback.
|
|
57
59
|
|
|
58
|
-
Source-triggered Function/Automation targets use
|
|
60
|
+
Source-triggered Function/Automation targets use Backend Release v2 when the platform exposes that capability. One child may mix create, source-only update, and manifest replacement update through explicit per-resource `operation/mode`; the CLI freezes the current Backend Release parent plus Git/change baseline, then runs `prepare -> verify -> activate` or stops verified for `--stage-only`. Activation CAS-checks the entire set and applies all updates in one transaction. `--stage-only` and Secret-bound Function publishing fail closed when Backend Release v2 is unavailable; compatibility fallback is limited to non-staged, non-Secret publishing after an explicit Backend head 404. Existing online bindings, contracts, metadata, trigger/view configuration, and enabled/published state remain unchanged; noops do not advance versions/timestamps. A deliberate whole-definition replacement requires exact `--only/--code` and `--replace-manifest --reason "<why>"`; SDD bypass does not imply replacement authority.
|
|
61
|
+
|
|
62
|
+
Functions with a top-level `secretRefs` field use `backend_release_v2`, including an explicit empty list that removes bindings. This path never falls back to source PATCH. It requires the per-app Secret capability probe to grant `app_function_secrets_v1`, `trusted_node_v2`, `backend_release_v2`, and `atomic_staged_children_v2`; otherwise plan/publish fails closed.
|
|
63
|
+
|
|
64
|
+
For whole-app atomic activation, publish the exact mixed Function/Automation scope with `resource publish function,automation --only function:<code>,automation:<code> --stage-only --change <change>`. This stops after Backend Release verify and returns `stagedResource` plus every handled selector; the CLI automatically merges it with other changed staged children in `.openxiangda/releases/<change>/staged-resources.json` for `release app-finalize --staged-resources-json`. A Backend Release that was already activated returns `activeResource` and must not be presented as staged.
|
|
65
|
+
|
|
66
|
+
Standard workspaces use the CLI-bundled scoped JS_CODE builder directly, including isolated Git-base checks; do not invoke `pnpm` merely to rebuild a selected Function/Automation. New snapshots carry `source_lineage_v1.sourceHash`, which is derived from authored source/dependencies rather than generated bundle bytes, so a builder upgrade does not falsely report source divergence.
|
|
59
67
|
|
|
60
68
|
Use `openxiangda release backend-head|backend-list|backend-detail|backend-diff` to inspect immutable history. `backend-rollback <releaseId> --change <change> --reason "..."` prepares, verifies, and activates a new release from the historical snapshot; it never mutates or directly reactivates the old row. `backend-abort` stops prepared/verified work and `backend-retry` retries only recorded post-commit side effects. Do not fetch newer revisions to replay an older payload.
|
|
61
69
|
|
|
@@ -76,16 +84,16 @@ Do not call `lowcode-workspace publish-all`, `pnpm publish:all`, `pnpm publish:o
|
|
|
76
84
|
|
|
77
85
|
## React SPA
|
|
78
86
|
|
|
79
|
-
Forms
|
|
87
|
+
Forms, Backend resources, and frontend Runtime are staged children of one Root App release:
|
|
80
88
|
|
|
81
89
|
```bash
|
|
82
|
-
openxiangda
|
|
90
|
+
openxiangda resource publish form-setting --only <formCode> --change <change> --profile <name>
|
|
83
91
|
openxiangda resource plan <type> --only <codes> --profile <name>
|
|
84
92
|
openxiangda runtime deploy --change <change> --profile <name> --no-activate --json
|
|
85
|
-
openxiangda sdd verify <change> --changed --stage prepublish
|
|
93
|
+
openxiangda sdd verify <change> --changed --stage prepublish --profile <name>
|
|
86
94
|
openxiangda release begin --change <change> --profile <name>
|
|
87
|
-
openxiangda resource publish
|
|
88
|
-
openxiangda
|
|
95
|
+
openxiangda resource publish function,automation --only function:<code>,automation:<code> --stage-only --change <change> --profile <name>
|
|
96
|
+
openxiangda release app-finalize --staged-resources-json .openxiangda/releases/<change>/staged-resources.json --change <change> --profile <name>
|
|
89
97
|
# Merge/fast-forward the frozen release SHA to the remote default branch.
|
|
90
98
|
openxiangda release integration-status --profile <name>
|
|
91
99
|
openxiangda release end --profile <name>
|
|
@@ -23,9 +23,10 @@ openxiangda form list --profile <name>
|
|
|
23
23
|
# src/forms/<formCode>/schema.ts — fields, options, rules, top-level FormEffect[]
|
|
24
24
|
# src/forms/<formCode>/page.tsx — presentation only
|
|
25
25
|
|
|
26
|
-
# 3.
|
|
27
|
-
openxiangda
|
|
28
|
-
openxiangda
|
|
26
|
+
# 3. normal release: stage the exact Form bundle, then Root App finalize
|
|
27
|
+
openxiangda resource plan form-setting --only <formCode> --profile <name>
|
|
28
|
+
openxiangda resource publish form-setting --only <formCode> --change <change> --profile <name>
|
|
29
|
+
openxiangda release app-finalize --staged-resources-json .openxiangda/releases/<change>/staged-resources.json --change <change> --profile <name>
|
|
29
30
|
```
|
|
30
31
|
|
|
31
32
|
## DO / DO NOT
|
|
@@ -71,19 +72,19 @@ src/forms/<formCode>/
|
|
|
71
72
|
└── page.tsx
|
|
72
73
|
```
|
|
73
74
|
|
|
74
|
-
Then
|
|
75
|
+
Then stage the exact Form bundle:
|
|
75
76
|
|
|
76
77
|
```bash
|
|
77
|
-
openxiangda
|
|
78
|
+
openxiangda resource publish form-setting --only <formCodes> --change <change> --profile <name>
|
|
78
79
|
```
|
|
79
80
|
|
|
80
|
-
For a single form edit,
|
|
81
|
+
For a single form edit, still keep the child staged until Root App finalize:
|
|
81
82
|
|
|
82
83
|
```bash
|
|
83
|
-
openxiangda
|
|
84
|
+
openxiangda resource publish form-setting --only <formCode> --change <change> --profile <name>
|
|
84
85
|
```
|
|
85
86
|
|
|
86
|
-
The
|
|
87
|
+
The Form resource bundle carries schema, settings, indexes, data-management, runtime-write, and public-access configuration under one CAS parent. It returns a canonical staged FormRelease and the CLI records it in the change-scoped staged-resources file. `workspace publish --form` is limited to an intentional first-time bootstrap or isolated repair outside a governed multi-resource release; it is not the normal release path.
|
|
87
88
|
|
|
88
89
|
For Phase 6 React SPA workspaces, `app-workspace.config.ts` should declare
|
|
89
90
|
`runtimeMode: "react-spa"`. In that mode, `workspace publish --form <code>` is
|
|
@@ -109,7 +110,7 @@ Do not use `openxiangda form create` as the normal way to generate a user-facing
|
|
|
109
110
|
## Form Release / CAS Safety
|
|
110
111
|
|
|
111
112
|
- Treat all Form configuration as one immutable release stream. Freeze `revision`, `etag`, and `activeFormReleaseHead` from one snapshot before writing.
|
|
112
|
-
- Publish declared schema, packages, settings, indexes, data-management, runtime-write, and public-access configuration through one Form `bundle` mutation. Resource Form bundles are stage-only by default
|
|
113
|
+
- Publish declared schema, packages, settings, indexes, data-management, runtime-write, and public-access configuration through one `resource publish form-setting --only <codes> --change <change>` Form `bundle` mutation. Resource Form bundles are stage-only by default and return immutable `contentHash` plus a canonical `stagedResource` (stable identity is `formUuid`); the CLI merges each entry into `.openxiangda/releases/<change>/staged-resources.json`. Root `app-finalize` atomically switches all child heads plus the App head. Use direct `--activate` only for an intentional form-only repair outside a governed multi-resource release; never use `workspace publish --form` inside an atomic React SPA release.
|
|
113
114
|
- Every mutation must carry `If-Match`, `expectedRevision`, `expectedParent`, and a stable artifact ID. Creation starts at revision `0`.
|
|
114
115
|
- A revision/head conflict is not retryable. Stop with zero later writes, re-plan, and explicitly merge the competing change; never silently adopt the newer live head.
|
|
115
116
|
- Use `openxiangda form release-head|release-list|release-detail|release-diff` for inspection and `release-activate|release-rollback|release-abort` for controlled recovery. See `docs/openxiangda-form-releases.md` in the OpenXiangda tool repository.
|
|
@@ -105,6 +105,8 @@ JS_CODE is the backend execution escape hatch for workflow and automation. Use i
|
|
|
105
105
|
|
|
106
106
|
App Function is the reusable backend execution model. Use it when the logic should be called by custom pages, multiple automations, workflows, or the runtime API. Source lives in `src/functions/<functionCode>/index.ts`; manifest lives in `src/resources/functions/<functionCode>.json`. Call it from pages with `sdk.function.invoke(code, { input })`, from graph definitions with `function_call`, or from the runtime endpoint `/:appType/v1/functions/:code/invoke.json`. Direct runtime invocation defaults to app automation management permission; ordinary page callers must declare `definitionJson.runtimeInvoke.audience` (`authenticated`, `page_permission_group`, `app_roles`, or `scope_policy`). Prefer `export default async function(ctx, input) {}` for App Function source; the second argument is the invoke input and the same value is available as `ctx.input`. Current MVP exposes controlled helpers only and does not expose raw SQL or Redis. `ctx.form.createOne/updateOne/updateById` are trusted backend writes governed by declared `resources.forms` and function invocation authorization; keep internal forms closed to direct user submit unless the business explicitly needs raw form submission. For internal function-only forms, set form settings `runtimeWrite.mode="function_only"` instead of relying only on missing submit permission groups.
|
|
107
107
|
|
|
108
|
+
For third-party credentials, use top-level metadata-only `secretRefs` plus `definitionJson.version="function_v2"` and `runtimeContractVersion="trusted_node_v2"`; read a declared value with `await ctx.secrets.get(name)` and call public business APIs through `ctx.utils.http`. Never use `process.env` for platform secrets. Values are managed with `openxiangda secret create|rotate --value-stdin --change <change> --profile <name>` and never appear in Git, build output, snapshots, plan diffs, logs, exceptions, or traces. Local tests may use `function test --secret-from-env logical=ENV` only; the value is passed to an isolated child over stdin and is never written to workspace/cache/state. For a Root App transaction, use exact-scope `resource publish function --only <code> --stage-only` and include its verified `stagedResource` in `release app-finalize`.
|
|
109
|
+
|
|
108
110
|
For new AI-authored automations, prefer code-first `automation_code_ts` resources instead of visual v3 graph definitions. Put the source in `src/automations/<resourceCode>/index.ts`, define `definition.code.json` with `kind: "automation_code_ts"`, and provide `preview.json` for read-only frontend display. Use `ctx.logger.debug/info/warn/error(message, data?)` at every important step; OpenXiangda can inspect logs with `automation executions`, `automation logs`, and `automation diagnose`.
|
|
109
111
|
|
|
110
112
|
For new AI-authored workflows where users do not need canvas editing, prefer compile-time `workflow.ts` using `openxiangda/workflow`. The CLI compiles it to v3 `definitionJson` and `preview.json`; the backend still runs the normal workflow engine for approval tasks, copy tasks, callback waits, branch advancement, and process records.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openxiangda",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.166",
|
|
4
4
|
"description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"bin": {
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
"test:api-contract": "node scripts/openxiangda-api-contract-smoke.mjs",
|
|
77
77
|
"test:help-no-side-effects": "node scripts/help-no-side-effects-smoke.mjs",
|
|
78
78
|
"test:app-function-fallback": "node scripts/app-function-source-fallback-smoke.mjs",
|
|
79
|
+
"test:app-function-secrets": "node scripts/app-function-secrets-cli-smoke.mjs",
|
|
79
80
|
"test:change-baseline": "node scripts/change-baseline-smoke.mjs",
|
|
80
81
|
"test:change-baseline-publish": "node scripts/change-baseline-publish-smoke.mjs",
|
|
81
82
|
"test:generic-base-preflight": "node scripts/generic-git-base-preflight-smoke.mjs",
|
|
@@ -2411,6 +2411,8 @@ var resolveLoginUrl = (appType, {
|
|
|
2411
2411
|
var getCurrentHref2 = () => typeof window === "undefined" ? "" : window.location.href;
|
|
2412
2412
|
|
|
2413
2413
|
// packages/sdk/src/runtime/core/publicAccess.ts
|
|
2414
|
+
var PUBLIC_SESSION_RENEW_WINDOW_MS = 5 * 60 * 1e3;
|
|
2415
|
+
var publicSessionInflight = /* @__PURE__ */ new Map();
|
|
2414
2416
|
var PublicAccessClientError = class extends Error {
|
|
2415
2417
|
constructor(message7, options = {}) {
|
|
2416
2418
|
super(message7);
|
|
@@ -2438,48 +2440,144 @@ var createPublicAccessClient = ({
|
|
|
2438
2440
|
userAgent: input.userAgent || getCurrentUserAgent(),
|
|
2439
2441
|
guestIdentifier: input.guestIdentifier || getOrCreatePublicGuestIdentifier(normalizedAppType)
|
|
2440
2442
|
};
|
|
2441
|
-
const
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
normalizedAppType
|
|
2446
|
-
)}/public/session`
|
|
2447
|
-
),
|
|
2448
|
-
{
|
|
2449
|
-
method: "POST",
|
|
2450
|
-
credentials: "include",
|
|
2451
|
-
headers: {
|
|
2452
|
-
accept: "application/json",
|
|
2453
|
-
"content-type": "application/json"
|
|
2454
|
-
},
|
|
2455
|
-
body: JSON.stringify(body)
|
|
2456
|
-
}
|
|
2443
|
+
const identity = buildPublicSessionIdentity(
|
|
2444
|
+
normalizedAppType,
|
|
2445
|
+
servicePrefix,
|
|
2446
|
+
body
|
|
2457
2447
|
);
|
|
2458
|
-
const
|
|
2459
|
-
|
|
2460
|
-
const
|
|
2461
|
-
if (
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2448
|
+
const cached = readCachedPublicSession(identity);
|
|
2449
|
+
if (cached) return cached;
|
|
2450
|
+
const existing = publicSessionInflight.get(identity);
|
|
2451
|
+
if (existing) return existing;
|
|
2452
|
+
const pending = (async () => {
|
|
2453
|
+
const response = await boundFetch(
|
|
2454
|
+
buildServiceUrl2(
|
|
2455
|
+
servicePrefix,
|
|
2456
|
+
`/openxiangda-api/v1/apps/${encodeURIComponent(
|
|
2457
|
+
normalizedAppType
|
|
2458
|
+
)}/public/session`
|
|
2465
2459
|
),
|
|
2466
|
-
{
|
|
2460
|
+
{
|
|
2461
|
+
method: "POST",
|
|
2462
|
+
credentials: "include",
|
|
2463
|
+
headers: {
|
|
2464
|
+
accept: "application/json",
|
|
2465
|
+
"content-type": "application/json"
|
|
2466
|
+
},
|
|
2467
|
+
body: JSON.stringify(body)
|
|
2468
|
+
}
|
|
2467
2469
|
);
|
|
2470
|
+
const payload = await readPayload2(response);
|
|
2471
|
+
const code = getRecordValue2(payload, "code");
|
|
2472
|
+
const success = getRecordValue2(payload, "success");
|
|
2473
|
+
if (!response.ok || success === false || !isSuccessCode3(code)) {
|
|
2474
|
+
throw new PublicAccessClientError(
|
|
2475
|
+
String(
|
|
2476
|
+
getRecordValue2(payload, "message") || `Public access session failed: ${response.status}`
|
|
2477
|
+
),
|
|
2478
|
+
{ status: response.status, code, payload }
|
|
2479
|
+
);
|
|
2480
|
+
}
|
|
2481
|
+
const data = getRecordValue2(payload, "data");
|
|
2482
|
+
const extra = getRecordValue2(payload, "extra");
|
|
2483
|
+
const session = {
|
|
2484
|
+
...data,
|
|
2485
|
+
publicAccess: extra?.publicAccess || null,
|
|
2486
|
+
raw: payload
|
|
2487
|
+
};
|
|
2488
|
+
writeCachedPublicSession(identity, session);
|
|
2489
|
+
return session;
|
|
2490
|
+
})();
|
|
2491
|
+
publicSessionInflight.set(identity, pending);
|
|
2492
|
+
try {
|
|
2493
|
+
return await pending;
|
|
2494
|
+
} finally {
|
|
2495
|
+
if (publicSessionInflight.get(identity) === pending) {
|
|
2496
|
+
publicSessionInflight.delete(identity);
|
|
2497
|
+
}
|
|
2468
2498
|
}
|
|
2469
|
-
const data = getRecordValue2(payload, "data");
|
|
2470
|
-
const extra = getRecordValue2(payload, "extra");
|
|
2471
|
-
return {
|
|
2472
|
-
...data,
|
|
2473
|
-
publicAccess: extra?.publicAccess || null,
|
|
2474
|
-
raw: payload
|
|
2475
|
-
};
|
|
2476
2499
|
};
|
|
2477
2500
|
return {
|
|
2478
2501
|
appType: normalizedAppType,
|
|
2479
2502
|
servicePrefix,
|
|
2480
|
-
startSession: request
|
|
2503
|
+
startSession: request,
|
|
2504
|
+
clearSession: (input = {}) => {
|
|
2505
|
+
const body = {
|
|
2506
|
+
...input,
|
|
2507
|
+
path: input.path || getCurrentPathname(),
|
|
2508
|
+
domain: input.domain || getCurrentDomain(),
|
|
2509
|
+
userAgent: input.userAgent || getCurrentUserAgent(),
|
|
2510
|
+
guestIdentifier: input.guestIdentifier || getOrCreatePublicGuestIdentifier(normalizedAppType)
|
|
2511
|
+
};
|
|
2512
|
+
clearCachedPublicSession(
|
|
2513
|
+
buildPublicSessionIdentity(normalizedAppType, servicePrefix, body)
|
|
2514
|
+
);
|
|
2515
|
+
}
|
|
2481
2516
|
};
|
|
2482
2517
|
};
|
|
2518
|
+
var buildPublicSessionIdentity = (appType, servicePrefix, input) => JSON.stringify({
|
|
2519
|
+
appType,
|
|
2520
|
+
servicePrefix: servicePrefix.replace(/\/$/, ""),
|
|
2521
|
+
path: input.path || "",
|
|
2522
|
+
policyCode: input.policyCode || "",
|
|
2523
|
+
routeCode: input.routeCode || "",
|
|
2524
|
+
ticket: input.ticket || "",
|
|
2525
|
+
guestIdentifier: input.guestIdentifier || "",
|
|
2526
|
+
domain: input.domain || ""
|
|
2527
|
+
});
|
|
2528
|
+
var publicSessionStorageKey = (identity) => {
|
|
2529
|
+
let hash = 2166136261;
|
|
2530
|
+
for (let index = 0; index < identity.length; index += 1) {
|
|
2531
|
+
hash ^= identity.charCodeAt(index);
|
|
2532
|
+
hash = Math.imul(hash, 16777619);
|
|
2533
|
+
}
|
|
2534
|
+
return `openxiangda:public:session:v1:${identity.length}:${(hash >>> 0).toString(16)}`;
|
|
2535
|
+
};
|
|
2536
|
+
var readCachedPublicSession = (identity) => {
|
|
2537
|
+
try {
|
|
2538
|
+
if (typeof window === "undefined" || !window.sessionStorage) return null;
|
|
2539
|
+
const raw = window.sessionStorage.getItem(publicSessionStorageKey(identity));
|
|
2540
|
+
if (!raw) return null;
|
|
2541
|
+
const parsed = JSON.parse(raw);
|
|
2542
|
+
if (parsed.identity !== identity || !parsed.session) return null;
|
|
2543
|
+
if (!isPublicSessionReusable(parsed.session)) {
|
|
2544
|
+
clearCachedPublicSession(identity);
|
|
2545
|
+
return null;
|
|
2546
|
+
}
|
|
2547
|
+
return parsed.session;
|
|
2548
|
+
} catch {
|
|
2549
|
+
return null;
|
|
2550
|
+
}
|
|
2551
|
+
};
|
|
2552
|
+
var writeCachedPublicSession = (identity, session) => {
|
|
2553
|
+
if (!isPublicSessionReusable(session)) return;
|
|
2554
|
+
try {
|
|
2555
|
+
if (typeof window === "undefined" || !window.sessionStorage) return;
|
|
2556
|
+
const { raw: _raw, ...cacheableSession } = session;
|
|
2557
|
+
window.sessionStorage.setItem(
|
|
2558
|
+
publicSessionStorageKey(identity),
|
|
2559
|
+
JSON.stringify({ identity, session: cacheableSession })
|
|
2560
|
+
);
|
|
2561
|
+
} catch {
|
|
2562
|
+
}
|
|
2563
|
+
};
|
|
2564
|
+
var clearCachedPublicSession = (identity) => {
|
|
2565
|
+
try {
|
|
2566
|
+
if (typeof window !== "undefined" && window.sessionStorage) {
|
|
2567
|
+
window.sessionStorage.removeItem(publicSessionStorageKey(identity));
|
|
2568
|
+
}
|
|
2569
|
+
} catch {
|
|
2570
|
+
}
|
|
2571
|
+
};
|
|
2572
|
+
var isPublicSessionReusable = (session) => {
|
|
2573
|
+
const accessTokenExpiresAt = Number(session.accessTokenExpiresAt);
|
|
2574
|
+
if (!Number.isFinite(accessTokenExpiresAt) || accessTokenExpiresAt <= 0) {
|
|
2575
|
+
return false;
|
|
2576
|
+
}
|
|
2577
|
+
const policyExpiresAt = session.publicAccess?.expiresAt ? new Date(session.publicAccess.expiresAt).getTime() : Number.POSITIVE_INFINITY;
|
|
2578
|
+
const expiresAt = Math.min(accessTokenExpiresAt, policyExpiresAt);
|
|
2579
|
+
return expiresAt - Date.now() >= PUBLIC_SESSION_RENEW_WINDOW_MS;
|
|
2580
|
+
};
|
|
2483
2581
|
var buildServiceUrl2 = (servicePrefix, path) => {
|
|
2484
2582
|
const prefix = servicePrefix.endsWith("/") ? servicePrefix.slice(0, -1) : servicePrefix;
|
|
2485
2583
|
const suffix = path.startsWith("/") ? path : `/${path}`;
|
|
@@ -9915,29 +10013,40 @@ var usePublicAccess = (options = {}) => {
|
|
|
9915
10013
|
setError(null);
|
|
9916
10014
|
try {
|
|
9917
10015
|
const resolvedPath = input.path || stableSessionInput.path || readPathFromLocation();
|
|
9918
|
-
const
|
|
10016
|
+
const resolvedInput = {
|
|
9919
10017
|
...stableSessionInput,
|
|
9920
10018
|
...input,
|
|
9921
10019
|
ticket: input.ticket || stableSessionInput.ticket || readTicketFromLocation(),
|
|
9922
10020
|
path: resolvedPath
|
|
9923
|
-
});
|
|
9924
|
-
if (!mountedRef.current) return data;
|
|
9925
|
-
activeSessionRef.current = {
|
|
9926
|
-
accessToken: data.accessToken,
|
|
9927
|
-
path: resolvedPath
|
|
9928
10021
|
};
|
|
9929
|
-
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
|
|
10022
|
+
let data = await client.startSession(resolvedInput);
|
|
10023
|
+
if (!mountedRef.current) return data;
|
|
10024
|
+
const activateSession = async (next) => {
|
|
10025
|
+
activeSessionRef.current = {
|
|
10026
|
+
accessToken: next.accessToken,
|
|
10027
|
+
path: resolvedPath
|
|
10028
|
+
};
|
|
10029
|
+
setSession(next);
|
|
10030
|
+
setAccessToken(next.accessToken, {
|
|
9937
10031
|
scope: "public",
|
|
9938
10032
|
path: resolvedPath
|
|
9939
|
-
}
|
|
9940
|
-
|
|
10033
|
+
});
|
|
10034
|
+
await reloadRuntime({
|
|
10035
|
+
accessToken: next.accessToken,
|
|
10036
|
+
accessTokenOptions: {
|
|
10037
|
+
scope: "public",
|
|
10038
|
+
path: resolvedPath
|
|
10039
|
+
}
|
|
10040
|
+
});
|
|
10041
|
+
};
|
|
10042
|
+
try {
|
|
10043
|
+
await activateSession(data);
|
|
10044
|
+
} catch (activationError) {
|
|
10045
|
+
if (!isUnauthorizedPublicSessionError(activationError)) throw activationError;
|
|
10046
|
+
client.clearSession(resolvedInput);
|
|
10047
|
+
data = await client.startSession(resolvedInput);
|
|
10048
|
+
await activateSession(data);
|
|
10049
|
+
}
|
|
9941
10050
|
if (!mountedRef.current) return data;
|
|
9942
10051
|
setLoading(false);
|
|
9943
10052
|
return data;
|
|
@@ -9999,6 +10108,13 @@ var readTicketFromLocation = () => {
|
|
|
9999
10108
|
return new URLSearchParams(window.location.search).get("ticket") || void 0;
|
|
10000
10109
|
};
|
|
10001
10110
|
var readPathFromLocation = () => typeof window === "undefined" ? void 0 : window.location.pathname;
|
|
10111
|
+
var isUnauthorizedPublicSessionError = (error) => {
|
|
10112
|
+
if (!error || typeof error !== "object") return false;
|
|
10113
|
+
const status = Number(
|
|
10114
|
+
error.status ?? error.statusCode
|
|
10115
|
+
);
|
|
10116
|
+
return status === 401 || status === 403;
|
|
10117
|
+
};
|
|
10002
10118
|
|
|
10003
10119
|
// packages/sdk/src/runtime/host/builtinRouteRenderer.tsx
|
|
10004
10120
|
var import_react88 = require("react");
|