@burdenoff/fe-libs 2026.1021.2 → 2026.1021.4
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/dist/chrome/contextSwitcher/contextSwitch.d.ts +84 -0
- package/dist/chrome/contextSwitcher/contextSwitch.js +31 -0
- package/dist/chrome/contextSwitcher/resolveSwitchTarget.d.ts +51 -0
- package/dist/chrome/contextSwitcher/resolveSwitchTarget.js +26 -0
- package/dist/chrome/contextSwitcher/useContextSwitchController.d.ts +50 -0
- package/dist/chrome/contextSwitcher/useContextSwitchController.js +62 -0
- package/dist/chrome/index.d.ts +6 -0
- package/dist/chrome.js +26 -23
- package/dist/shell-native/boot.js +32 -30
- package/dist/shell-native/deep-link-context.d.ts +44 -0
- package/dist/shell-native/deep-link-context.js +31 -0
- package/dist/shell-native/deep-link.d.ts +31 -4
- package/dist/shell-native/deep-link.js +60 -25
- package/dist/shell-native/index.d.ts +3 -1
- package/dist/shell-native/types.d.ts +9 -0
- package/dist/shell-native/useDeepLinkNavigator.d.ts +18 -0
- package/dist/shell-native/useDeepLinkNavigator.js +19 -0
- package/dist/shell-native.js +18 -16
- package/package.json +1 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Header organization / workspace switches (BOFF-8078), shared by every shell
|
|
3
|
+
* through `useContextSwitchController` (lifted from fluidgrids-app, where the
|
|
4
|
+
* same file was copied into bigconsole-app and healthybowl-app).
|
|
5
|
+
*
|
|
6
|
+
* `selectWorkspace` (microfe-auth) issues a token for the workspace it is
|
|
7
|
+
* given and then lists that workspace's projects. The list request does NOT
|
|
8
|
+
* carry the workspace it was asked about: fe-libs `graphqlFetch` derives the
|
|
9
|
+
* `x-workspace-id` header, and the context the fresh workspace token is
|
|
10
|
+
* validated against, from the stored ActiveContext (`bf-p-<uid>-context`, with
|
|
11
|
+
* the `?workspace=` URL param taking precedence). When the handler calls
|
|
12
|
+
* `selectWorkspace(B)` while ActiveContext still names workspace A, the new
|
|
13
|
+
* token is dropped as "invalid for context A", a token for A is minted in its
|
|
14
|
+
* place, and the request lists A's projects. The handler then selected A's
|
|
15
|
+
* first project, so the stored context paired workspace B with a project from
|
|
16
|
+
* workspace A, and kept that pair across reloads.
|
|
17
|
+
*
|
|
18
|
+
* Measured in prod on 2026-09-28 with the header switcher, A = f423fce9 and
|
|
19
|
+
* B = 089761cb: `MyProjectsForContext` went out with `x-workspace-id: f423fce9`
|
|
20
|
+
* and A's workspace token right after `IssueWorkspaceToken` returned B's token,
|
|
21
|
+
* and answered A's only project (e35d7c3b). VibeControls, which publishes the
|
|
22
|
+
* new workspace first, ends on B's project (e6febd92).
|
|
23
|
+
*
|
|
24
|
+
* So both switches publish the target context to ActiveContext first — the
|
|
25
|
+
* organization and workspace, with the project cleared — and only then ask
|
|
26
|
+
* microfe-auth to select it. Only a project that belongs to the target
|
|
27
|
+
* workspace is ever selected, and a switch that a later switch has superseded
|
|
28
|
+
* is discarded instead of applied.
|
|
29
|
+
*/
|
|
30
|
+
export interface SwitchableOrganization {
|
|
31
|
+
id: string;
|
|
32
|
+
tenantId?: unknown;
|
|
33
|
+
}
|
|
34
|
+
export interface SwitchableWorkspace {
|
|
35
|
+
id: string;
|
|
36
|
+
organizationId?: string | null;
|
|
37
|
+
tenantId?: unknown;
|
|
38
|
+
}
|
|
39
|
+
export interface SwitchableProject {
|
|
40
|
+
id: string;
|
|
41
|
+
workspaceId?: string | null;
|
|
42
|
+
}
|
|
43
|
+
export interface WorkspaceSelection<P extends SwitchableProject> {
|
|
44
|
+
success: boolean;
|
|
45
|
+
projects?: ReadonlyArray<P>;
|
|
46
|
+
}
|
|
47
|
+
export interface ContextSwitchDeps<O extends SwitchableOrganization, W extends SwitchableWorkspace, P extends SwitchableProject> {
|
|
48
|
+
/**
|
|
49
|
+
* Monotonic switch counter (a React ref), shared by the organization and
|
|
50
|
+
* workspace switches: the most recent switch of either kind wins.
|
|
51
|
+
*/
|
|
52
|
+
requests: {
|
|
53
|
+
current: number;
|
|
54
|
+
};
|
|
55
|
+
setOrganization: (organizationId: string | null) => void;
|
|
56
|
+
setWorkspace: (workspaceId: string | null) => void;
|
|
57
|
+
setProject: (projectId: string | null) => void;
|
|
58
|
+
setTenant: (tenantId: string | null) => void;
|
|
59
|
+
selectOrganization: (organization: O) => Promise<ReadonlyArray<W>>;
|
|
60
|
+
selectWorkspace: (workspace: W) => Promise<WorkspaceSelection<P>>;
|
|
61
|
+
selectProject: (project: P) => void;
|
|
62
|
+
}
|
|
63
|
+
export type ContextSwitchOutcome = {
|
|
64
|
+
status: 'project-selected';
|
|
65
|
+
workspaceId: string;
|
|
66
|
+
projectId: string;
|
|
67
|
+
} | {
|
|
68
|
+
status: 'no-project';
|
|
69
|
+
workspaceId: string;
|
|
70
|
+
} | {
|
|
71
|
+
status: 'no-workspace';
|
|
72
|
+
} | {
|
|
73
|
+
status: 'superseded';
|
|
74
|
+
};
|
|
75
|
+
/** The first project the selection returned that belongs to `workspaceId`. */
|
|
76
|
+
export declare function firstProjectOfWorkspace<P extends SwitchableProject>(selection: WorkspaceSelection<P>, workspaceId: string): P | null;
|
|
77
|
+
/** Header workspace switch: enter `workspace` and select its first project. */
|
|
78
|
+
export declare function switchWorkspace<O extends SwitchableOrganization, W extends SwitchableWorkspace, P extends SwitchableProject>(deps: ContextSwitchDeps<O, W, P>, workspace: W): Promise<ContextSwitchOutcome>;
|
|
79
|
+
/**
|
|
80
|
+
* Header organization switch: clear the workspace and project, load the
|
|
81
|
+
* organization's workspaces, then enter the first one exactly as a workspace
|
|
82
|
+
* switch would.
|
|
83
|
+
*/
|
|
84
|
+
export declare function switchOrganization<O extends SwitchableOrganization, W extends SwitchableWorkspace, P extends SwitchableProject>(deps: ContextSwitchDeps<O, W, P>, organization: O): Promise<ContextSwitchOutcome>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/chrome/contextSwitcher/contextSwitch.ts
|
|
2
|
+
function e(e, t) {
|
|
3
|
+
return e.success ? e.projects?.find((e) => e.workspaceId === t) ?? null : null;
|
|
4
|
+
}
|
|
5
|
+
async function t(t, n, r) {
|
|
6
|
+
n.organizationId && t.setOrganization(n.organizationId), typeof n.tenantId == "string" && t.setTenant(n.tenantId), t.setWorkspace(n.id), t.setProject(null);
|
|
7
|
+
let i = await t.selectWorkspace(n);
|
|
8
|
+
if (r !== t.requests.current) return { status: "superseded" };
|
|
9
|
+
let a = e(i, n.id);
|
|
10
|
+
return a ? (t.selectProject(a), t.setProject(a.id), {
|
|
11
|
+
status: "project-selected",
|
|
12
|
+
workspaceId: n.id,
|
|
13
|
+
projectId: a.id
|
|
14
|
+
}) : {
|
|
15
|
+
status: "no-project",
|
|
16
|
+
workspaceId: n.id
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function n(e, n) {
|
|
20
|
+
return t(e, n, ++e.requests.current);
|
|
21
|
+
}
|
|
22
|
+
async function r(e, n) {
|
|
23
|
+
let r = ++e.requests.current;
|
|
24
|
+
e.setOrganization(n.id), typeof n.tenantId == "string" && e.setTenant(n.tenantId), e.setWorkspace(null), e.setProject(null);
|
|
25
|
+
let i = await e.selectOrganization(n);
|
|
26
|
+
if (r !== e.requests.current) return { status: "superseded" };
|
|
27
|
+
let a = i[0];
|
|
28
|
+
return a ? t(e, a, r) : { status: "no-workspace" };
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { e as firstProjectOfWorkspace, r as switchOrganization, n as switchWorkspace };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* resolveSwitchTarget — where to land when the active org/workspace changes.
|
|
3
|
+
*
|
|
4
|
+
* The workspace token is re-issued on every context switch (BOFF-2821). A route
|
|
5
|
+
* that holds a *resource id belonging to the old workspace* (a workflow, a run,
|
|
6
|
+
* an agent, a file …) would then fire queries the new token cannot authorize —
|
|
7
|
+
* the "Unexpected error" banners — or show the old workspace's record under the
|
|
8
|
+
* new workspace. So when the switch starts on such a resource-deep route the
|
|
9
|
+
* shell replace-navigates to that section's list page; the stale id is dropped.
|
|
10
|
+
*
|
|
11
|
+
* List/section pages (no resource id) are NOT redirected: they re-scope in place
|
|
12
|
+
* through the new selection. Each product passes its own rule table, because the
|
|
13
|
+
* route shapes are product-specific (VibeControls' `/vibecontrols/<section>/<id>`,
|
|
14
|
+
* FluidGrids' `/workflows/workflow/<key>/<version>` and `/workflows/runs/<id>`).
|
|
15
|
+
*
|
|
16
|
+
* Lifted from vibecontrols-app `src/routes/resolveSwitchTarget.ts` (BOFF-2821),
|
|
17
|
+
* with VibeControls' hard-coded tables turned into a parameter.
|
|
18
|
+
*/
|
|
19
|
+
/** One resource-deep route and the list/landing route that replaces it. */
|
|
20
|
+
export interface ContextSwitchRedirectRule {
|
|
21
|
+
/**
|
|
22
|
+
* Matches a pathname that carries a resource id of the old context. Rules are
|
|
23
|
+
* tried in order; the first match wins. A `g`/`y` flag is ignored (the match
|
|
24
|
+
* is always made from the start of a fresh state), so a shared rule table
|
|
25
|
+
* gives the same answer on every call.
|
|
26
|
+
*/
|
|
27
|
+
match: RegExp;
|
|
28
|
+
/** The landing to go to: a fixed path, or one computed from the match. */
|
|
29
|
+
to: string | ((match: RegExpExecArray) => string);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The target pathname to replace-navigate to after a context switch, or `null`
|
|
33
|
+
* to stay on `pathname` (only the context query params change).
|
|
34
|
+
*
|
|
35
|
+
* Returns `null` when no rule matches, and when the matching rule's landing is
|
|
36
|
+
* `pathname` itself, so a rule can never loop on its own landing page.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveSwitchTarget(pathname: string, rules: ReadonlyArray<ContextSwitchRedirectRule>): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* A rule for a family of `<prefix>/<section>/<id…>` detail routes: anything
|
|
41
|
+
* deeper than a `<prefix>/<section>` landing redirects back to that landing, so
|
|
42
|
+
* current and future detail routes are covered without enumerating each one.
|
|
43
|
+
* The bare `<prefix>/<section>` page and `<prefix>` itself stay in place.
|
|
44
|
+
*
|
|
45
|
+
* `sectionLandingRule('/vibecontrols')` is VibeControls' rule:
|
|
46
|
+
* `/vibecontrols/agents/a1` → `/vibecontrols/agents`,
|
|
47
|
+
* `/vibecontrols/calendar/task/t1` → `/vibecontrols/calendar`.
|
|
48
|
+
*
|
|
49
|
+
* @param prefix a rooted path without a trailing slash, e.g. `/vibecontrols`
|
|
50
|
+
*/
|
|
51
|
+
export declare function sectionLandingRule(prefix: string): ContextSwitchRedirectRule;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/chrome/contextSwitcher/resolveSwitchTarget.ts
|
|
2
|
+
function e(e, n) {
|
|
3
|
+
for (let r of n) {
|
|
4
|
+
let n = t(r.match).exec(e);
|
|
5
|
+
if (!n) continue;
|
|
6
|
+
let i = typeof r.to == "string" ? r.to : r.to(n);
|
|
7
|
+
return i === e ? null : i;
|
|
8
|
+
}
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
function t(e) {
|
|
12
|
+
return !e.global && !e.sticky ? e : new RegExp(e.source, e.flags.replace(/[gy]/g, ""));
|
|
13
|
+
}
|
|
14
|
+
function n(e) {
|
|
15
|
+
return e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
16
|
+
}
|
|
17
|
+
function r(e) {
|
|
18
|
+
if (!/^\/[^/]/.test(e) || e.endsWith("/")) throw Error(`sectionLandingRule: prefix must be a rooted path without a trailing slash, got "${e}"`);
|
|
19
|
+
let t = n(e);
|
|
20
|
+
return {
|
|
21
|
+
match: RegExp(`^${t}/([^/]+)/[^/]+`),
|
|
22
|
+
to: (t) => `${e}/${t[1]}`
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
export { e as resolveSwitchTarget, r as sectionLandingRule };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ContextSwitchOutcome, SwitchableOrganization, SwitchableProject, SwitchableWorkspace, WorkspaceSelection } from './contextSwitch';
|
|
2
|
+
import { ContextSwitchRedirectRule } from './resolveSwitchTarget';
|
|
3
|
+
/** Which header switch produced an outcome. */
|
|
4
|
+
export type ContextSwitchKind = 'organization' | 'workspace';
|
|
5
|
+
export interface ContextSwitchEvent {
|
|
6
|
+
kind: ContextSwitchKind;
|
|
7
|
+
/** The organization or workspace the user picked. */
|
|
8
|
+
id: string;
|
|
9
|
+
outcome: ContextSwitchOutcome;
|
|
10
|
+
}
|
|
11
|
+
export interface UseContextSwitchControllerOptions<O extends SwitchableOrganization, W extends SwitchableWorkspace, P extends SwitchableProject> {
|
|
12
|
+
/** The switcher's lists; a pick is resolved to its full record here. */
|
|
13
|
+
organizations: ReadonlyArray<O>;
|
|
14
|
+
workspaces: ReadonlyArray<W>;
|
|
15
|
+
projects: ReadonlyArray<P>;
|
|
16
|
+
/** microfe-auth: select the organization and list its workspaces. */
|
|
17
|
+
selectOrganization: (organization: O) => Promise<ReadonlyArray<W>>;
|
|
18
|
+
/** microfe-auth: issue the workspace token and list the workspace's projects. */
|
|
19
|
+
selectWorkspace: (workspace: W) => Promise<WorkspaceSelection<P>>;
|
|
20
|
+
/** microfe-auth: select a project. */
|
|
21
|
+
selectProject: (project: P) => void;
|
|
22
|
+
/**
|
|
23
|
+
* The product's resource-deep routes and their list pages. Omitted or empty:
|
|
24
|
+
* a switch never navigates.
|
|
25
|
+
*/
|
|
26
|
+
redirectRules?: ReadonlyArray<ContextSwitchRedirectRule>;
|
|
27
|
+
/** Called with every switch's outcome, superseded ones included. */
|
|
28
|
+
onSwitch?: (event: ContextSwitchEvent) => void;
|
|
29
|
+
}
|
|
30
|
+
export interface ContextSwitchController {
|
|
31
|
+
/**
|
|
32
|
+
* Switch to `organization` and enter its first workspace. Resolves to the
|
|
33
|
+
* outcome, or `null` when the id is not in `organizations`.
|
|
34
|
+
*/
|
|
35
|
+
onOrganizationChange: (organization: {
|
|
36
|
+
id: string;
|
|
37
|
+
}) => Promise<ContextSwitchOutcome | null>;
|
|
38
|
+
/**
|
|
39
|
+
* Switch to `workspace` and select its first project. Resolves to the
|
|
40
|
+
* outcome, or `null` when the id is not in `workspaces`.
|
|
41
|
+
*/
|
|
42
|
+
onWorkspaceChange: (workspace: {
|
|
43
|
+
id: string;
|
|
44
|
+
}) => Promise<ContextSwitchOutcome | null>;
|
|
45
|
+
/** Select `project` (an id missing from `projects` still becomes active). */
|
|
46
|
+
onProjectChange: (project: {
|
|
47
|
+
id: string;
|
|
48
|
+
}) => void;
|
|
49
|
+
}
|
|
50
|
+
export declare function useContextSwitchController<O extends SwitchableOrganization, W extends SwitchableWorkspace, P extends SwitchableProject>(options: UseContextSwitchControllerOptions<O, W, P>): ContextSwitchController;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useActiveContext as e } from "../../shared/providers/shell/ActiveContextProvider.js";
|
|
2
|
+
import { useLogger as t } from "../../shared/logger/useLogger.js";
|
|
3
|
+
import { switchOrganization as n, switchWorkspace as r } from "./contextSwitch.js";
|
|
4
|
+
import { resolveSwitchTarget as i } from "./resolveSwitchTarget.js";
|
|
5
|
+
import { useRef as a } from "react";
|
|
6
|
+
import { useLocation as o, useNavigate as s } from "react-router";
|
|
7
|
+
//#region src/chrome/contextSwitcher/useContextSwitchController.ts
|
|
8
|
+
function c(c) {
|
|
9
|
+
let { organizations: l, workspaces: u, projects: d, selectOrganization: f, selectWorkspace: p, selectProject: m, redirectRules: h, onSwitch: g } = c, _ = t("ContextSwitcher"), v = s(), { pathname: y } = o(), b = e(), x = {
|
|
10
|
+
requests: a(0),
|
|
11
|
+
setOrganization: b.setOrganization,
|
|
12
|
+
setWorkspace: b.setWorkspace,
|
|
13
|
+
setProject: b.setProject,
|
|
14
|
+
setTenant: b.setTenant,
|
|
15
|
+
selectOrganization: f,
|
|
16
|
+
selectWorkspace: p,
|
|
17
|
+
selectProject: m
|
|
18
|
+
}, S = () => {
|
|
19
|
+
if (!h || h.length === 0) return;
|
|
20
|
+
let e = i(y, h);
|
|
21
|
+
e && (_.info("Leaving a resource of the previous context", {
|
|
22
|
+
from: y,
|
|
23
|
+
to: e
|
|
24
|
+
}), v({
|
|
25
|
+
pathname: e,
|
|
26
|
+
search: typeof window > "u" ? "" : window.location.search
|
|
27
|
+
}, { replace: !0 }));
|
|
28
|
+
}, C = (e, t, n) => {
|
|
29
|
+
n.status === "superseded" && _.info(`Discarding stale ${e}-switch result`, { [`${e}Id`]: t }), g?.({
|
|
30
|
+
kind: e,
|
|
31
|
+
id: t,
|
|
32
|
+
outcome: n
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
return {
|
|
36
|
+
onOrganizationChange: async (e) => {
|
|
37
|
+
let t = l.find((t) => t.id === e.id);
|
|
38
|
+
if (!t) return _.warn("Organization switch ignored: not in the switcher list", { organizationId: e.id }), null;
|
|
39
|
+
_.info("Changing organization", { organizationId: t.id });
|
|
40
|
+
let r = t.id !== b.organizationId, i = n(x, t);
|
|
41
|
+
r && S();
|
|
42
|
+
let a = await i;
|
|
43
|
+
return C("organization", t.id, a), a;
|
|
44
|
+
},
|
|
45
|
+
onWorkspaceChange: async (e) => {
|
|
46
|
+
let t = u.find((t) => t.id === e.id);
|
|
47
|
+
if (!t) return _.warn("Workspace switch ignored: not in the switcher list", { workspaceId: e.id }), null;
|
|
48
|
+
_.info("Changing workspace", { workspaceId: t.id });
|
|
49
|
+
let n = t.id !== b.workspaceId, i = r(x, t);
|
|
50
|
+
n && S();
|
|
51
|
+
let a = await i;
|
|
52
|
+
return C("workspace", t.id, a), a;
|
|
53
|
+
},
|
|
54
|
+
onProjectChange: (e) => {
|
|
55
|
+
_.info("Changing project", { projectId: e.id });
|
|
56
|
+
let t = d.find((t) => t.id === e.id);
|
|
57
|
+
t && m(t), b.setProject(e.id);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { c as useContextSwitchController };
|
package/dist/chrome/index.d.ts
CHANGED
|
@@ -84,6 +84,12 @@ export type { LanguageSwitcherProps, Language } from './LanguageSwitcher';
|
|
|
84
84
|
export type { GeographySwitcherProps } from './GeographySwitcher';
|
|
85
85
|
export type { NotificationCenterProps } from './NotificationCenter';
|
|
86
86
|
export type { ContextSwitcherProps, Organization, Workspace, Project, } from './ContextSwitcher';
|
|
87
|
+
export { useContextSwitchController } from './contextSwitcher/useContextSwitchController';
|
|
88
|
+
export type { ContextSwitchController, ContextSwitchEvent, ContextSwitchKind, UseContextSwitchControllerOptions, } from './contextSwitcher/useContextSwitchController';
|
|
89
|
+
export { firstProjectOfWorkspace, switchOrganization, switchWorkspace, } from './contextSwitcher/contextSwitch';
|
|
90
|
+
export type { ContextSwitchDeps, ContextSwitchOutcome, SwitchableOrganization, SwitchableProject, SwitchableWorkspace, WorkspaceSelection, } from './contextSwitcher/contextSwitch';
|
|
91
|
+
export { resolveSwitchTarget, sectionLandingRule, } from './contextSwitcher/resolveSwitchTarget';
|
|
92
|
+
export type { ContextSwitchRedirectRule } from './contextSwitcher/resolveSwitchTarget';
|
|
87
93
|
export type { LanguagePickerSheetProps } from './LanguagePickerSheet';
|
|
88
94
|
export type { ProductPickerSheetProps } from './ProductPickerSheet';
|
|
89
95
|
export type { AccountSwitcherProps, Account } from './AccountSwitcher';
|
package/dist/chrome.js
CHANGED
|
@@ -23,26 +23,29 @@ import { NotificationCenter as D } from "./chrome/NotificationCenter.js";
|
|
|
23
23
|
import { LanguagePickerSheet as O } from "./chrome/LanguagePickerSheet.js";
|
|
24
24
|
import { ProductPickerSheet as k } from "./chrome/ProductPickerSheet.js";
|
|
25
25
|
import { AccountSwitcher as A } from "./chrome/AccountSwitcher.js";
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
48
|
-
|
|
26
|
+
import { firstProjectOfWorkspace as j, switchOrganization as M, switchWorkspace as N } from "./chrome/contextSwitcher/contextSwitch.js";
|
|
27
|
+
import { resolveSwitchTarget as P, sectionLandingRule as F } from "./chrome/contextSwitcher/resolveSwitchTarget.js";
|
|
28
|
+
import { useContextSwitchController as I } from "./chrome/contextSwitcher/useContextSwitchController.js";
|
|
29
|
+
import { AutoBreadcrumbs as L, registerBreadcrumbName as R, unregisterBreadcrumbName as z } from "./chrome/AutoBreadcrumbs.js";
|
|
30
|
+
import { NavCustomizeDialog as B } from "./chrome/NavCustomizeDialog.js";
|
|
31
|
+
import { PersonaSimulationPill as V, PersonaSurfaceControls as H } from "./chrome/PersonaSurfaceControls.js";
|
|
32
|
+
import { ProductSideNav as U } from "./chrome/ProductSideNav.js";
|
|
33
|
+
import { BrandMark as W } from "./chrome/BrandMark.js";
|
|
34
|
+
import { DynamicFooter as G } from "./chrome/DynamicFooter.js";
|
|
35
|
+
import { PanelTabs as K } from "./chrome/PanelTabs.js";
|
|
36
|
+
import { PanelContainer as q } from "./chrome/PanelContainer.js";
|
|
37
|
+
import { StatusBar as J } from "./chrome/StatusBar.js";
|
|
38
|
+
import { PluginRoutes as Y } from "./chrome/PluginRoutes.js";
|
|
39
|
+
import { PluginSettingsPage as X } from "./chrome/PluginSettingsPage.js";
|
|
40
|
+
import { PluginToolbar as Z } from "./chrome/PluginToolbar.js";
|
|
41
|
+
import { PluginWidgetGrid as Q } from "./chrome/PluginWidgetGrid.js";
|
|
42
|
+
import { PluginContextMenuTarget as $ } from "./chrome/PluginContextMenuTarget.js";
|
|
43
|
+
import { ThemePreferences as ee } from "./chrome/ThemePreferences.js";
|
|
44
|
+
import { selectMobileTabs as te } from "./chrome/mobileTabBar/selectTabs.js";
|
|
45
|
+
import { useMobileTabBarVisible as ne } from "./chrome/mobileTabBar/useMobileTabBarVisible.js";
|
|
46
|
+
import { MobileTabBar as re } from "./chrome/MobileTabBar.js";
|
|
47
|
+
import { NativeTopBar as ie } from "./chrome/NativeTopBar.js";
|
|
48
|
+
import { SwipeNavigation as ae } from "./chrome/SwipeNavigation.js";
|
|
49
|
+
import { NativePageTransition as oe } from "./chrome/NativePageTransition.js";
|
|
50
|
+
import { findNavTitle as se, useNativeTopBarTitle as ce } from "./chrome/nativeTopBarTitle.js";
|
|
51
|
+
export { A as AccountSwitcher, o as AppShellLayout, L as AutoBreadcrumbs, W as BrandMark, _ as Breadcrumbs, g as CommandPalette, w as ContextSwitcher, d as DEFAULT_FOOTER_COLUMNS, f as DEFAULT_SOCIAL_LINKS, G as DynamicFooter, p as Footer, l as FooterBar, T as GeographySwitcher, h as GlobalSearch, c as HeaderBar, O as LanguagePickerSheet, C as LanguageSwitcher, re as MobileTabBar, t as NAV_MORE_ID_PREFIX, oe as NativePageTransition, ie as NativeTopBar, B as NavCustomizeDialog, v as NavLink, D as NotificationCenter, s as PageHeader, q as PanelContainer, K as PanelTabs, V as PersonaSimulationPill, H as PersonaSurfaceControls, $ as PluginContextMenuTarget, u as PluginFooterStrip, Y as PluginRoutes, X as PluginSettingsPage, Z as PluginToolbar, Q as PluginWidgetGrid, k as ProductPickerSheet, U as ProductSideNav, S as ProductSwitcher, x as ProfileSwitcher, E as PushOptInToggle, e as ShortcutHelpDialog, m as SideNavigation, b as Sidebar, J as StatusBar, ae as SwipeNavigation, y as Tabs, ee as ThemePreferences, n as applyNavVisibility, r as applyPermissionFilter, i as applySurfaceVisibility, se as findNavTitle, j as firstProjectOfWorkspace, a as isItemEffectivelyShown, R as registerBreadcrumbName, P as resolveSwitchTarget, F as sectionLandingRule, te as selectMobileTabs, M as switchOrganization, N as switchWorkspace, z as unregisterBreadcrumbName, I as useContextSwitchController, ne as useMobileTabBarVisible, ce as useNativeTopBarTitle };
|
|
@@ -8,18 +8,18 @@ import { installNativeAuthNavigationIntercept as f } from "./auth-navigation.js"
|
|
|
8
8
|
import { createDefaultBackButtonHandlers as p, installBackButtonHandler as m } from "./back-button.js";
|
|
9
9
|
import { installExternalUrlOpener as h } from "./browser.js";
|
|
10
10
|
import { installNativeBridge as g } from "./bridge.js";
|
|
11
|
-
import {
|
|
12
|
-
import { populateDeviceInfo as
|
|
13
|
-
import { configureKeyboard as
|
|
14
|
-
import { installNetworkListener as
|
|
15
|
-
import { notifyOtaReady as
|
|
16
|
-
import { installPushNotifications as
|
|
17
|
-
import { installPushOptIn as
|
|
18
|
-
import { installPwa as
|
|
19
|
-
import { armSplashSafetyTimer as
|
|
20
|
-
import { installStatusBarThemeListener as
|
|
11
|
+
import { armDeepLinkFallback as _, disarmDeepLinkFallback as v, installDeepLinkHandler as y } from "./deep-link.js";
|
|
12
|
+
import { populateDeviceInfo as b } from "./device.js";
|
|
13
|
+
import { configureKeyboard as x } from "./keyboard.js";
|
|
14
|
+
import { installNetworkListener as S } from "./network.js";
|
|
15
|
+
import { notifyOtaReady as C } from "./ota.js";
|
|
16
|
+
import { installPushNotifications as w } from "./push.js";
|
|
17
|
+
import { installPushOptIn as T } from "./push-optin.js";
|
|
18
|
+
import { installPwa as E } from "./pwa.js";
|
|
19
|
+
import { armSplashSafetyTimer as D, markAppReady as O } from "./splash.js";
|
|
20
|
+
import { installStatusBarThemeListener as k } from "./system-bars.js";
|
|
21
21
|
//#region src/shell-native/boot.ts
|
|
22
|
-
function
|
|
22
|
+
function A(e, t) {
|
|
23
23
|
try {
|
|
24
24
|
let n = t();
|
|
25
25
|
n instanceof Promise && n.catch((t) => {
|
|
@@ -29,38 +29,40 @@ function O(e, t) {
|
|
|
29
29
|
console.debug(`[shell-native] boot step "${e}" failed:`, t);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
|
-
function
|
|
32
|
+
function j() {
|
|
33
33
|
if (typeof document > "u") return;
|
|
34
34
|
let e = document.querySelector("meta[name=\"viewport\"]");
|
|
35
35
|
if (!e) return;
|
|
36
36
|
let t = (e.content || "").split(",").map((e) => e.trim()).filter((e) => e.length > 0 && !e.startsWith("maximum-scale") && !e.startsWith("user-scalable") && !e.startsWith("interactive-widget"));
|
|
37
37
|
t.push("maximum-scale=1", "user-scalable=no"), o() === "android" && t.push("interactive-widget=resizes-content"), e.content = t.join(", ");
|
|
38
38
|
}
|
|
39
|
-
var
|
|
40
|
-
function
|
|
41
|
-
if (
|
|
42
|
-
|
|
39
|
+
var M = null;
|
|
40
|
+
function N(v) {
|
|
41
|
+
if (M) return M;
|
|
42
|
+
d(v.electronGlobalKey ?? `${v.product}Electron`), v.preview?.allow !== !1 && A("preview", () => n(i())), A("notifyOtaReady", () => C(v.plugins ?? {})), A("authIntercept", () => f(v.authIntercept)), A("externalUrlOpener", () => h()), A("nativeBridge", () => {
|
|
43
43
|
g(), a();
|
|
44
|
-
}),
|
|
45
|
-
...p(
|
|
46
|
-
...
|
|
47
|
-
})),
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
}), A("deviceInfo", () => b()), A("statusBarTheme", () => k()), A("keyboard", () => x()), A("backButton", () => m({
|
|
45
|
+
...p(v.appName),
|
|
46
|
+
...v.backButton
|
|
47
|
+
})), A("deepLinks", () => y());
|
|
48
|
+
let N = v.deepLinks?.fallbackDelayMs;
|
|
49
|
+
if (N !== !1 && A("deepLinkFallback", () => _(N ?? 4e3)), A("network", () => S(v.network ?? {})), A("runtimeHooks", () => u(v.runtimeHooks ?? {})), v.push?.enabled && v.plugins?.pushNotifications) {
|
|
50
|
+
let e = v.plugins.pushNotifications, t = v.plugins.localNotifications;
|
|
51
|
+
A("push", () => w(e, v.push ?? {}, t));
|
|
50
52
|
}
|
|
51
|
-
return l() &&
|
|
53
|
+
return l() && v.splash?.autoHide !== !1 && A("splashSafetyTimer", () => D(v.splash?.maxWaitMs ?? 8e3)), A("pwa", () => E({
|
|
52
54
|
enabled: !0,
|
|
53
|
-
...
|
|
54
|
-
})),
|
|
55
|
+
...v.pwa
|
|
56
|
+
})), A("pushOptIn", () => T()), t() && A("desktopShell", () => e(v.desktop)), s() && A("viewport", () => j()), M = {
|
|
55
57
|
platform: o(),
|
|
56
58
|
isNative: s(),
|
|
57
59
|
isPreview: c(),
|
|
58
60
|
previewMode: r(),
|
|
59
|
-
markAppReady:
|
|
60
|
-
},
|
|
61
|
+
markAppReady: O
|
|
62
|
+
}, M;
|
|
61
63
|
}
|
|
62
|
-
function
|
|
63
|
-
|
|
64
|
+
function P() {
|
|
65
|
+
M = null, v();
|
|
64
66
|
}
|
|
65
67
|
//#endregion
|
|
66
|
-
export {
|
|
68
|
+
export { N as bootNativeShell, P as resetNativeShellBootForTests };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ActiveContextUrlParams } from '../shared/providers/shell/ActiveContextStorage';
|
|
2
|
+
import { DeepLinkNavigator } from './deep-link';
|
|
3
|
+
/** The `setContext` payload of `ActiveContextProvider` (`useActiveContext().setContext`). */
|
|
4
|
+
export type DeepLinkContextUpdate = Partial<{
|
|
5
|
+
organizationId: string | null;
|
|
6
|
+
workspaceId: string | null;
|
|
7
|
+
projectId: string | null;
|
|
8
|
+
tenantId: string | null;
|
|
9
|
+
}>;
|
|
10
|
+
/**
|
|
11
|
+
* Ids a deep link may set as active context: a UUID or a cuid. Anything else is
|
|
12
|
+
* ignored so a crafted link cannot plant junk in the stored context.
|
|
13
|
+
*/
|
|
14
|
+
export declare const DEEP_LINK_CONTEXT_ID_PATTERN: RegExp;
|
|
15
|
+
export interface DeepLinkContextOptions {
|
|
16
|
+
/** Query param names; defaults to `?org=&workspace=&project=&tenant=`. */
|
|
17
|
+
urlParams?: ActiveContextUrlParams;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Read the active-context ids a deep-link path carries. `path` is
|
|
21
|
+
* `pathname + search + hash`; the `#fragment` is ignored. Returns `null` when
|
|
22
|
+
* the link names no (valid) context.
|
|
23
|
+
*
|
|
24
|
+
* A link that names an organization or workspace but no project clears the
|
|
25
|
+
* project, so a project from the previous workspace cannot drive
|
|
26
|
+
* workspace-token issuance.
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseDeepLinkContext(path: string, options?: DeepLinkContextOptions): DeepLinkContextUpdate | null;
|
|
29
|
+
export interface DeepLinkNavigatorOptions extends DeepLinkContextOptions {
|
|
30
|
+
/**
|
|
31
|
+
* Commit the link's context before navigating — pass
|
|
32
|
+
* `useActiveContext().setContext`. A deep link arrives through `navigate()`
|
|
33
|
+
* (no reload, no `popstate`), so `ActiveContextProvider` would otherwise never
|
|
34
|
+
* see its `?org/workspace/project/tenant`.
|
|
35
|
+
*/
|
|
36
|
+
setContext?: (context: DeepLinkContextUpdate) => void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Build the navigator `registerDeepLinkNavigator` expects: it drops paths that
|
|
40
|
+
* are not same-origin in-app paths, commits the link's context once, then
|
|
41
|
+
* navigates. This is action-driven (it only runs for an actual deep link or
|
|
42
|
+
* push tap), so it cannot set up a URL ↔ context write-back loop.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createDeepLinkNavigator(navigate: (path: string) => void, options?: DeepLinkNavigatorOptions): DeepLinkNavigator;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { DEFAULT_ACTIVE_CONTEXT_URL_PARAMS as e } from "../shared/providers/shell/ActiveContextStorage.js";
|
|
2
|
+
import { isSafeInAppPath as t } from "./deep-link.js";
|
|
3
|
+
//#region src/shell-native/deep-link-context.ts
|
|
4
|
+
var n = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|c[a-z0-9]{8,32})$/i;
|
|
5
|
+
function r(t, r = {}) {
|
|
6
|
+
let i = t.indexOf("?");
|
|
7
|
+
if (i === -1) return null;
|
|
8
|
+
let a = t.indexOf("#", i), o = a === -1 ? t.slice(i) : t.slice(i, a), s = new URLSearchParams(o), c = {
|
|
9
|
+
...e,
|
|
10
|
+
...r.urlParams
|
|
11
|
+
}, l = (e) => {
|
|
12
|
+
let t = s.get(e);
|
|
13
|
+
return t && n.test(t) ? t : null;
|
|
14
|
+
}, u = l(c.organization), d = l(c.workspace), f = l(c.project), p = l(c.tenant), m = {};
|
|
15
|
+
return u && (m.organizationId = u), d && (m.workspaceId = d), f ? m.projectId = f : (u || d) && (m.projectId = null), p && (m.tenantId = p), Object.keys(m).length > 0 ? m : null;
|
|
16
|
+
}
|
|
17
|
+
function i(e, n = {}) {
|
|
18
|
+
return (i) => {
|
|
19
|
+
if (!t(i)) {
|
|
20
|
+
console.debug("[shell-native] ignored a deep link that is not an in-app path");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (n.setContext) {
|
|
24
|
+
let e = r(i, n);
|
|
25
|
+
e && n.setContext(e);
|
|
26
|
+
}
|
|
27
|
+
e(i);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { n as DEEP_LINK_CONTEXT_ID_PATTERN, i as createDeepLinkNavigator, r as parseDeepLinkContext };
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
type DeepLinkNavigator = (path: string) => void;
|
|
1
|
+
export type DeepLinkNavigator = (path: string) => void;
|
|
2
|
+
/**
|
|
3
|
+
* How long `bootNativeShell` waits for a router to call
|
|
4
|
+
* `registerDeepLinkNavigator` before it drains queued deep links itself
|
|
5
|
+
* through `history.pushState` + `popstate` (see `armDeepLinkFallback`).
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_DEEP_LINK_FALLBACK_DELAY_MS = 4000;
|
|
8
|
+
/**
|
|
9
|
+
* True for a same-origin, in-app path: `/x`, `/x?y#z`. Refuses protocol-relative
|
|
10
|
+
* (`//host`, `/\host`) and anything that is not a rooted path, so a deep link or
|
|
11
|
+
* push payload can never steer the webview to another origin.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isSafeInAppPath(path: string): boolean;
|
|
2
14
|
/**
|
|
3
15
|
* Map a deep-link URL to an in-app SPA path. Handles both
|
|
4
16
|
* `scheme://app/path?x` (custom scheme; host is the synthetic `app`) and
|
|
@@ -9,10 +21,25 @@ export declare function deepLinkUrlToPath(rawUrl: string): string | null;
|
|
|
9
21
|
export declare function installDeepLinkHandler(): Promise<void>;
|
|
10
22
|
/**
|
|
11
23
|
* Register the router's navigate function. Links that arrived before the router
|
|
12
|
-
* mounted are queued and flushed here.
|
|
24
|
+
* mounted are queued and flushed here. Returns a disposer that unregisters this
|
|
25
|
+
* navigator only if it is still the active one (a newer registration wins).
|
|
26
|
+
*
|
|
27
|
+
* Prefer `useDeepLinkNavigator` in React shells — it also hydrates the active
|
|
28
|
+
* context from the link's `?org/workspace/project/tenant`.
|
|
13
29
|
*/
|
|
14
|
-
export declare function registerDeepLinkNavigator(navigate: DeepLinkNavigator): void;
|
|
30
|
+
export declare function registerDeepLinkNavigator(navigate: DeepLinkNavigator): () => void;
|
|
15
31
|
export declare function clearDeepLinkNavigator(): void;
|
|
16
32
|
/** Route a path through the active navigator (or queue it) — used by push taps. */
|
|
17
33
|
export declare function navigateDeepLinkPath(path: string): void;
|
|
18
|
-
|
|
34
|
+
/**
|
|
35
|
+
* Safety net for shells whose router never registers a navigator: once
|
|
36
|
+
* `delayMs` has passed without a registration, queued links (and every later
|
|
37
|
+
* link while no navigator is registered) navigate through `history.pushState`
|
|
38
|
+
* + `popstate`. A navigator registered later always takes precedence. Calling
|
|
39
|
+
* it again re-arms with the new delay; `bootNativeShell` arms it.
|
|
40
|
+
*/
|
|
41
|
+
export declare function armDeepLinkFallback(delayMs?: number): void;
|
|
42
|
+
/** Cancel the fallback timer and leave router-less navigation off. */
|
|
43
|
+
export declare function disarmDeepLinkFallback(): void;
|
|
44
|
+
/** Test-only: drop the queue, the navigator and the fallback. */
|
|
45
|
+
export declare function resetDeepLinksForTests(): void;
|
|
@@ -3,14 +3,35 @@ import { isNativePluginAvailable as t } from "./platform.js";
|
|
|
3
3
|
import { getLegacyElectronBridge as n } from "./desktop-bridge.js";
|
|
4
4
|
import { App as r } from "@capacitor/app";
|
|
5
5
|
//#region src/shell-native/deep-link.ts
|
|
6
|
-
var i = [],
|
|
7
|
-
function
|
|
8
|
-
if (!
|
|
9
|
-
let
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
6
|
+
var i = 4e3, a = [], o = null, s = null, c = !1;
|
|
7
|
+
function l(e) {
|
|
8
|
+
if (typeof e != "string" || !e.startsWith("/") || e.startsWith("//") || e.startsWith("/\\")) return !1;
|
|
9
|
+
for (let t = 0; t < e.length; t += 1) {
|
|
10
|
+
let n = e.charCodeAt(t);
|
|
11
|
+
if (n < 32 || n === 127) return !1;
|
|
12
|
+
}
|
|
13
|
+
return !0;
|
|
14
|
+
}
|
|
15
|
+
function u(e) {
|
|
16
|
+
if (l(e) && !(typeof window > "u")) try {
|
|
17
|
+
window.history.pushState(null, "", e);
|
|
18
|
+
let t = typeof PopStateEvent == "function" ? new PopStateEvent("popstate", { state: null }) : new Event("popstate");
|
|
19
|
+
window.dispatchEvent(t);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.debug("[shell-native] deep-link fallback navigation failed:", e);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function d() {
|
|
25
|
+
return o || (c ? u : null);
|
|
26
|
+
}
|
|
27
|
+
function f() {
|
|
28
|
+
let e = d();
|
|
29
|
+
if (!e || a.length === 0) return;
|
|
30
|
+
let t = a;
|
|
31
|
+
a = [];
|
|
32
|
+
for (let n of t) e(n);
|
|
33
|
+
}
|
|
34
|
+
function p(e) {
|
|
14
35
|
let t;
|
|
15
36
|
try {
|
|
16
37
|
t = new URL(e);
|
|
@@ -19,35 +40,49 @@ function s(e) {
|
|
|
19
40
|
}
|
|
20
41
|
return `${t.pathname || "/"}${t.search}${t.hash}`;
|
|
21
42
|
}
|
|
22
|
-
function
|
|
23
|
-
let t =
|
|
24
|
-
t !== null && (
|
|
43
|
+
function m(e) {
|
|
44
|
+
let t = p(e);
|
|
45
|
+
t !== null && b(t);
|
|
25
46
|
}
|
|
26
|
-
async function
|
|
47
|
+
async function h() {
|
|
27
48
|
if (t("App")) try {
|
|
28
|
-
await r.addListener("appUrlOpen", (e) =>
|
|
49
|
+
await r.addListener("appUrlOpen", (e) => m(e.url));
|
|
29
50
|
} catch {}
|
|
30
51
|
}
|
|
31
|
-
function
|
|
52
|
+
function g() {
|
|
32
53
|
let t = e();
|
|
33
54
|
if (t && typeof t.onDeepLink == "function") {
|
|
34
|
-
t.onDeepLink((e) =>
|
|
55
|
+
t.onDeepLink((e) => m(e));
|
|
35
56
|
return;
|
|
36
57
|
}
|
|
37
58
|
let r = n();
|
|
38
|
-
r && typeof r.onDeepLink == "function" && r.onDeepLink((e) =>
|
|
59
|
+
r && typeof r.onDeepLink == "function" && r.onDeepLink((e) => m(e));
|
|
39
60
|
}
|
|
40
|
-
async function
|
|
41
|
-
|
|
61
|
+
async function _() {
|
|
62
|
+
g(), await h();
|
|
42
63
|
}
|
|
43
|
-
function
|
|
44
|
-
|
|
64
|
+
function v(e) {
|
|
65
|
+
return o = e, f(), () => {
|
|
66
|
+
o === e && (o = null);
|
|
67
|
+
};
|
|
45
68
|
}
|
|
46
|
-
function
|
|
47
|
-
|
|
69
|
+
function y() {
|
|
70
|
+
o = null;
|
|
48
71
|
}
|
|
49
|
-
function
|
|
50
|
-
|
|
72
|
+
function b(e) {
|
|
73
|
+
let t = d();
|
|
74
|
+
t ? t(e) : a.push(e);
|
|
75
|
+
}
|
|
76
|
+
function x(e = i) {
|
|
77
|
+
S(), s = setTimeout(() => {
|
|
78
|
+
s = null, c = !0, !o && a.length > 0 && console.debug("[shell-native] no deep-link navigator registered; routing queued links via history"), f();
|
|
79
|
+
}, Number.isFinite(e) && e > 0 ? e : 0);
|
|
80
|
+
}
|
|
81
|
+
function S() {
|
|
82
|
+
s !== null && clearTimeout(s), s = null, c = !1;
|
|
83
|
+
}
|
|
84
|
+
function C() {
|
|
85
|
+
S(), a = [], o = null;
|
|
51
86
|
}
|
|
52
87
|
//#endregion
|
|
53
|
-
export {
|
|
88
|
+
export { i as DEFAULT_DEEP_LINK_FALLBACK_DELAY_MS, x as armDeepLinkFallback, y as clearDeepLinkNavigator, p as deepLinkUrlToPath, S as disarmDeepLinkFallback, _ as installDeepLinkHandler, l as isSafeInAppPath, b as navigateDeepLinkPath, v as registerDeepLinkNavigator, C as resetDeepLinksForTests };
|
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
*/
|
|
28
28
|
export { bootNativeShell, resetNativeShellBootForTests } from './boot';
|
|
29
29
|
export { APP_READY_EVENT, armSplashSafetyTimer, isAppReady, markAppReady, resetAppReadyForTests, } from './splash';
|
|
30
|
-
export { clearDeepLinkNavigator, deepLinkUrlToPath, installDeepLinkHandler, navigateDeepLinkPath, registerDeepLinkNavigator, } from './deep-link';
|
|
30
|
+
export { armDeepLinkFallback, clearDeepLinkNavigator, DEFAULT_DEEP_LINK_FALLBACK_DELAY_MS, deepLinkUrlToPath, disarmDeepLinkFallback, installDeepLinkHandler, isSafeInAppPath, navigateDeepLinkPath, registerDeepLinkNavigator, resetDeepLinksForTests, type DeepLinkNavigator, } from './deep-link';
|
|
31
|
+
export { createDeepLinkNavigator, DEEP_LINK_CONTEXT_ID_PATTERN, parseDeepLinkContext, type DeepLinkContextOptions, type DeepLinkContextUpdate, type DeepLinkNavigatorOptions, } from './deep-link-context';
|
|
32
|
+
export { useDeepLinkNavigator } from './useDeepLinkNavigator';
|
|
31
33
|
export { installNativeBridge } from './bridge';
|
|
32
34
|
export { applyNativePlatformClass, getNativePlatform, isNativePlatform, isNativePluginAvailable, isNativePreview, isRealNativePlatform, } from './platform';
|
|
33
35
|
export { applyNativePreview, getActiveNativePreview, resolveNativePreview, NATIVE_PREVIEW_QUERY_PARAM, NATIVE_PREVIEW_STORAGE_KEY, } from './preview';
|
|
@@ -318,6 +318,15 @@ export interface BootNativeShellOptions {
|
|
|
318
318
|
desktop?: {
|
|
319
319
|
navigate?: (path: string) => void;
|
|
320
320
|
};
|
|
321
|
+
/**
|
|
322
|
+
* Deep-link queue. Links queue until the router registers a navigator
|
|
323
|
+
* (`useDeepLinkNavigator`). If none registers within `fallbackDelayMs`
|
|
324
|
+
* (default 4000), queued and later links navigate through
|
|
325
|
+
* `history.pushState` + `popstate` instead. `false` disables the fallback.
|
|
326
|
+
*/
|
|
327
|
+
deepLinks?: {
|
|
328
|
+
fallbackDelayMs?: number | false;
|
|
329
|
+
};
|
|
321
330
|
}
|
|
322
331
|
export interface NativeShellHandle {
|
|
323
332
|
platform: NativeShellPlatform;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DeepLinkNavigatorOptions } from './deep-link-context';
|
|
2
|
+
/**
|
|
3
|
+
* Bind the native deep-link queue (Capacitor `appUrlOpen`, the desktop
|
|
4
|
+
* bridge's `onDeepLink`, native and web push taps) to the shell's router.
|
|
5
|
+
* Mount it ONCE, inside the router and inside `ActiveContextProvider`:
|
|
6
|
+
*
|
|
7
|
+
* ```tsx
|
|
8
|
+
* const navigate = useNavigate();
|
|
9
|
+
* const { setContext } = useActiveContext();
|
|
10
|
+
* useDeepLinkNavigator(navigate, { setContext });
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* Links that arrived before mount (cold-start OAuth callbacks, the push tap
|
|
14
|
+
* that launched the app) are flushed on mount. The latest `navigate` and
|
|
15
|
+
* options are always used, so callers need not memoise them. On unmount the
|
|
16
|
+
* navigator is unregistered unless a newer registration replaced it.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useDeepLinkNavigator(navigate: (path: string) => void, options?: DeepLinkNavigatorOptions): void;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { registerDeepLinkNavigator as e } from "./deep-link.js";
|
|
2
|
+
import { createDeepLinkNavigator as t } from "./deep-link-context.js";
|
|
3
|
+
import { useEffect as n, useRef as r } from "react";
|
|
4
|
+
//#region src/shell-native/useDeepLinkNavigator.ts
|
|
5
|
+
function i(i, a = {}) {
|
|
6
|
+
let o = r(i), s = r(a);
|
|
7
|
+
n(() => {
|
|
8
|
+
o.current = i, s.current = a;
|
|
9
|
+
}), n(() => e(t((e) => o.current(e), {
|
|
10
|
+
get urlParams() {
|
|
11
|
+
return s.current.urlParams;
|
|
12
|
+
},
|
|
13
|
+
get setContext() {
|
|
14
|
+
return s.current.setContext;
|
|
15
|
+
}
|
|
16
|
+
})), []);
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { i as useDeepLinkNavigator };
|
package/dist/shell-native.js
CHANGED
|
@@ -13,19 +13,21 @@ import { impactFeedback as D, notificationFeedback as O, selectionFeedback as k
|
|
|
13
13
|
import { showNativeToast as A } from "./shell-native/toast.js";
|
|
14
14
|
import { shareContent as j } from "./shell-native/share.js";
|
|
15
15
|
import { installNativeBridge as M } from "./shell-native/bridge.js";
|
|
16
|
-
import {
|
|
17
|
-
import { populateDeviceInfo as
|
|
18
|
-
import { KEYBOARD_HEIGHT_CSS_VAR as
|
|
19
|
-
import { NETWORK_STATUS_EVENT as
|
|
20
|
-
import { notifyOtaReady as
|
|
21
|
-
import { PUSH_TOKEN_EVENT as
|
|
22
|
-
import { SW_NOTIFICATION_CLICK_MESSAGE as
|
|
23
|
-
import { installPushOptIn as
|
|
24
|
-
import { PWA_INSTALLED_EVENT as
|
|
25
|
-
import { APP_READY_EVENT as
|
|
26
|
-
import { DARK_FAMILY_THEMES as
|
|
27
|
-
import { bootNativeShell as
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
|
|
16
|
+
import { DEFAULT_DEEP_LINK_FALLBACK_DELAY_MS as N, armDeepLinkFallback as P, clearDeepLinkNavigator as F, deepLinkUrlToPath as I, disarmDeepLinkFallback as L, installDeepLinkHandler as R, isSafeInAppPath as z, navigateDeepLinkPath as B, registerDeepLinkNavigator as V, resetDeepLinksForTests as H } from "./shell-native/deep-link.js";
|
|
17
|
+
import { populateDeviceInfo as U } from "./shell-native/device.js";
|
|
18
|
+
import { KEYBOARD_HEIGHT_CSS_VAR as W, KEYBOARD_OPEN_CLASS as G, configureKeyboard as K, keyboardHandlers as q } from "./shell-native/keyboard.js";
|
|
19
|
+
import { NETWORK_STATUS_EVENT as J, getLastNetworkStatus as Y, installNetworkListener as X } from "./shell-native/network.js";
|
|
20
|
+
import { notifyOtaReady as Z } from "./shell-native/ota.js";
|
|
21
|
+
import { PUSH_TOKEN_EVENT as Q, emitPushToken as $, installPushNotifications as ee, requestPushPermission as te } from "./shell-native/push.js";
|
|
22
|
+
import { SW_NOTIFICATION_CLICK_MESSAGE as ne, SW_PUSH_SUBSCRIPTION_CHANGE_MESSAGE as re, WEB_PUSH_MANIFEST_KEY as ie, getWebPushManifestConfig as ae, getWebPushSubscription as oe, installWebPushMessageBridge as se, isWebPushSupported as ce, requestWebPushPermission as le, resetWebPushForTests as ue, resolveVapidPublicKey as de, serializeWebPushSubscription as fe, subscribeWebPush as pe, unsubscribeWebPush as me, urlBase64ToUint8Array as he } from "./shell-native/web-push.js";
|
|
23
|
+
import { installPushOptIn as ge, resetPushOptInForTests as _e } from "./shell-native/push-optin.js";
|
|
24
|
+
import { PWA_INSTALLED_EVENT as ve, PWA_INSTALL_AVAILABLE_EVENT as ye, PWA_UPDATE_READY_EVENT as be, installPwa as xe, installPwaPrompt as Se, isPwaEligibleHost as Ce, resetPwaForTests as we } from "./shell-native/pwa.js";
|
|
25
|
+
import { APP_READY_EVENT as Te, armSplashSafetyTimer as Ee, isAppReady as De, markAppReady as Oe, resetAppReadyForTests as ke } from "./shell-native/splash.js";
|
|
26
|
+
import { DARK_FAMILY_THEMES as Ae, applyStatusBarTheme as je, installStatusBarThemeListener as Me, isDarkThemeName as Ne, resolveDocumentIsDark as Pe, statusBarDetailIsDark as Fe } from "./shell-native/system-bars.js";
|
|
27
|
+
import { bootNativeShell as Ie, resetNativeShellBootForTests as Le } from "./shell-native/boot.js";
|
|
28
|
+
import { DEEP_LINK_CONTEXT_ID_PATTERN as Re, createDeepLinkNavigator as ze, parseDeepLinkContext as Be } from "./shell-native/deep-link-context.js";
|
|
29
|
+
import { useDeepLinkNavigator as Ve } from "./shell-native/useDeepLinkNavigator.js";
|
|
30
|
+
import { defineCapacitorConfig as He, isValidAllowNavigationRule as Ue } from "./shell-native/capacitor-config.js";
|
|
31
|
+
import { defineWebManifest as We } from "./shell-native/web-manifest.js";
|
|
32
|
+
import { SERVICE_WORKER_TEMPLATE as Ge, renderServiceWorker as Ke } from "./shell-native/service-worker.js";
|
|
33
|
+
export { Te as APP_READY_EVENT, Ae as DARK_FAMILY_THEMES, Re as DEEP_LINK_CONTEXT_ID_PATTERN, _ as DEFAULT_AUTH_INTERCEPT_PREFIXES, N as DEFAULT_DEEP_LINK_FALLBACK_DELAY_MS, W as KEYBOARD_HEIGHT_CSS_VAR, G as KEYBOARD_OPEN_CLASS, t as NATIVE_PREVIEW_QUERY_PARAM, n as NATIVE_PREVIEW_STORAGE_KEY, J as NETWORK_STATUS_EVENT, Q as PUSH_TOKEN_EVENT, ve as PWA_INSTALLED_EVENT, ye as PWA_INSTALL_AVAILABLE_EVENT, be as PWA_UPDATE_READY_EVENT, Ge as SERVICE_WORKER_TEMPLATE, ne as SW_NOTIFICATION_CLICK_MESSAGE, re as SW_PUSH_SUBSCRIPTION_CHANGE_MESSAGE, ie as WEB_PUSH_MANIFEST_KEY, o as applyNativePlatformClass, r as applyNativePreview, je as applyStatusBarTheme, P as armDeepLinkFallback, Ee as armSplashSafetyTimer, Ie as bootNativeShell, F as clearDeepLinkNavigator, p as configureElectronGlobalKey, K as configureKeyboard, ze as createDeepLinkNavigator, b as createDefaultBackButtonHandlers, I as deepLinkUrlToPath, He as defineCapacitorConfig, We as defineWebManifest, L as disarmDeepLinkFallback, $ as emitPushToken, i as getActiveNativePreview, e as getDesktopBridge, m as getElectronGlobalKey, Y as getLastNetworkStatus, h as getLegacyElectronBridge, s as getNativePlatform, ae as getWebPushManifestConfig, oe as getWebPushSubscription, D as impactFeedback, x as installBackButtonHandler, R as installDeepLinkHandler, w as installExternalUrlOpener, f as installMobileRuntimeHooks, v as installNativeAuthNavigationIntercept, M as installNativeBridge, X as installNetworkListener, ee as installPushNotifications, ge as installPushOptIn, xe as installPwa, Se as installPwaPrompt, Me as installStatusBarThemeListener, se as installWebPushMessageBridge, De as isAppReady, Ne as isDarkThemeName, g as isDesktopShell, c as isNativePlatform, l as isNativePluginAvailable, u as isNativePreview, Ce as isPwaEligibleHost, d as isRealNativePlatform, z as isSafeInAppPath, Ue as isValidAllowNavigationRule, ce as isWebPushSupported, q as keyboardHandlers, Oe as markAppReady, B as navigateDeepLinkPath, O as notificationFeedback, Z as notifyOtaReady, T as openExternalUrl, Be as parseDeepLinkContext, U as populateDeviceInfo, V as registerDeepLinkNavigator, Ke as renderServiceWorker, te as requestPushPermission, le as requestWebPushPermission, ke as resetAppReadyForTests, H as resetDeepLinksForTests, Le as resetNativeShellBootForTests, _e as resetPushOptInForTests, we as resetPwaForTests, ue as resetWebPushForTests, Pe as resolveDocumentIsDark, a as resolveNativePreview, de as resolveVapidPublicKey, S as runBackButtonPrecedence, k as selectionFeedback, fe as serializeWebPushSubscription, j as shareContent, C as showNativeActionSheet, y as showNativeConfirm, A as showNativeToast, Fe as statusBarDetailIsDark, pe as subscribeWebPush, me as unsubscribeWebPush, he as urlBase64ToUint8Array, Ve as useDeepLinkNavigator, E as writeClipboardText };
|