@brass-build/client 0.2.0 → 0.4.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.
Files changed (168) hide show
  1. package/AGENTS.md +26 -5
  2. package/CHANGELOG.md +61 -2
  3. package/README.md +247 -87
  4. package/dist/acting-organization.d.ts +131 -0
  5. package/dist/acting-organization.d.ts.map +1 -0
  6. package/dist/acting-organization.js +278 -0
  7. package/dist/acting-organization.js.map +1 -0
  8. package/dist/api-headers.js +7 -7
  9. package/dist/api-headers.js.map +1 -1
  10. package/dist/call-service.d.ts +74 -0
  11. package/dist/call-service.d.ts.map +1 -0
  12. package/dist/call-service.js +253 -0
  13. package/dist/call-service.js.map +1 -0
  14. package/dist/client.d.ts +29 -0
  15. package/dist/client.d.ts.map +1 -1
  16. package/dist/client.js +583 -12
  17. package/dist/client.js.map +1 -1
  18. package/dist/document-checkpoints.d.ts +1 -0
  19. package/dist/document-checkpoints.d.ts.map +1 -1
  20. package/dist/document-checkpoints.js +27 -0
  21. package/dist/document-checkpoints.js.map +1 -1
  22. package/dist/document-contents.d.ts +15 -0
  23. package/dist/document-contents.d.ts.map +1 -0
  24. package/dist/document-contents.js +88 -0
  25. package/dist/document-contents.js.map +1 -0
  26. package/dist/document-signals.d.ts +31 -1
  27. package/dist/document-signals.d.ts.map +1 -1
  28. package/dist/document-signals.js +59 -0
  29. package/dist/document-signals.js.map +1 -1
  30. package/dist/document-streams.d.ts +4 -2
  31. package/dist/document-streams.d.ts.map +1 -1
  32. package/dist/document-streams.js +6 -0
  33. package/dist/document-streams.js.map +1 -1
  34. package/dist/document-thumbnail-capture.d.ts +20 -0
  35. package/dist/document-thumbnail-capture.d.ts.map +1 -1
  36. package/dist/document-thumbnail-capture.js +247 -9
  37. package/dist/document-thumbnail-capture.js.map +1 -1
  38. package/dist/documents-namespace.d.ts.map +1 -1
  39. package/dist/documents-namespace.js +3 -0
  40. package/dist/documents-namespace.js.map +1 -1
  41. package/dist/documents.d.ts +51 -3
  42. package/dist/documents.d.ts.map +1 -1
  43. package/dist/documents.js +415 -36
  44. package/dist/documents.js.map +1 -1
  45. package/dist/export-guest.d.ts.map +1 -1
  46. package/dist/export-guest.js +5 -0
  47. package/dist/export-guest.js.map +1 -1
  48. package/dist/handle-types.d.ts +23 -0
  49. package/dist/handle-types.d.ts.map +1 -1
  50. package/dist/handles.d.ts +1 -1
  51. package/dist/handles.d.ts.map +1 -1
  52. package/dist/handles.js +3 -3
  53. package/dist/handles.js.map +1 -1
  54. package/dist/hosted-ui.d.ts +10 -0
  55. package/dist/hosted-ui.d.ts.map +1 -1
  56. package/dist/hosted-ui.js +115 -30
  57. package/dist/hosted-ui.js.map +1 -1
  58. package/dist/importer.d.ts.map +1 -1
  59. package/dist/importer.js +28 -66
  60. package/dist/importer.js.map +1 -1
  61. package/dist/index.d.ts +8 -3
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +25 -1
  64. package/dist/index.js.map +1 -1
  65. package/dist/internal.d.ts +2 -0
  66. package/dist/internal.d.ts.map +1 -1
  67. package/dist/internal.js +2 -0
  68. package/dist/internal.js.map +1 -1
  69. package/dist/mutate-batching.js +2 -2
  70. package/dist/mutate-batching.js.map +1 -1
  71. package/dist/open-document.d.ts +16 -1
  72. package/dist/open-document.d.ts.map +1 -1
  73. package/dist/open-document.js +123 -35
  74. package/dist/open-document.js.map +1 -1
  75. package/dist/presence.d.ts +10 -0
  76. package/dist/presence.d.ts.map +1 -1
  77. package/dist/presence.js.map +1 -1
  78. package/dist/record-paging.d.ts +13 -14
  79. package/dist/record-paging.d.ts.map +1 -1
  80. package/dist/record-paging.js +13 -14
  81. package/dist/record-paging.js.map +1 -1
  82. package/dist/serializer.d.ts +3 -3
  83. package/dist/session-toolbar.d.ts +9 -0
  84. package/dist/session-toolbar.d.ts.map +1 -1
  85. package/dist/session-toolbar.js +191 -21
  86. package/dist/session-toolbar.js.map +1 -1
  87. package/dist/tail-ops.js +2 -2
  88. package/dist/tail-ops.js.map +1 -1
  89. package/dist/types.d.ts +0 -42
  90. package/dist/types.d.ts.map +1 -1
  91. package/dist/undo.d.ts +1 -1
  92. package/dist/undo.d.ts.map +1 -1
  93. package/dist/undo.js +1 -1
  94. package/dist/undo.js.map +1 -1
  95. package/dist/version-bar.d.ts.map +1 -1
  96. package/dist/version-bar.js +75 -4
  97. package/dist/version-bar.js.map +1 -1
  98. package/dist/version.d.ts +2 -2
  99. package/dist/version.js +1 -1
  100. package/dist/wasm-ready-gate.d.ts +13 -0
  101. package/dist/wasm-ready-gate.d.ts.map +1 -0
  102. package/dist/wasm-ready-gate.js +28 -0
  103. package/dist/wasm-ready-gate.js.map +1 -0
  104. package/dist/wasm-runtime.browser.d.ts +11 -0
  105. package/dist/wasm-runtime.browser.d.ts.map +1 -0
  106. package/dist/wasm-runtime.browser.js +44 -0
  107. package/dist/wasm-runtime.browser.js.map +1 -0
  108. package/dist/wasm-runtime.d.ts +6 -0
  109. package/dist/wasm-runtime.d.ts.map +1 -0
  110. package/dist/wasm-runtime.js +20 -0
  111. package/dist/wasm-runtime.js.map +1 -0
  112. package/dist/when-saved.d.ts +2 -0
  113. package/dist/when-saved.d.ts.map +1 -0
  114. package/dist/when-saved.js +70 -0
  115. package/dist/when-saved.js.map +1 -0
  116. package/dist/ws-client.d.ts +4 -0
  117. package/dist/ws-client.d.ts.map +1 -1
  118. package/dist/ws-client.js +70 -1
  119. package/dist/ws-client.js.map +1 -1
  120. package/dist/ws-frame-limit.d.ts.map +1 -1
  121. package/dist/ws-frame-limit.js +9 -10
  122. package/dist/ws-frame-limit.js.map +1 -1
  123. package/docs/app-manifest.md +335 -5
  124. package/docs/custom-chrome.md +7 -2
  125. package/docs/presence.md +12 -0
  126. package/docs/services.md +265 -0
  127. package/docs/text-fields.md +191 -0
  128. package/package.json +4 -5
  129. package/src/acting-organization.ts +396 -0
  130. package/src/api-headers.ts +7 -7
  131. package/src/call-service.ts +354 -0
  132. package/src/client.ts +655 -15
  133. package/src/document-checkpoints.ts +30 -0
  134. package/src/document-contents.ts +104 -0
  135. package/src/document-signals.ts +113 -1
  136. package/src/document-streams.ts +15 -1
  137. package/src/document-thumbnail-capture.ts +253 -9
  138. package/src/documents-namespace.ts +20 -10
  139. package/src/documents.ts +460 -33
  140. package/src/export-guest.ts +5 -0
  141. package/src/handle-types.ts +35 -0
  142. package/src/handles.ts +7 -6
  143. package/src/hosted-ui.ts +155 -29
  144. package/src/importer.ts +28 -62
  145. package/src/index.ts +43 -4
  146. package/src/internal.ts +10 -0
  147. package/src/mutate-batching.ts +2 -2
  148. package/src/open-document.ts +179 -39
  149. package/src/presence.ts +6 -0
  150. package/src/record-paging.ts +13 -14
  151. package/src/serializer.ts +3 -3
  152. package/src/session-toolbar.ts +219 -22
  153. package/src/tail-ops.ts +2 -2
  154. package/src/types.ts +6 -4
  155. package/src/undo.ts +1 -1
  156. package/src/version-bar.ts +77 -7
  157. package/src/version.ts +1 -1
  158. package/src/wasm-ready-gate.ts +30 -0
  159. package/src/wasm-runtime.browser.ts +48 -0
  160. package/src/wasm-runtime.ts +21 -0
  161. package/src/when-saved.ts +66 -0
  162. package/src/ws-client.ts +70 -1
  163. package/src/ws-frame-limit.ts +9 -10
  164. package/dist/vite.d.ts +0 -31
  165. package/dist/vite.d.ts.map +0 -1
  166. package/dist/vite.js +0 -161
  167. package/dist/vite.js.map +0 -1
  168. package/src/vite.ts +0 -214
@@ -0,0 +1,131 @@
1
+ export declare function storeActingOrganization(appId: string, organizationId: string): void;
2
+ export declare function readActingOrganization(appId: string): string | null;
3
+ export declare function clearActingOrganization(appId: string): void;
4
+ export type ResolveOutcome = {
5
+ kind: 'resolved';
6
+ organizationId: string;
7
+ organizationName: string;
8
+ /**
9
+ * Whether this app is installed on the organization. A deliberate
10
+ * switch reads it so the install is raised before the switch takes
11
+ * effect, rather than leaving the reader acting somewhere the next call
12
+ * is refused.
13
+ */
14
+ installed: boolean;
15
+ } | {
16
+ kind: 'needs-choice';
17
+ } | {
18
+ kind: 'no-organization';
19
+ };
20
+ export interface ResolveDeps {
21
+ /** The open document's organization, where one is open. */
22
+ activeDocumentOrganizationId: () => Promise<string | null>;
23
+ /** An organization already resolved on this page load. */
24
+ cachedResolution: () => string | null;
25
+ /**
26
+ * The organization the launch named, where this page load carried one. A
27
+ * person clicking Open from an organization's dashboard has just said where
28
+ * they are working, so it outranks a carried choice and is never read as
29
+ * stale: an app the organization has not installed is what the install
30
+ * consent behind the refusal is for.
31
+ */
32
+ launchPreference: () => string | null;
33
+ /** The choice this tab carries for this app, from an earlier act. */
34
+ storedPreference: () => string | null;
35
+ fetchResolved: (prefer: string | null) => Promise<ResolveOutcome>;
36
+ /** The hosted dialog that asks the person; null when they dismissed it. */
37
+ pickOrganization: () => Promise<string | null>;
38
+ /**
39
+ * Drop the stored choice, for one the app can no longer act in. The server
40
+ * validates a preference against MEMBERSHIP, so a choice naming an
41
+ * organization the app has since been uninstalled from stays resolvable
42
+ * forever while serving nothing.
43
+ */
44
+ forgetPreference: () => void;
45
+ remember: (organizationId: string,
46
+ /** Empty where the resolution carried an id and no name. */
47
+ organizationName: string,
48
+ /**
49
+ * Whether to carry the choice past this page load. An organization this
50
+ * app is not installed on has not become one the app can act in yet: the
51
+ * act that resolved it is about to be refused, and the reader may decline
52
+ * the install that follows. Recording it anyway pins every later visit to
53
+ * an organization nothing they press can work in, with the chooser out of
54
+ * reach. It is persisted when the install lands instead.
55
+ */
56
+ persist: boolean) => void;
57
+ }
58
+ /**
59
+ * Which organization the next act uses.
60
+ *
61
+ * `ask: false` runs the same ladder and stops where it would raise the hosted
62
+ * chooser, answering null instead. That is what an app reading its own state
63
+ * before it acts needs: the question the chooser asks is one only the person
64
+ * can answer, and asking it unbidden takes the page over for something the
65
+ * reader never pressed.
66
+ */
67
+ export declare function resolveActingOrganization(deps: ResolveDeps, options?: {
68
+ ask?: boolean;
69
+ }): Promise<string | null>;
70
+ /**
71
+ * What the chrome names: the organization the NEXT act would spend.
72
+ *
73
+ * An open document takes it over, because the document's organization is what
74
+ * every act made while it is open spends. That holds even when the name is
75
+ * unknown, which is a document in an organization this reader reaches through
76
+ * a directly shared project: the menu says nothing rather than naming the memo
77
+ * over it. Closing the document hands it back, so the two never disagree with
78
+ * what the app is about to do.
79
+ *
80
+ * Named whether or not the caller belongs to anywhere else. The menu offers a
81
+ * switch, and the surface behind it is also how a first or a second
82
+ * organization gets created, so what a caller with one has is a choice they
83
+ * have not made yet rather than no choice; naming the organization is what
84
+ * makes that switch legible instead of an offer to change something the menu
85
+ * never states.
86
+ *
87
+ * Null where neither is known, which is an ordinary state rather than a gap
88
+ * to fill: an app reached by its own URL, holding no document and nothing
89
+ * stored, has no organization until an act needs one, and resolving one to
90
+ * decorate a menu is exactly the boot-time question this design refuses.
91
+ */
92
+ export declare function visibleOrganization(openDocument: {
93
+ organization: Named | null;
94
+ } | null, memo: Named | null): {
95
+ id: string;
96
+ name: string;
97
+ } | null;
98
+ interface Named {
99
+ id: string;
100
+ name: string;
101
+ }
102
+ export interface ActingOrganizationFetchDeps {
103
+ apiBaseUrl: string;
104
+ getAccessToken: () => Promise<string | null>;
105
+ getAppAssertion: () => Promise<string | null>;
106
+ fetchImpl?: typeof fetch;
107
+ }
108
+ export declare function fetchActingOrganization(deps: ActingOrganizationFetchDeps, prefer: string | null): Promise<ResolveOutcome>;
109
+ /**
110
+ * A deliberate switch, as the account menu drives it. Separate from
111
+ * `resolveActingOrganization` because the two differ in what follows the
112
+ * choice: a resolve is followed by the act that asked for it, which raises
113
+ * its own refusal, while a switch is followed by nothing at all. So the
114
+ * switch has to establish for itself that the app can act where the reader
115
+ * sent it, and has to tell the app that everything it read is now about a
116
+ * different organization.
117
+ */
118
+ export interface SwitchDeps {
119
+ /** The organization being acted in now, so the surface can mark it. */
120
+ current: () => string | null;
121
+ pickOrganization: (current: string | undefined) => Promise<string | null>;
122
+ fetchResolved: (prefer: string) => Promise<ResolveOutcome>;
123
+ /** The hosted install dialog, pinned; null when the reader declined. */
124
+ offerInstall: (organizationId: string) => Promise<boolean>;
125
+ remember: (organizationId: string, organizationName: string) => void;
126
+ /** Re-reads the app under the new organization. */
127
+ reload: () => void;
128
+ }
129
+ export declare function switchActingOrganization(deps: SwitchDeps): Promise<boolean>;
130
+ export {};
131
+ //# sourceMappingURL=acting-organization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acting-organization.d.ts","sourceRoot":"","sources":["../src/acting-organization.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,278 @@
1
+ // Which organization an act that has no document spends.
2
+ //
3
+ // An app opened from a dashboard is launched with one, and an app holding an
4
+ // open document inherits that document's. Everything else is resolved by the
5
+ // server from the caller's own context at the first act that needs it, so the
6
+ // app is never handed a list and never asks at boot.
7
+ //
8
+ // The per-app sessionStorage slot below carries the answer forward within a
9
+ // tab: keys are namespaced per app id so integrations on one origin don't
10
+ // collide, and the slot is cleared on sign-out (session storage also drops on
11
+ // tab close).
12
+ import { applyAppAssertionHeader, applyClientVersionHeader } from './api-headers.js';
13
+ import { BrassError } from './errors.js';
14
+ const KEY_PREFIX = '__brass_acting_org_';
15
+ // Single per-page-load warn: a resolve runs on every act that needs an
16
+ // organization, so without the latch a server-rendered host would flood its
17
+ // logs.
18
+ let warnedUnavailable = false;
19
+ function getStorage() {
20
+ if (typeof sessionStorage === 'undefined')
21
+ return null;
22
+ try {
23
+ return sessionStorage;
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ function warnOnceUnavailable() {
30
+ if (warnedUnavailable)
31
+ return;
32
+ warnedUnavailable = true;
33
+ console.warn('Brass: sessionStorage is unavailable, so the organization this app acts in is resolved again on every page load.');
34
+ }
35
+ function orgKey(appId) {
36
+ return `${KEY_PREFIX}${appId}`;
37
+ }
38
+ export function storeActingOrganization(appId, organizationId) {
39
+ const storage = getStorage();
40
+ if (!storage) {
41
+ warnOnceUnavailable();
42
+ return;
43
+ }
44
+ try {
45
+ storage.setItem(orgKey(appId), organizationId);
46
+ }
47
+ catch {
48
+ // Quota / disabled storage; drop silently. The stored value is a
49
+ // preference the server re-derives without it, so losing it costs a
50
+ // resolve rather than the act that needed one.
51
+ }
52
+ }
53
+ export function readActingOrganization(appId) {
54
+ const storage = getStorage();
55
+ let raw;
56
+ if (!storage)
57
+ return null;
58
+ try {
59
+ raw = storage.getItem(orgKey(appId));
60
+ }
61
+ catch {
62
+ // A storage that holds a handle but refuses reads (a partitioned or
63
+ // locked-down context). See `storeActingOrganization`.
64
+ return null;
65
+ }
66
+ const trimmed = raw?.trim() ?? '';
67
+ return trimmed === '' ? null : trimmed;
68
+ }
69
+ export function clearActingOrganization(appId) {
70
+ const storage = getStorage();
71
+ if (!storage)
72
+ return;
73
+ try {
74
+ storage.removeItem(orgKey(appId));
75
+ }
76
+ catch {
77
+ // See `storeActingOrganization`.
78
+ }
79
+ }
80
+ /**
81
+ * Which organization the next act uses.
82
+ *
83
+ * `ask: false` runs the same ladder and stops where it would raise the hosted
84
+ * chooser, answering null instead. That is what an app reading its own state
85
+ * before it acts needs: the question the chooser asks is one only the person
86
+ * can answer, and asking it unbidden takes the page over for something the
87
+ * reader never pressed.
88
+ */
89
+ export async function resolveActingOrganization(deps, options = {}) {
90
+ const fromDocument = await deps.activeDocumentOrganizationId();
91
+ if (fromDocument) {
92
+ // Recorded, not merely returned. This is where the app is working, so it
93
+ // is what the next act spends once the document closes; without it a
94
+ // reader who opens a document, closes it and creates another is asked
95
+ // where to put the second one, having just worked in the answer. It carries no name (the
96
+ // document named an id) and does not reach the tab's slot: a document
97
+ // can live in an organization the reader reaches through a shared
98
+ // project and is not a member of, which is a place to act from while it
99
+ // is open rather than a choice to carry.
100
+ deps.remember(fromDocument, '', false);
101
+ return fromDocument;
102
+ }
103
+ const cached = deps.cachedResolution();
104
+ if (cached)
105
+ return cached;
106
+ // The stored choice is a preference rather than an answer: the server
107
+ // validates it against membership and ignores one that no longer holds, so
108
+ // a stale value self-heals into whatever the caller's context resolves to.
109
+ const launch = deps.launchPreference();
110
+ const stored = launch === null ? deps.storedPreference() : null;
111
+ const first = await deps.fetchResolved(launch ?? stored);
112
+ if (first.kind === 'resolved') {
113
+ // Membership is not the whole of what makes a choice usable. An
114
+ // organization the app has been uninstalled from still resolves, so a
115
+ // stored choice naming one would raise the install for a place somebody
116
+ // deliberately removed this app from, on every load, for as long as the
117
+ // tab lives. Read as stale instead: drop it and ask again with no
118
+ // preference, which reaches whatever this caller's context resolves to
119
+ // and otherwise the chooser, where the reader says where to work now.
120
+ //
121
+ // Only for a choice this tab was CARRYING. A launch, a resolution the
122
+ // reader just made, or their single membership is the answer either way,
123
+ // and the install is what it needs.
124
+ if (!first.installed && stored !== null && first.organizationId === stored) {
125
+ deps.forgetPreference();
126
+ const healed = await deps.fetchResolved(null);
127
+ if (healed.kind === 'resolved')
128
+ return keep(deps, healed);
129
+ }
130
+ else {
131
+ return keep(deps, first);
132
+ }
133
+ }
134
+ // Context could not answer. Only the person can, so a caller that asked not
135
+ // to raise a surface is told that rather than asked.
136
+ if (options.ask === false)
137
+ return null;
138
+ // The surface offers their own memberships and creates one where they have
139
+ // none, so the answer it gives is always one the server can resolve: asking
140
+ // twice would be asking the same question again rather than making progress.
141
+ const picked = await deps.pickOrganization();
142
+ if (!picked) {
143
+ throw new BrassError(first.kind === 'no-organization'
144
+ ? 'No organization yet. Create one to continue.'
145
+ : 'No organization chosen.', { status: 400 });
146
+ }
147
+ const second = await deps.fetchResolved(picked);
148
+ if (second.kind === 'resolved')
149
+ return keep(deps, second);
150
+ throw new BrassError('Brass could not use the organization you chose.', {
151
+ status: 400,
152
+ });
153
+ }
154
+ function keep(deps, outcome) {
155
+ deps.remember(outcome.organizationId, outcome.organizationName, outcome.installed);
156
+ return outcome.organizationId;
157
+ }
158
+ /**
159
+ * What the chrome names: the organization the NEXT act would spend.
160
+ *
161
+ * An open document takes it over, because the document's organization is what
162
+ * every act made while it is open spends. That holds even when the name is
163
+ * unknown, which is a document in an organization this reader reaches through
164
+ * a directly shared project: the menu says nothing rather than naming the memo
165
+ * over it. Closing the document hands it back, so the two never disagree with
166
+ * what the app is about to do.
167
+ *
168
+ * Named whether or not the caller belongs to anywhere else. The menu offers a
169
+ * switch, and the surface behind it is also how a first or a second
170
+ * organization gets created, so what a caller with one has is a choice they
171
+ * have not made yet rather than no choice; naming the organization is what
172
+ * makes that switch legible instead of an offer to change something the menu
173
+ * never states.
174
+ *
175
+ * Null where neither is known, which is an ordinary state rather than a gap
176
+ * to fill: an app reached by its own URL, holding no document and nothing
177
+ * stored, has no organization until an act needs one, and resolving one to
178
+ * decorate a menu is exactly the boot-time question this design refuses.
179
+ */
180
+ export function visibleOrganization(openDocument, memo) {
181
+ // An open document takes the menu over ENTIRELY, naming its organization
182
+ // or nothing. Falling back to the memo here would be the exact fault this
183
+ // exists to prevent: the document's organization is what its acts spend,
184
+ // so a menu naming the memo over an open document names an organization
185
+ // the app is not using.
186
+ if (openDocument)
187
+ return named(openDocument.organization);
188
+ return named(memo);
189
+ }
190
+ function named(organization) {
191
+ // An id with no name is a resolution taken from an open document, which
192
+ // names no organization. There is nothing to draw until the chrome asks the
193
+ // server for one.
194
+ if (!organization || organization.name === '')
195
+ return null;
196
+ return { id: organization.id, name: organization.name };
197
+ }
198
+ // The refusals that are a question for the person rather than a fault: the
199
+ // caller belongs to several organizations and none of the context named one,
200
+ // or belongs to none at all.
201
+ const OUTCOME_CODES = new Map([
202
+ ['destination_required', 'needs-choice'],
203
+ ['no_organization', 'no-organization'],
204
+ ]);
205
+ export async function fetchActingOrganization(deps, prefer) {
206
+ const token = await deps.getAccessToken();
207
+ if (!token)
208
+ throw new BrassError('Not signed in', { status: 401 });
209
+ const headers = { authorization: `Bearer ${token}` };
210
+ applyClientVersionHeader(headers);
211
+ await applyAppAssertionHeader(headers, deps.getAppAssertion);
212
+ const base = deps.apiBaseUrl.replace(/\/$/, '');
213
+ const query = prefer ? `?prefer=${encodeURIComponent(prefer)}` : '';
214
+ const doFetch = deps.fetchImpl ?? fetch;
215
+ const response = await doFetch(`${base}/me/acting-organization${query}`, {
216
+ method: 'GET',
217
+ headers,
218
+ });
219
+ let body = {};
220
+ try {
221
+ body = (await response.json());
222
+ }
223
+ catch {
224
+ // Leaves the generic message below, which the status already qualifies.
225
+ }
226
+ if (response.ok && typeof body.organization_id === 'string') {
227
+ return {
228
+ kind: 'resolved',
229
+ organizationId: body.organization_id,
230
+ organizationName: typeof body.organization_name === 'string' ? body.organization_name : '',
231
+ // Absent reads as installed, so an older api that does not send it
232
+ // leaves the act to raise the refusal as it always did rather than
233
+ // asking for an install nothing established was missing.
234
+ installed: body.installed !== false,
235
+ };
236
+ }
237
+ const kind = typeof body.code === 'string' ? OUTCOME_CODES.get(body.code) : undefined;
238
+ if (response.status === 409 && kind)
239
+ return { kind };
240
+ throw new BrassError(typeof body.error === 'string' ? body.error : 'Could not resolve an organization', { status: response.status });
241
+ }
242
+ export async function switchActingOrganization(deps) {
243
+ const current = deps.current() ?? undefined;
244
+ const picked = await deps.pickOrganization(current);
245
+ if (!picked)
246
+ return false;
247
+ // Re-picking the organization the app is already acting in is a valid
248
+ // answer to the question the surface asks, and it moves nothing. The
249
+ // ordinary way to reach it is a caller with one organization opening the
250
+ // chooser to see what is there, so it must not cost them their open
251
+ // document and a page reload to say where they already are.
252
+ if (picked === current)
253
+ return false;
254
+ // The pick goes back to the server, which names the organization as the
255
+ // platform holds it and refuses one the caller cannot act in.
256
+ const named = await deps.fetchResolved(picked);
257
+ if (named.kind !== 'resolved')
258
+ return false;
259
+ // An organization this app is not installed on cannot serve it, so the
260
+ // install is asked for here rather than left to the next act. Without this
261
+ // the switch appears to succeed and the refusal arrives later, or on a
262
+ // reload, against work the reader has already started.
263
+ if (!named.installed && !(await deps.offerInstall(named.organizationId))) {
264
+ // Declining abandons the switch. Recording an organization the app cannot
265
+ // act in leaves the reader on one organization's screen with nothing they
266
+ // press able to work.
267
+ return false;
268
+ }
269
+ deps.remember(named.organizationId, named.organizationName);
270
+ // Everything the app read under the previous organization is about a
271
+ // different one now, and the app has no way to know which of its own state
272
+ // that covers. The reader asked for this, so re-reading the app is the
273
+ // expected consequence of their own act rather than a surprise, and the
274
+ // choice is already recorded so it survives.
275
+ deps.reload();
276
+ return true;
277
+ }
278
+ //# sourceMappingURL=acting-organization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acting-organization.js","sourceRoot":"","sources":["../src/acting-organization.ts"],"names":[],"mappings":"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"}
@@ -12,13 +12,13 @@ export async function applyAppAssertionHeader(headers, getAppAssertion) {
12
12
  if (assertion)
13
13
  headers['x-brass-app-assertion'] = assertion;
14
14
  }
15
- // Attaches the SDK's version as `x-brass-client` (`sdk/<version>`), the same
16
- // value reported on /refresh and the WS connect URL, so the server sees the
17
- // deployed SDK version at the data-API surface too. The minimum-version gate
18
- // reads it at document open to serve a too-old client an upgrade message
19
- // (rendered verbatim through `session.error`). Same boundary as the
20
- // app-assertion header: data API only, never a presigned URL. Unconditional, so the
21
- // version distribution covers read-only apps that never refresh.
15
+ // Names the SDK and its version. Brass answers a build too old to serve with
16
+ // an upgrade message, which reaches the app as `session.error` and is
17
+ // rendered verbatim.
18
+ //
19
+ // Same boundary as the app-assertion header: Brass's own API calls carry it,
20
+ // a presigned URL never does. Attached unconditionally, so an app that only
21
+ // ever reads carries it too.
22
22
  export function applyClientVersionHeader(headers) {
23
23
  headers['x-brass-client'] = SDK_CLIENT_ID;
24
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"api-headers.js","sourceRoot":"","sources":["../src/api-headers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,wEAAwE;AACxE,kDAAkD;AAClD,EAAE;AACF,uEAAuE;AACvE,mEAAmE;AACnE,uEAAuE;AACvE,yEAAyE;AACzE,QAAQ;AACR,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,OAA+B,EAC/B,eAA8C;IAE9C,MAAM,SAAS,GAAG,MAAM,eAAe,EAAE,EAAE,CAAC;IAC5C,IAAI,SAAS;QAAE,OAAO,CAAC,uBAAuB,CAAC,GAAG,SAAS,CAAC;AAC9D,CAAC;AAED,6EAA6E;AAC7E,4EAA4E;AAC5E,6EAA6E;AAC7E,yEAAyE;AACzE,oEAAoE;AACpE,oFAAoF;AACpF,iEAAiE;AACjE,MAAM,UAAU,wBAAwB,CAAC,OAA+B;IACtE,OAAO,CAAC,gBAAgB,CAAC,GAAG,aAAa,CAAC;AAC5C,CAAC"}
1
+ {"version":3,"file":"api-headers.js","sourceRoot":"","sources":["../src/api-headers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,wEAAwE;AACxE,kDAAkD;AAClD,EAAE;AACF,uEAAuE;AACvE,mEAAmE;AACnE,uEAAuE;AACvE,yEAAyE;AACzE,QAAQ;AACR,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,OAA+B,EAC/B,eAA8C;IAE9C,MAAM,SAAS,GAAG,MAAM,eAAe,EAAE,EAAE,CAAC;IAC5C,IAAI,SAAS;QAAE,OAAO,CAAC,uBAAuB,CAAC,GAAG,SAAS,CAAC;AAC9D,CAAC;AAED,6EAA6E;AAC7E,sEAAsE;AACtE,qBAAqB;AACrB,EAAE;AACF,6EAA6E;AAC7E,4EAA4E;AAC5E,6BAA6B;AAC7B,MAAM,UAAU,wBAAwB,CAAC,OAA+B;IACtE,OAAO,CAAC,gBAAgB,CAAC,GAAG,aAAa,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,74 @@
1
+ export type CallServiceBody = string | Blob | FormData | URLSearchParams;
2
+ export interface CallServiceOptions {
3
+ /** Defaults to `'POST'`. */
4
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
5
+ /**
6
+ * Sent to the service as-is, whatever a vendor asks for. The credential is
7
+ * not among them; Brass attaches that.
8
+ */
9
+ headers?: Record<string, string>;
10
+ body?: CallServiceBody;
11
+ /**
12
+ * Whether Brass asks the user for anything the call is missing: a
13
+ * credential at the host, consent to install the app in their
14
+ * organization, or which organization to act in. The call is sent again
15
+ * once they answer.
16
+ *
17
+ * Defaults to false, so the refusal comes back with its `code` for the
18
+ * app's own surface to answer, and `Brass.connectService(host)` opens the
19
+ * credential dialog when the reader asks for it.
20
+ */
21
+ requestMissingCredentials?: boolean;
22
+ }
23
+ export declare function encodeServiceHeaders(headers: Record<string, string>): string;
24
+ export declare function normalizeServiceHost(host: string): string;
25
+ export declare function brokeredCallUrl(apiBaseUrl: string, organizationId: string, host: string, path: string): string;
26
+ export declare function invalidServicePath(path: string): boolean;
27
+ export interface CallServiceDeps {
28
+ apiBaseUrl: string;
29
+ getAccessToken: () => Promise<string | null>;
30
+ getAppAssertion: () => Promise<string | null>;
31
+ /**
32
+ * Which organization this call uses, resolved from context. `ask` is true
33
+ * only where the caller asked for the missing pieces, and the resolve
34
+ * otherwise answers null for a page whose reader would have to be asked.
35
+ */
36
+ resolveOrganization: (ask: boolean) => Promise<string | null>;
37
+ /** Offers the hosted install dialog; true once an install landed. */
38
+ offerInstallConsent: (err: unknown) => Promise<boolean>;
39
+ /**
40
+ * Offers the hosted service-credential dialog for one host; true once that
41
+ * host became usable.
42
+ */
43
+ offerServiceCredential: (organizationId: string, host: string) => Promise<boolean>;
44
+ fetchImpl?: typeof fetch;
45
+ }
46
+ export declare function callService(deps: CallServiceDeps, host: string, path: string, options?: CallServiceOptions): Promise<Response>;
47
+ export interface AppServiceState {
48
+ host: string;
49
+ /**
50
+ * The pattern your manifest declared, where it declared one
51
+ * (`*.myshopify.com`). One entry per tenant connected under it carries it,
52
+ * plus one naming the pattern itself while none is connected. Absent on a
53
+ * host declared literally.
54
+ */
55
+ declared_as?: string;
56
+ /**
57
+ * Whether `Brass.connectService(host)` can lead somewhere here. True where
58
+ * somebody using your app can supply what this host needs; false where it is
59
+ * waiting on the credential you enter as the app's author, which nobody on
60
+ * the page can settle.
61
+ */
62
+ connectable: boolean;
63
+ ready: boolean;
64
+ code?: string;
65
+ /**
66
+ * Values your manifest marked `readable`, for the account this call would
67
+ * spend. Use them to build a path the platform cannot build for you, such as
68
+ * the account id Cloudflare puts in every URL. Present only on a ready
69
+ * service, since only then is there an account to name.
70
+ */
71
+ values?: Record<string, string>;
72
+ }
73
+ export declare function listAppServices(deps: Pick<CallServiceDeps, 'apiBaseUrl' | 'getAccessToken' | 'getAppAssertion' | 'fetchImpl'>, organizationId: string): Promise<AppServiceState[]>;
74
+ //# sourceMappingURL=call-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"call-service.d.ts","sourceRoot":"","sources":["../src/call-service.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,IAAI,GAAG,QAAQ,GAAG,eAAe,CAAC;AAEzE,MAAM,WAAW,kBAAkB;IACjC,4BAA4B;IAC5B,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;IACrD;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB;;;;;;;;;OASG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACrC;AAaD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAE5E;AAeD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,MAAM,CAKR;AAYD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAwBxD;AAgBD,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,eAAe,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9C;;;;OAIG;IACH,mBAAmB,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC9D,qEAAqE;IACrE,mBAAmB,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD;;;OAGG;IACH,sBAAsB,EAAE,CACtB,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,KACT,OAAO,CAAC,OAAO,CAAC,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,wBAAsB,WAAW,CAC/B,IAAI,EAAE,eAAe,EACrB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,QAAQ,CAAC,CAgDnB;AA4DD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAMD,wBAAsB,eAAe,CACnC,IAAI,EAAE,IAAI,CACR,eAAe,EACf,YAAY,GAAG,gBAAgB,GAAG,iBAAiB,GAAG,WAAW,CAClE,EACD,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,eAAe,EAAE,CAAC,CAkD5B"}