@shipeasy/devtools-core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +40 -0
- package/README.md +19 -0
- package/dist/api.d.mts +143 -0
- package/dist/api.d.ts +143 -0
- package/dist/api.js +10 -0
- package/dist/api.mjs +10 -0
- package/dist/auth.d.mts +57 -0
- package/dist/auth.d.ts +57 -0
- package/dist/auth.js +12 -0
- package/dist/auth.mjs +12 -0
- package/dist/chunk-2B75JLTX.js +25 -0
- package/dist/chunk-5VQLXGNR.mjs +101 -0
- package/dist/chunk-6SU5REDK.mjs +2848 -0
- package/dist/chunk-7NO55TFC.mjs +25 -0
- package/dist/chunk-GUTBZI2O.js +2848 -0
- package/dist/chunk-I2KLQ2HA.js +22 -0
- package/dist/chunk-KPO3R7ND.js +28 -0
- package/dist/chunk-LCRTLHGL.js +101 -0
- package/dist/chunk-LZDNBMH6.mjs +1394 -0
- package/dist/chunk-MK56AKZR.js +178 -0
- package/dist/chunk-RANNOC72.js +72 -0
- package/dist/chunk-RH6X24HG.mjs +28 -0
- package/dist/chunk-U5DDQYSF.mjs +22 -0
- package/dist/chunk-VHYOR3XA.mjs +178 -0
- package/dist/chunk-WP2TWHG3.mjs +72 -0
- package/dist/chunk-YQHCUZGP.js +1394 -0
- package/dist/feedback-state.d.mts +29 -0
- package/dist/feedback-state.d.ts +29 -0
- package/dist/feedback-state.js +9 -0
- package/dist/feedback-state.mjs +9 -0
- package/dist/forms.d.mts +95 -0
- package/dist/forms.d.ts +95 -0
- package/dist/forms.js +21 -0
- package/dist/forms.mjs +21 -0
- package/dist/gate-flow.d.mts +41 -0
- package/dist/gate-flow.d.ts +41 -0
- package/dist/gate-flow.js +17 -0
- package/dist/gate-flow.mjs +17 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +86 -0
- package/dist/index.mjs +86 -0
- package/dist/public-report.d.mts +56 -0
- package/dist/public-report.d.ts +56 -0
- package/dist/public-report.js +12 -0
- package/dist/public-report.mjs +12 -0
- package/dist/self-report.d.mts +25 -0
- package/dist/self-report.d.ts +25 -0
- package/dist/self-report.js +73 -0
- package/dist/self-report.mjs +73 -0
- package/dist/types-D0sroUDD.d.mts +278 -0
- package/dist/types-D0sroUDD.d.ts +278 -0
- package/dist/zod.gen-DTKpgw-T.d.mts +64 -0
- package/dist/zod.gen-DTKpgw-T.d.ts +64 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Shipeasy Source-Available License (Shipeasy-SAL) 1.0
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shipeasy, Inc. All rights reserved.
|
|
4
|
+
|
|
5
|
+
1. License Grant.
|
|
6
|
+
Subject to the terms of this License, Shipeasy, Inc. ("Shipeasy") grants
|
|
7
|
+
you a non-exclusive, non-transferable, revocable, worldwide license to:
|
|
8
|
+
|
|
9
|
+
(a) Use, copy, and modify the Software solely as a client integration for
|
|
10
|
+
interacting with Shipeasy's hosted services (the "Service");
|
|
11
|
+
(b) Distribute the Software as part of an application that calls the
|
|
12
|
+
Service, in object form, provided the recipient also agrees to this
|
|
13
|
+
License.
|
|
14
|
+
|
|
15
|
+
2. Restrictions.
|
|
16
|
+
You may not:
|
|
17
|
+
|
|
18
|
+
(a) Use the Software, in whole or in part, to build, host, or operate any
|
|
19
|
+
service that competes with the Service or that provides feature-flag,
|
|
20
|
+
experimentation, configuration, internationalization, or related
|
|
21
|
+
functionality to third parties on a commercial basis;
|
|
22
|
+
(b) Sublicense, sell, rent, or lease the Software;
|
|
23
|
+
(c) Remove or alter copyright notices, license terms, or attribution.
|
|
24
|
+
|
|
25
|
+
3. Contributions.
|
|
26
|
+
Any pull request you submit is licensed back to Shipeasy under this
|
|
27
|
+
License plus a perpetual, irrevocable right for Shipeasy to relicense.
|
|
28
|
+
|
|
29
|
+
4. Trademarks.
|
|
30
|
+
This License does not grant rights in the names "Shipeasy", related
|
|
31
|
+
marks, or logos.
|
|
32
|
+
|
|
33
|
+
5. No Warranty / Limitation of Liability.
|
|
34
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. IN NO
|
|
35
|
+
EVENT SHALL SHIPEASY BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
|
|
36
|
+
LIABILITY ARISING FROM USE OF THE SOFTWARE.
|
|
37
|
+
|
|
38
|
+
6. Termination.
|
|
39
|
+
This License terminates automatically if you breach it. Sections 2-5
|
|
40
|
+
survive termination.
|
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# @shipeasy/devtools-core
|
|
2
|
+
|
|
3
|
+
Headless core shared by the [Shipeasy](https://shipeasy.ai) devtools overlays —
|
|
4
|
+
the admin API client, PKCE device auth, public bug intake, and the generated
|
|
5
|
+
form schemas.
|
|
6
|
+
|
|
7
|
+
You normally do not install this directly: it comes with
|
|
8
|
+
[`@shipeasy/react-native-devtools`](https://www.npmjs.com/package/@shipeasy/react-native-devtools).
|
|
9
|
+
Install it on its own only to build a custom devtools surface.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @shipeasy/devtools-core
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
It reads the app's live SDK state over the `globalThis` bridge the client Engine
|
|
16
|
+
publishes (`@shipeasy/sdk/devtools-contract`) rather than importing the client,
|
|
17
|
+
so it can never inline a second Engine. `@shipeasy/sdk` is therefore a peer.
|
|
18
|
+
|
|
19
|
+
Full docs: <https://shipeasy-ai.github.io/sdk-ts/>
|
package/dist/api.d.mts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { o as ProjectRecord, G as GateRecord, C as ConfigRecord, E as ExperimentRecord, U as UniverseRecord, m as ProfileRecord, g as DraftRecord, K as KeyRecord, c as BugRecord, B as BugDetail, O as OpsItemDetail, h as FeatureRequestRecord, F as FeatureRequestDetail, a as AttachmentUploadResult } from './types-D0sroUDD.mjs';
|
|
2
|
+
import { C as CreateBugRequestInput, a as CreateFeatureRequestRequestInput } from './zod.gen-DTKpgw-T.mjs';
|
|
3
|
+
import 'zod';
|
|
4
|
+
|
|
5
|
+
/** Thrown when an admin request returns 401. Distinct from generic `Error`
|
|
6
|
+
* so callers can branch on it without string-matching. */
|
|
7
|
+
declare class AuthError extends Error {
|
|
8
|
+
readonly status = 401;
|
|
9
|
+
constructor(message: string);
|
|
10
|
+
}
|
|
11
|
+
interface DevtoolsClientOptions {
|
|
12
|
+
/** Raw admin SDK key from device auth (`sdk_admin_*`). */
|
|
13
|
+
token: string;
|
|
14
|
+
projectId: string;
|
|
15
|
+
/** Admin API origin. Defaults to production (`https://shipeasy.ai`). */
|
|
16
|
+
adminBaseUrl?: string;
|
|
17
|
+
/** Called once per 401 so the host can clear its stored session and
|
|
18
|
+
* re-prompt login. The request still rejects with `AuthError`. */
|
|
19
|
+
onUnauthed?: () => void;
|
|
20
|
+
/** Injectable fetch for tests / exotic runtimes. Defaults to global fetch.
|
|
21
|
+
* Note the generated client invokes it with a single `Request` argument. */
|
|
22
|
+
fetch?: typeof fetch;
|
|
23
|
+
}
|
|
24
|
+
declare class DevtoolsClient {
|
|
25
|
+
private cache;
|
|
26
|
+
private readonly adminBaseUrl;
|
|
27
|
+
/** Raw admin bearer key for this session. Public so overlays can compare it
|
|
28
|
+
* against their stored session and rebuild the client on session swap. */
|
|
29
|
+
readonly token: string;
|
|
30
|
+
readonly projectId: string;
|
|
31
|
+
private readonly onUnauthed?;
|
|
32
|
+
private readonly fetchImpl;
|
|
33
|
+
/** Session-configured generated client — every spec-covered call goes
|
|
34
|
+
* through it (bearer auth, base URL, injectable fetch). */
|
|
35
|
+
private readonly api;
|
|
36
|
+
constructor(opts: DevtoolsClientOptions);
|
|
37
|
+
/** Admin origin this client talks to. Overlays build dashboard deep links
|
|
38
|
+
* ("Open in dashboard ↗") off it. */
|
|
39
|
+
get adminUrl(): string;
|
|
40
|
+
/** Run `fn` once per `key` and cache the resulting promise. Rejections are
|
|
41
|
+
* evicted so the next call retries instead of replaying the failure. */
|
|
42
|
+
private memo;
|
|
43
|
+
/** Drop all cached responses (pull-to-refresh). */
|
|
44
|
+
invalidate(): void;
|
|
45
|
+
/** Build + throw the error for a failed request. 401 produces an `AuthError`
|
|
46
|
+
* and fires `onUnauthed` so the host can drop the stale session instead of
|
|
47
|
+
* leaving the user staring at a failed-load message. */
|
|
48
|
+
private fail;
|
|
49
|
+
/** Resolve a generated op result to its body, or throw the normalized
|
|
50
|
+
* error. Write ops with empty bodies pass on any 2xx. */
|
|
51
|
+
private unwrap;
|
|
52
|
+
/** Some list endpoints return a bare array, others a `{ data }` envelope
|
|
53
|
+
* (the spec is the contract, but be tolerant like the overlays always
|
|
54
|
+
* were). */
|
|
55
|
+
private asList;
|
|
56
|
+
/**
|
|
57
|
+
* Drain a paginated `{ data, next_cursor }` list op by walking cursors.
|
|
58
|
+
* Devtools shows the entire list at once; without this, large projects would
|
|
59
|
+
* silently truncate at the first page (limit=100 default).
|
|
60
|
+
*/
|
|
61
|
+
private drain;
|
|
62
|
+
private rawInit;
|
|
63
|
+
private rawGet;
|
|
64
|
+
private rawSend;
|
|
65
|
+
/** Read the response error body for diagnostics. Best-effort; never throws. */
|
|
66
|
+
private readErrorDetail;
|
|
67
|
+
project(): Promise<ProjectRecord>;
|
|
68
|
+
gates(): Promise<GateRecord[]>;
|
|
69
|
+
configs(): Promise<ConfigRecord[]>;
|
|
70
|
+
/** Experiments list. The endpoint returns the non-archived set (running /
|
|
71
|
+
* draft / stopped) by default; pass `{ archived: true }` for the archive tab
|
|
72
|
+
* (a separate memo bucket so the overlay's Archived section loads on demand). */
|
|
73
|
+
experiments(opts?: {
|
|
74
|
+
archived?: boolean;
|
|
75
|
+
}): Promise<ExperimentRecord[]>;
|
|
76
|
+
universes(): Promise<UniverseRecord[]>;
|
|
77
|
+
profiles(): Promise<ProfileRecord[]>;
|
|
78
|
+
drafts(): Promise<DraftRecord[]>;
|
|
79
|
+
keys(profileId?: string): Promise<KeyRecord[]>;
|
|
80
|
+
createDraft(input: {
|
|
81
|
+
profileId: string;
|
|
82
|
+
name: string;
|
|
83
|
+
}): Promise<DraftRecord>;
|
|
84
|
+
upsertDraftKey(draftId: string, key: string, value: string): Promise<void>;
|
|
85
|
+
updateKeyById(id: string, value: string): Promise<void>;
|
|
86
|
+
upsertKeys(profileId: string, keys: Array<{
|
|
87
|
+
key: string;
|
|
88
|
+
value: string;
|
|
89
|
+
}>, chunk?: string): Promise<void>;
|
|
90
|
+
/** Drop every cached `keys(profileId?)` response. We don't track which
|
|
91
|
+
* profile a single key write affects, so be safe and clear them all. */
|
|
92
|
+
private invalidateKeysCache;
|
|
93
|
+
bugs(): Promise<BugRecord[]>;
|
|
94
|
+
bug(id: string): Promise<BugDetail>;
|
|
95
|
+
/** Auto-filed error tickets. Same list endpoint, `type=error`. */
|
|
96
|
+
errors(): Promise<BugRecord[]>;
|
|
97
|
+
/** Auto-filed alert tickets. Same list endpoint, `type=alert`. */
|
|
98
|
+
alerts(): Promise<BugRecord[]>;
|
|
99
|
+
/** Generic ops-item detail — works for any type (bug/feature/error/alert). */
|
|
100
|
+
opsItem(id: string): Promise<OpsItemDetail>;
|
|
101
|
+
/** Triage-only update (status / priority) that works for ANY item type —
|
|
102
|
+
* the only editable shape for auto-filed error/alert tickets. Scrubs every
|
|
103
|
+
* list bucket + the item's detail so the panels refetch. */
|
|
104
|
+
updateOps(id: string, patch: {
|
|
105
|
+
status?: BugRecord["status"];
|
|
106
|
+
priority?: BugRecord["priority"];
|
|
107
|
+
}): Promise<void>;
|
|
108
|
+
createBug(input: Omit<CreateBugRequestInput, "type">): Promise<{
|
|
109
|
+
id: string;
|
|
110
|
+
}>;
|
|
111
|
+
updateBug(id: string, patch: {
|
|
112
|
+
title?: string;
|
|
113
|
+
stepsToReproduce?: string;
|
|
114
|
+
actualResult?: string;
|
|
115
|
+
expectedResult?: string;
|
|
116
|
+
status?: BugRecord["status"];
|
|
117
|
+
priority?: BugRecord["priority"];
|
|
118
|
+
}): Promise<void>;
|
|
119
|
+
featureRequests(): Promise<FeatureRequestRecord[]>;
|
|
120
|
+
featureRequest(id: string): Promise<FeatureRequestDetail>;
|
|
121
|
+
createFeatureRequest(input: Omit<CreateFeatureRequestRequestInput, "type">): Promise<{
|
|
122
|
+
id: string;
|
|
123
|
+
}>;
|
|
124
|
+
updateFeatureRequest(id: string, patch: {
|
|
125
|
+
title?: string;
|
|
126
|
+
description?: string;
|
|
127
|
+
useCase?: string;
|
|
128
|
+
status?: FeatureRequestRecord["status"];
|
|
129
|
+
priority?: FeatureRequestRecord["priority"];
|
|
130
|
+
}): Promise<void>;
|
|
131
|
+
/** Fetch an attachment file as a Blob via the authenticated stream route
|
|
132
|
+
* (an <Image>/<img> can't send the Authorization header itself). */
|
|
133
|
+
attachmentBlob(id: string): Promise<Blob>;
|
|
134
|
+
uploadAttachment(args: {
|
|
135
|
+
reportKind: "bug" | "feature_request";
|
|
136
|
+
reportId: string;
|
|
137
|
+
kind: "screenshot" | "recording" | "file";
|
|
138
|
+
filename: string;
|
|
139
|
+
blob: Blob;
|
|
140
|
+
}): Promise<AttachmentUploadResult>;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export { AuthError, DevtoolsClient, type DevtoolsClientOptions };
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { o as ProjectRecord, G as GateRecord, C as ConfigRecord, E as ExperimentRecord, U as UniverseRecord, m as ProfileRecord, g as DraftRecord, K as KeyRecord, c as BugRecord, B as BugDetail, O as OpsItemDetail, h as FeatureRequestRecord, F as FeatureRequestDetail, a as AttachmentUploadResult } from './types-D0sroUDD.js';
|
|
2
|
+
import { C as CreateBugRequestInput, a as CreateFeatureRequestRequestInput } from './zod.gen-DTKpgw-T.js';
|
|
3
|
+
import 'zod';
|
|
4
|
+
|
|
5
|
+
/** Thrown when an admin request returns 401. Distinct from generic `Error`
|
|
6
|
+
* so callers can branch on it without string-matching. */
|
|
7
|
+
declare class AuthError extends Error {
|
|
8
|
+
readonly status = 401;
|
|
9
|
+
constructor(message: string);
|
|
10
|
+
}
|
|
11
|
+
interface DevtoolsClientOptions {
|
|
12
|
+
/** Raw admin SDK key from device auth (`sdk_admin_*`). */
|
|
13
|
+
token: string;
|
|
14
|
+
projectId: string;
|
|
15
|
+
/** Admin API origin. Defaults to production (`https://shipeasy.ai`). */
|
|
16
|
+
adminBaseUrl?: string;
|
|
17
|
+
/** Called once per 401 so the host can clear its stored session and
|
|
18
|
+
* re-prompt login. The request still rejects with `AuthError`. */
|
|
19
|
+
onUnauthed?: () => void;
|
|
20
|
+
/** Injectable fetch for tests / exotic runtimes. Defaults to global fetch.
|
|
21
|
+
* Note the generated client invokes it with a single `Request` argument. */
|
|
22
|
+
fetch?: typeof fetch;
|
|
23
|
+
}
|
|
24
|
+
declare class DevtoolsClient {
|
|
25
|
+
private cache;
|
|
26
|
+
private readonly adminBaseUrl;
|
|
27
|
+
/** Raw admin bearer key for this session. Public so overlays can compare it
|
|
28
|
+
* against their stored session and rebuild the client on session swap. */
|
|
29
|
+
readonly token: string;
|
|
30
|
+
readonly projectId: string;
|
|
31
|
+
private readonly onUnauthed?;
|
|
32
|
+
private readonly fetchImpl;
|
|
33
|
+
/** Session-configured generated client — every spec-covered call goes
|
|
34
|
+
* through it (bearer auth, base URL, injectable fetch). */
|
|
35
|
+
private readonly api;
|
|
36
|
+
constructor(opts: DevtoolsClientOptions);
|
|
37
|
+
/** Admin origin this client talks to. Overlays build dashboard deep links
|
|
38
|
+
* ("Open in dashboard ↗") off it. */
|
|
39
|
+
get adminUrl(): string;
|
|
40
|
+
/** Run `fn` once per `key` and cache the resulting promise. Rejections are
|
|
41
|
+
* evicted so the next call retries instead of replaying the failure. */
|
|
42
|
+
private memo;
|
|
43
|
+
/** Drop all cached responses (pull-to-refresh). */
|
|
44
|
+
invalidate(): void;
|
|
45
|
+
/** Build + throw the error for a failed request. 401 produces an `AuthError`
|
|
46
|
+
* and fires `onUnauthed` so the host can drop the stale session instead of
|
|
47
|
+
* leaving the user staring at a failed-load message. */
|
|
48
|
+
private fail;
|
|
49
|
+
/** Resolve a generated op result to its body, or throw the normalized
|
|
50
|
+
* error. Write ops with empty bodies pass on any 2xx. */
|
|
51
|
+
private unwrap;
|
|
52
|
+
/** Some list endpoints return a bare array, others a `{ data }` envelope
|
|
53
|
+
* (the spec is the contract, but be tolerant like the overlays always
|
|
54
|
+
* were). */
|
|
55
|
+
private asList;
|
|
56
|
+
/**
|
|
57
|
+
* Drain a paginated `{ data, next_cursor }` list op by walking cursors.
|
|
58
|
+
* Devtools shows the entire list at once; without this, large projects would
|
|
59
|
+
* silently truncate at the first page (limit=100 default).
|
|
60
|
+
*/
|
|
61
|
+
private drain;
|
|
62
|
+
private rawInit;
|
|
63
|
+
private rawGet;
|
|
64
|
+
private rawSend;
|
|
65
|
+
/** Read the response error body for diagnostics. Best-effort; never throws. */
|
|
66
|
+
private readErrorDetail;
|
|
67
|
+
project(): Promise<ProjectRecord>;
|
|
68
|
+
gates(): Promise<GateRecord[]>;
|
|
69
|
+
configs(): Promise<ConfigRecord[]>;
|
|
70
|
+
/** Experiments list. The endpoint returns the non-archived set (running /
|
|
71
|
+
* draft / stopped) by default; pass `{ archived: true }` for the archive tab
|
|
72
|
+
* (a separate memo bucket so the overlay's Archived section loads on demand). */
|
|
73
|
+
experiments(opts?: {
|
|
74
|
+
archived?: boolean;
|
|
75
|
+
}): Promise<ExperimentRecord[]>;
|
|
76
|
+
universes(): Promise<UniverseRecord[]>;
|
|
77
|
+
profiles(): Promise<ProfileRecord[]>;
|
|
78
|
+
drafts(): Promise<DraftRecord[]>;
|
|
79
|
+
keys(profileId?: string): Promise<KeyRecord[]>;
|
|
80
|
+
createDraft(input: {
|
|
81
|
+
profileId: string;
|
|
82
|
+
name: string;
|
|
83
|
+
}): Promise<DraftRecord>;
|
|
84
|
+
upsertDraftKey(draftId: string, key: string, value: string): Promise<void>;
|
|
85
|
+
updateKeyById(id: string, value: string): Promise<void>;
|
|
86
|
+
upsertKeys(profileId: string, keys: Array<{
|
|
87
|
+
key: string;
|
|
88
|
+
value: string;
|
|
89
|
+
}>, chunk?: string): Promise<void>;
|
|
90
|
+
/** Drop every cached `keys(profileId?)` response. We don't track which
|
|
91
|
+
* profile a single key write affects, so be safe and clear them all. */
|
|
92
|
+
private invalidateKeysCache;
|
|
93
|
+
bugs(): Promise<BugRecord[]>;
|
|
94
|
+
bug(id: string): Promise<BugDetail>;
|
|
95
|
+
/** Auto-filed error tickets. Same list endpoint, `type=error`. */
|
|
96
|
+
errors(): Promise<BugRecord[]>;
|
|
97
|
+
/** Auto-filed alert tickets. Same list endpoint, `type=alert`. */
|
|
98
|
+
alerts(): Promise<BugRecord[]>;
|
|
99
|
+
/** Generic ops-item detail — works for any type (bug/feature/error/alert). */
|
|
100
|
+
opsItem(id: string): Promise<OpsItemDetail>;
|
|
101
|
+
/** Triage-only update (status / priority) that works for ANY item type —
|
|
102
|
+
* the only editable shape for auto-filed error/alert tickets. Scrubs every
|
|
103
|
+
* list bucket + the item's detail so the panels refetch. */
|
|
104
|
+
updateOps(id: string, patch: {
|
|
105
|
+
status?: BugRecord["status"];
|
|
106
|
+
priority?: BugRecord["priority"];
|
|
107
|
+
}): Promise<void>;
|
|
108
|
+
createBug(input: Omit<CreateBugRequestInput, "type">): Promise<{
|
|
109
|
+
id: string;
|
|
110
|
+
}>;
|
|
111
|
+
updateBug(id: string, patch: {
|
|
112
|
+
title?: string;
|
|
113
|
+
stepsToReproduce?: string;
|
|
114
|
+
actualResult?: string;
|
|
115
|
+
expectedResult?: string;
|
|
116
|
+
status?: BugRecord["status"];
|
|
117
|
+
priority?: BugRecord["priority"];
|
|
118
|
+
}): Promise<void>;
|
|
119
|
+
featureRequests(): Promise<FeatureRequestRecord[]>;
|
|
120
|
+
featureRequest(id: string): Promise<FeatureRequestDetail>;
|
|
121
|
+
createFeatureRequest(input: Omit<CreateFeatureRequestRequestInput, "type">): Promise<{
|
|
122
|
+
id: string;
|
|
123
|
+
}>;
|
|
124
|
+
updateFeatureRequest(id: string, patch: {
|
|
125
|
+
title?: string;
|
|
126
|
+
description?: string;
|
|
127
|
+
useCase?: string;
|
|
128
|
+
status?: FeatureRequestRecord["status"];
|
|
129
|
+
priority?: FeatureRequestRecord["priority"];
|
|
130
|
+
}): Promise<void>;
|
|
131
|
+
/** Fetch an attachment file as a Blob via the authenticated stream route
|
|
132
|
+
* (an <Image>/<img> can't send the Authorization header itself). */
|
|
133
|
+
attachmentBlob(id: string): Promise<Blob>;
|
|
134
|
+
uploadAttachment(args: {
|
|
135
|
+
reportKind: "bug" | "feature_request";
|
|
136
|
+
reportId: string;
|
|
137
|
+
kind: "screenshot" | "recording" | "file";
|
|
138
|
+
filename: string;
|
|
139
|
+
blob: Blob;
|
|
140
|
+
}): Promise<AttachmentUploadResult>;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export { AuthError, DevtoolsClient, type DevtoolsClientOptions };
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
var _chunkYQHCUZGPjs = require('./chunk-YQHCUZGP.js');
|
|
5
|
+
require('./chunk-KPO3R7ND.js');
|
|
6
|
+
require('./chunk-I2KLQ2HA.js');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
exports.AuthError = _chunkYQHCUZGPjs.AuthError; exports.DevtoolsClient = _chunkYQHCUZGPjs.DevtoolsClient;
|
package/dist/api.mjs
ADDED
package/dist/auth.d.mts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { f as DevtoolsSession } from './types-D0sroUDD.mjs';
|
|
2
|
+
|
|
3
|
+
/** Thrown when the user dismisses the auth browser without completing login. */
|
|
4
|
+
declare class LoginCancelled extends Error {
|
|
5
|
+
constructor();
|
|
6
|
+
}
|
|
7
|
+
/** Result contract of an auth-session opener (mirrors expo-web-browser's
|
|
8
|
+
* `openAuthSessionAsync`): resolves when the browser deep-links back to
|
|
9
|
+
* `redirectUri` (type "success", `url` = the full deep link) or the user
|
|
10
|
+
* dismisses it. */
|
|
11
|
+
interface AuthSessionResult {
|
|
12
|
+
type: "success" | "cancel" | "dismiss" | string;
|
|
13
|
+
url?: string;
|
|
14
|
+
}
|
|
15
|
+
interface DeviceAuthAdapters {
|
|
16
|
+
/** Open `url` in an auth browser session and resolve when it redirects to
|
|
17
|
+
* `redirectUri` (or the user cancels). RN: `WebBrowser.openAuthSessionAsync`. */
|
|
18
|
+
openAuthSession(url: string, redirectUri: string): Promise<AuthSessionResult>;
|
|
19
|
+
/** base64url(SHA-256(input)) — the PKCE S256 transform. Defaults to
|
|
20
|
+
* `crypto.subtle` where available; RN injects expo-crypto. */
|
|
21
|
+
sha256Base64Url?(input: string): Promise<string>;
|
|
22
|
+
/** UUID source for the verifier. Defaults to `crypto.randomUUID`. */
|
|
23
|
+
randomUUID?(): string;
|
|
24
|
+
/** Injectable fetch for tests. Defaults to global fetch. */
|
|
25
|
+
fetch?: typeof fetch;
|
|
26
|
+
}
|
|
27
|
+
interface DeviceAuthOptions {
|
|
28
|
+
/** The HOST APP's deep-link redirect URI (its own scheme), e.g.
|
|
29
|
+
* `acme://se-auth`. Must match a scheme the app registered with the OS. */
|
|
30
|
+
redirectUri: string;
|
|
31
|
+
/** Edge worker origin (`/auth/device/*`). Defaults to production. */
|
|
32
|
+
edgeBaseUrl?: string;
|
|
33
|
+
/** Admin web origin (the `/cli-auth` page). Defaults to production. */
|
|
34
|
+
adminBaseUrl?: string;
|
|
35
|
+
/** Pre-select a project on the /cli-auth page (skips the picker when the
|
|
36
|
+
* user has access). */
|
|
37
|
+
projectId?: string;
|
|
38
|
+
/** The app's public client key (`sdk_client_*`). The /cli-auth page resolves
|
|
39
|
+
* it to the project it belongs to and locks the flow there — the key IS the
|
|
40
|
+
* project identity, so the user never sees a project picker (same contract
|
|
41
|
+
* as the browser overlay's `sdkKey` param). An explicit `projectId` wins. */
|
|
42
|
+
clientKey?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Parse a deep link's query params without relying on the host's URL
|
|
45
|
+
* implementation (React Native's URL polyfill is historically incomplete). */
|
|
46
|
+
declare function parseDeepLinkQuery(url: string): Record<string, string>;
|
|
47
|
+
/**
|
|
48
|
+
* Run the full device-auth flow and return the session. Throws
|
|
49
|
+
* {@link LoginCancelled} if the user dismisses the browser, or `Error` on a
|
|
50
|
+
* protocol failure (state mismatch, exchange rejection).
|
|
51
|
+
*
|
|
52
|
+
* The caller persists the returned session (RN: SecureStore/Keychain) — this
|
|
53
|
+
* core deliberately does no storage of its own.
|
|
54
|
+
*/
|
|
55
|
+
declare function startDeviceAuth(opts: DeviceAuthOptions, adapters: DeviceAuthAdapters): Promise<DevtoolsSession>;
|
|
56
|
+
|
|
57
|
+
export { type AuthSessionResult, type DeviceAuthAdapters, type DeviceAuthOptions, LoginCancelled, parseDeepLinkQuery, startDeviceAuth };
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { f as DevtoolsSession } from './types-D0sroUDD.js';
|
|
2
|
+
|
|
3
|
+
/** Thrown when the user dismisses the auth browser without completing login. */
|
|
4
|
+
declare class LoginCancelled extends Error {
|
|
5
|
+
constructor();
|
|
6
|
+
}
|
|
7
|
+
/** Result contract of an auth-session opener (mirrors expo-web-browser's
|
|
8
|
+
* `openAuthSessionAsync`): resolves when the browser deep-links back to
|
|
9
|
+
* `redirectUri` (type "success", `url` = the full deep link) or the user
|
|
10
|
+
* dismisses it. */
|
|
11
|
+
interface AuthSessionResult {
|
|
12
|
+
type: "success" | "cancel" | "dismiss" | string;
|
|
13
|
+
url?: string;
|
|
14
|
+
}
|
|
15
|
+
interface DeviceAuthAdapters {
|
|
16
|
+
/** Open `url` in an auth browser session and resolve when it redirects to
|
|
17
|
+
* `redirectUri` (or the user cancels). RN: `WebBrowser.openAuthSessionAsync`. */
|
|
18
|
+
openAuthSession(url: string, redirectUri: string): Promise<AuthSessionResult>;
|
|
19
|
+
/** base64url(SHA-256(input)) — the PKCE S256 transform. Defaults to
|
|
20
|
+
* `crypto.subtle` where available; RN injects expo-crypto. */
|
|
21
|
+
sha256Base64Url?(input: string): Promise<string>;
|
|
22
|
+
/** UUID source for the verifier. Defaults to `crypto.randomUUID`. */
|
|
23
|
+
randomUUID?(): string;
|
|
24
|
+
/** Injectable fetch for tests. Defaults to global fetch. */
|
|
25
|
+
fetch?: typeof fetch;
|
|
26
|
+
}
|
|
27
|
+
interface DeviceAuthOptions {
|
|
28
|
+
/** The HOST APP's deep-link redirect URI (its own scheme), e.g.
|
|
29
|
+
* `acme://se-auth`. Must match a scheme the app registered with the OS. */
|
|
30
|
+
redirectUri: string;
|
|
31
|
+
/** Edge worker origin (`/auth/device/*`). Defaults to production. */
|
|
32
|
+
edgeBaseUrl?: string;
|
|
33
|
+
/** Admin web origin (the `/cli-auth` page). Defaults to production. */
|
|
34
|
+
adminBaseUrl?: string;
|
|
35
|
+
/** Pre-select a project on the /cli-auth page (skips the picker when the
|
|
36
|
+
* user has access). */
|
|
37
|
+
projectId?: string;
|
|
38
|
+
/** The app's public client key (`sdk_client_*`). The /cli-auth page resolves
|
|
39
|
+
* it to the project it belongs to and locks the flow there — the key IS the
|
|
40
|
+
* project identity, so the user never sees a project picker (same contract
|
|
41
|
+
* as the browser overlay's `sdkKey` param). An explicit `projectId` wins. */
|
|
42
|
+
clientKey?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Parse a deep link's query params without relying on the host's URL
|
|
45
|
+
* implementation (React Native's URL polyfill is historically incomplete). */
|
|
46
|
+
declare function parseDeepLinkQuery(url: string): Record<string, string>;
|
|
47
|
+
/**
|
|
48
|
+
* Run the full device-auth flow and return the session. Throws
|
|
49
|
+
* {@link LoginCancelled} if the user dismisses the browser, or `Error` on a
|
|
50
|
+
* protocol failure (state mismatch, exchange rejection).
|
|
51
|
+
*
|
|
52
|
+
* The caller persists the returned session (RN: SecureStore/Keychain) — this
|
|
53
|
+
* core deliberately does no storage of its own.
|
|
54
|
+
*/
|
|
55
|
+
declare function startDeviceAuth(opts: DeviceAuthOptions, adapters: DeviceAuthAdapters): Promise<DevtoolsSession>;
|
|
56
|
+
|
|
57
|
+
export { type AuthSessionResult, type DeviceAuthAdapters, type DeviceAuthOptions, LoginCancelled, parseDeepLinkQuery, startDeviceAuth };
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
var _chunkLCRTLHGLjs = require('./chunk-LCRTLHGL.js');
|
|
6
|
+
require('./chunk-KPO3R7ND.js');
|
|
7
|
+
require('./chunk-I2KLQ2HA.js');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
exports.LoginCancelled = _chunkLCRTLHGLjs.LoginCancelled; exports.parseDeepLinkQuery = _chunkLCRTLHGLjs.parseDeepLinkQuery; exports.startDeviceAuth = _chunkLCRTLHGLjs.startDeviceAuth;
|
package/dist/auth.mjs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/feedback-state.ts
|
|
2
|
+
function prMerged(pr) {
|
|
3
|
+
return pr.merged === true || pr.state === "merged" || typeof pr.mergedAt === "string";
|
|
4
|
+
}
|
|
5
|
+
function agentAwaitingReply(cd) {
|
|
6
|
+
const a = _optionalChain([cd, 'optionalAccess', _ => _.agent]);
|
|
7
|
+
return _optionalChain([a, 'optionalAccess', _2 => _2.awaitingReply]) === true || _optionalChain([a, 'optionalAccess', _3 => _3.state]) === "awaiting_reply";
|
|
8
|
+
}
|
|
9
|
+
function feedbackAgentInfo(item) {
|
|
10
|
+
const pr = _nullishCoalesce(_optionalChain([item, 'access', _4 => _4.connectorData, 'optionalAccess', _5 => _5.github, 'optionalAccess', _6 => _6.pr]), () => ( null));
|
|
11
|
+
if (pr && prMerged(pr)) return { state: "pr_landed", pr: { number: pr.number, url: pr.url } };
|
|
12
|
+
if (agentAwaitingReply(item.connectorData)) return { state: "question" };
|
|
13
|
+
if (pr) return { state: "pr_ready", pr: { number: pr.number, url: pr.url } };
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
var FEEDBACK_AGENT_LABEL = {
|
|
17
|
+
pr_landed: "merged",
|
|
18
|
+
question: "needs reply",
|
|
19
|
+
pr_ready: "PR ready"
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
exports.feedbackAgentInfo = feedbackAgentInfo; exports.FEEDBACK_AGENT_LABEL = FEEDBACK_AGENT_LABEL;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_ADMIN_BASE_URL,
|
|
3
|
+
DEFAULT_EDGE_BASE_URL,
|
|
4
|
+
types_exports
|
|
5
|
+
} from "./chunk-RH6X24HG.mjs";
|
|
6
|
+
|
|
7
|
+
// src/auth.ts
|
|
8
|
+
var LoginCancelled = class extends Error {
|
|
9
|
+
constructor() {
|
|
10
|
+
super("Login cancelled");
|
|
11
|
+
this.name = "LoginCancelled";
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
function defaultRandomUUID() {
|
|
15
|
+
const c = globalThis.crypto;
|
|
16
|
+
if (c?.randomUUID) return c.randomUUID();
|
|
17
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (ch) => {
|
|
18
|
+
const r = Math.random() * 16 | 0;
|
|
19
|
+
return (ch === "x" ? r : r & 3 | 8).toString(16);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async function defaultSha256Base64Url(input) {
|
|
23
|
+
const subtle = globalThis.crypto?.subtle;
|
|
24
|
+
if (!subtle) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
"No crypto.subtle available \u2014 pass adapters.sha256Base64Url (e.g. expo-crypto's digestStringAsync)."
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
const hash = await subtle.digest("SHA-256", new TextEncoder().encode(input));
|
|
30
|
+
const bytes = new Uint8Array(hash);
|
|
31
|
+
let binary = "";
|
|
32
|
+
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
|
|
33
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
34
|
+
}
|
|
35
|
+
function makeVerifier(randomUUID) {
|
|
36
|
+
return (randomUUID() + randomUUID()).replace(/-/g, "");
|
|
37
|
+
}
|
|
38
|
+
function parseDeepLinkQuery(url) {
|
|
39
|
+
const q = url.indexOf("?");
|
|
40
|
+
if (q < 0) return {};
|
|
41
|
+
const out = {};
|
|
42
|
+
const query = url.slice(q + 1).split("#")[0];
|
|
43
|
+
for (const pair of query.split("&")) {
|
|
44
|
+
if (!pair) continue;
|
|
45
|
+
const eq = pair.indexOf("=");
|
|
46
|
+
const k = eq < 0 ? pair : pair.slice(0, eq);
|
|
47
|
+
const v = eq < 0 ? "" : pair.slice(eq + 1);
|
|
48
|
+
try {
|
|
49
|
+
out[decodeURIComponent(k)] = decodeURIComponent(v.replace(/\+/g, " "));
|
|
50
|
+
} catch {
|
|
51
|
+
out[k] = v;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
async function startDeviceAuth(opts, adapters) {
|
|
57
|
+
const edge = (opts.edgeBaseUrl ?? DEFAULT_EDGE_BASE_URL).replace(/\/$/, "");
|
|
58
|
+
const admin = (opts.adminBaseUrl ?? DEFAULT_ADMIN_BASE_URL).replace(/\/$/, "");
|
|
59
|
+
const fetchImpl = adapters.fetch ?? ((...args) => fetch(...args));
|
|
60
|
+
const randomUUID = adapters.randomUUID ?? defaultRandomUUID;
|
|
61
|
+
const digest = adapters.sha256Base64Url ?? defaultSha256Base64Url;
|
|
62
|
+
const verifier = makeVerifier(randomUUID);
|
|
63
|
+
const challenge = await digest(verifier);
|
|
64
|
+
const startRes = await fetchImpl(`${edge}/auth/device/start`, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: { "Content-Type": "application/json" },
|
|
67
|
+
body: JSON.stringify({ code_challenge: challenge })
|
|
68
|
+
});
|
|
69
|
+
if (!startRes.ok) {
|
|
70
|
+
throw new Error(`Login could not start (HTTP ${startRes.status}). Please try again.`);
|
|
71
|
+
}
|
|
72
|
+
const { state } = await startRes.json();
|
|
73
|
+
if (!state) throw new Error("Login could not start (no state). Please try again.");
|
|
74
|
+
const authUrl = `${admin}/cli-auth?state=${encodeURIComponent(state)}&code_challenge=${encodeURIComponent(challenge)}&redirect_uri=${encodeURIComponent(opts.redirectUri)}` + (opts.projectId ? `&project_id=${encodeURIComponent(opts.projectId)}` : "") + (!opts.projectId && opts.clientKey ? `&client_key=${encodeURIComponent(opts.clientKey)}` : "");
|
|
75
|
+
const result = await adapters.openAuthSession(authUrl, opts.redirectUri);
|
|
76
|
+
if (result.type !== "success" || !result.url) throw new LoginCancelled();
|
|
77
|
+
const params = parseDeepLinkQuery(result.url);
|
|
78
|
+
if (!params.state || params.state !== state) throw new Error("Auth state mismatch");
|
|
79
|
+
const tokenRes = await fetchImpl(`${edge}/auth/device/token`, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: { "Content-Type": "application/json" },
|
|
82
|
+
body: JSON.stringify({ state, code_verifier: verifier })
|
|
83
|
+
});
|
|
84
|
+
if (!tokenRes.ok) {
|
|
85
|
+
throw new Error(`Login did not complete (HTTP ${tokenRes.status}). Please try again.`);
|
|
86
|
+
}
|
|
87
|
+
const data = await tokenRes.json();
|
|
88
|
+
if (!data.token || !data.project_id) throw new Error("Login did not return a token");
|
|
89
|
+
return {
|
|
90
|
+
token: data.token,
|
|
91
|
+
projectId: data.project_id,
|
|
92
|
+
userEmail: data.user_email ?? null,
|
|
93
|
+
projectName: data.project_name ?? null
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export {
|
|
98
|
+
LoginCancelled,
|
|
99
|
+
parseDeepLinkQuery,
|
|
100
|
+
startDeviceAuth
|
|
101
|
+
};
|