apps 0.0.1-beta.17 → 0.0.1-beta.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/js/ui/serving.js CHANGED
@@ -19,186 +19,58 @@ import {
19
19
  } from "../chunk-CDL5PM2Q.js";
20
20
  import "../chunk-CZ7CSFO4.js";
21
21
 
22
- // src/implementation/ui-errors.ts
23
- var installAppFailureUI = () => {
24
- let activeDialog;
25
- let windowStart = Date.now(), reports = 0, dropped = 0;
26
- const recent = /* @__PURE__ */ new Map();
27
- const extension = /^(?:chrome|moz|safari-web)-extension:/;
28
- const errorTypes = /* @__PURE__ */ new Set([
29
- "Error",
30
- "TypeError",
31
- "ReferenceError",
32
- "SyntaxError",
33
- "RangeError",
34
- "URIError",
35
- "EvalError",
36
- "AggregateError"
37
- ]);
38
- const sourceAttributes = (filename, line = 0, column = 0) => {
39
- const attributes = [];
40
- if (!URL.canParse(filename)) return attributes;
41
- const url = new URL(filename);
42
- if (url.origin !== location.origin || !url.pathname.startsWith("/_executor/assets/") || !url.pathname.endsWith(".js"))
43
- return attributes;
44
- attributes.push({ key: "code.file.path", value: { stringValue: url.pathname } });
45
- if (Number.isSafeInteger(line) && line > 0)
46
- attributes.push({ key: "code.line.number", value: { intValue: String(line) } });
47
- if (Number.isSafeInteger(column) && column > 0)
48
- attributes.push({ key: "code.column.number", value: { intValue: String(column) } });
49
- return attributes;
50
- };
51
- const fail = (kind, details, attributes = []) => {
52
- const now = Date.now();
53
- if (now - windowStart >= 6e4) {
54
- windowStart = now;
55
- reports = 0;
56
- recent.clear();
57
- }
58
- const signature = `${kind}:${details.slice(0, 2e3)}`;
59
- const previous = recent.get(signature);
60
- if (previous !== void 0 && now - previous < 1e3 || reports >= 20) {
61
- dropped++;
62
- return;
63
- }
64
- recent.set(signature, now);
65
- reports++;
66
- attributes.push({
67
- key: "executor.ui.failure.suppressed",
68
- value: { intValue: String(dropped) }
69
- });
70
- dropped = 0;
71
- const traceId = Array.from(
72
- crypto.getRandomValues(new Uint8Array(16)),
73
- (byte) => byte.toString(16).padStart(2, "0")
74
- ).join("");
75
- const time = `${Date.now()}000000`;
76
- let status;
77
- let deliveryMessage = "Sending error report\u2026";
78
- const show = () => {
79
- if (activeDialog?.open) return;
80
- const host = document.createElement("div");
81
- const root = host.attachShadow({ mode: "open" });
82
- const dialog = document.createElement("dialog");
83
- activeDialog = dialog;
84
- dialog.setAttribute("aria-labelledby", "executor-failure-title");
85
- const title = document.createElement("h2");
86
- title.id = "executor-failure-title";
87
- title.textContent = kind === "script" ? "This app could not load" : "This app stopped working";
88
- const message = document.createElement("p");
89
- message.textContent = kind === "script" ? "A file needed by this page did not load. Reload the page to try again." : "The app encountered an unexpected error. Reload the page to try again.";
90
- const warning = document.createElement("p");
91
- warning.textContent = "Copy any unsaved work before reloading.";
92
- const disclosure = document.createElement("details");
93
- const summary = document.createElement("summary");
94
- summary.textContent = "Error details";
95
- const report = document.createElement("textarea");
96
- report.readOnly = true;
97
- report.rows = 10;
98
- report.cols = 36;
99
- report.setAttribute("aria-label", "Error details");
100
- report.value = `${details.slice(0, 12e3)}
22
+ // src/implementation/browser-scripts.gen.ts
23
+ var appWatchSource = '"use strict";(()=>{var s=()=>{let i=document.getElementById("executor-context")?.textContent;if(!i)return;let n=JSON.parse(i);if(typeof n!="object"||n===null||!("deployment"in n))return;let r=n.deployment;if(typeof r!="string"||r.length===0)return;let t=new EventSource("/_executor/version"),l=!1,o=()=>{l||(l=!0,t.close(),location.reload())};t.addEventListener("version",d=>{let e=JSON.parse(d.data);typeof e=="object"&&e!==null&&"deployment"in e&&(e.deployment===null||typeof e.deployment=="string")&&e.deployment!==r&&o()}),t.addEventListener("revoked",o),window.addEventListener("executor:deployment-changed",o),window.addEventListener("pagehide",()=>t.close(),{once:!0}),window.addEventListener("pageshow",d=>{d.persisted&&o()})};s();})();';
24
+ var appFailureSource = '"use strict";(()=>{var D=()=>{let d,w=Date.now(),m=0,g=0,f=new Map,A=/^(?:chrome|moz|safari-web)-extension:/,R=new Set(["Error","TypeError","ReferenceError","SyntaxError","RangeError","URIError","EvalError","AggregateError"]),x=(e,t=0,i=0)=>{let r=[];if(!URL.canParse(e))return r;let a=new URL(e);return a.origin!==location.origin||!a.pathname.startsWith("/_executor/assets/")||!a.pathname.endsWith(".js")||(r.push({key:"code.file.path",value:{stringValue:a.pathname}}),Number.isSafeInteger(t)&&t>0&&r.push({key:"code.line.number",value:{intValue:String(t)}}),Number.isSafeInteger(i)&&i>0&&r.push({key:"code.column.number",value:{intValue:String(i)}})),r},E=(e,t,i=[])=>{let r=Date.now();r-w>=6e4&&(w=r,m=0,f.clear());let a=`${e}:${t.slice(0,2e3)}`,b=f.get(a);if(b!==void 0&&r-b<1e3||m>=20){g++;return}f.set(a,r),m++,i.push({key:"executor.ui.failure.suppressed",value:{intValue:String(g)}}),g=0;let h=Array.from(crypto.getRandomValues(new Uint8Array(16)),s=>s.toString(16).padStart(2,"0")).join(""),S=`${Date.now()}000000`,n,u="Sending error report\\u2026",k=()=>{if(d?.open)return;let s=document.createElement("div"),V=s.attachShadow({mode:"open"}),o=document.createElement("dialog");d=o,o.setAttribute("aria-labelledby","executor-failure-title");let y=document.createElement("h2");y.id="executor-failure-title",y.textContent=e==="script"?"This app could not load":"This app stopped working";let C=document.createElement("p");C.textContent=e==="script"?"A file needed by this page did not load. Reload the page to try again.":"The app encountered an unexpected error. Reload the page to try again.";let L=document.createElement("p");L.textContent="Copy any unsaved work before reloading.";let T=document.createElement("details"),v=document.createElement("summary");v.textContent="Error details";let c=document.createElement("textarea");c.readOnly=!0,c.rows=10,c.cols=36,c.setAttribute("aria-label","Error details"),c.value=`${t.slice(0,12e3)}\n\nDiagnostic ID: ${h}`,T.append(v,c),n=document.createElement("p"),n.setAttribute("role","status"),n.textContent=u;let l=document.createElement("button");l.type="button",l.textContent="Reload page",l.addEventListener("click",()=>location.reload());let p=document.createElement("button");p.type="button",p.textContent="Close",p.addEventListener("click",()=>o.close()),o.addEventListener("close",()=>{d===o&&(d=void 0),s.remove()}),o.append(y,C,L,T,n,l,p),V.append(o),document.documentElement.append(s),o.showModal()};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",k,{once:!0}):k(),fetch("/_executor/api/telemetry/traces",{method:"POST",headers:{"content-type":"application/json"},credentials:"same-origin",keepalive:!0,body:JSON.stringify({resourceSpans:[{scopeSpans:[{spans:[{traceId:h,spanId:h.slice(0,16),name:"ui.app.failure",kind:1,startTimeUnixNano:S,endTimeUnixNano:S,status:{code:2},attributes:[{key:"executor.ui.failure.kind",value:{stringValue:e}},...i]}]}]}]})}).then(s=>{u=s.ok?"Error report sent.":"Could not send the error report. You can copy the error details.",n&&(n.textContent=u)},()=>{u="Could not send the error report. You can copy the error details.",n&&(n.textContent=u)})};window.addEventListener("error",e=>{if(e instanceof ErrorEvent){if(A.test(e.filename))return;let t=x(e.filename,e.lineno,e.colno);e.error instanceof Error&&R.has(e.error.name)&&t.push({key:"exception.type",value:{stringValue:e.error.name}}),E("runtime",e.error instanceof Error?e.error.stack??e.message:e.message,t)}else e.target instanceof HTMLScriptElement&&E("script",`Could not load script: ${e.target.src}`,x(e.target.src))},!0),window.addEventListener("unhandledrejection",e=>{let t=e.reason;E("rejection",t instanceof Error?t.stack??t.message:"An app operation failed without handling its error.")})};D();})();';
25
+ var accountNoticeSource = '"use strict";(()=>{var g=()=>{let r=document.getElementById("executor-account-notice")?.textContent;if(!r)return;let t=JSON.parse(r);if(typeof t!="object"||t===null||!("items"in t)||!("fix"in t))return;let c=t.items,s=t.fix;if(!Array.isArray(c)||typeof s!="string")return;let a=()=>{let i=document.createElement("div"),x=i.attachShadow({mode:"open"}),d=document.createElement("style");d.textContent="aside{position:fixed;right:16px;bottom:16px;z-index:2147483647;max-width:360px;box-sizing:border-box;padding:14px 16px;border-radius:10px;border:1px solid #fcd34d;background:#fffbeb;color:#451a03;font:14px/1.45 ui-sans-serif,system-ui,sans-serif;box-shadow:0 8px 24px rgb(0 0 0/.12)}strong{display:block;margin-bottom:4px}ul{margin:0 0 10px;padding-left:18px}div{display:flex;gap:12px;align-items:center}a{color:inherit;font-weight:600}button{margin-left:auto;font:inherit;background:none;border:0;padding:0;color:inherit;text-decoration:underline;cursor:pointer}";let e=document.createElement("aside");e.setAttribute("role","status"),e.setAttribute("aria-label","Account warning");let l=document.createElement("strong");l.textContent="Some things might not work";let m=document.createElement("ul");for(let f of c){let p=document.createElement("li");p.textContent=String(f),m.append(p)}let u=document.createElement("div"),n=document.createElement("a");n.href=s,n.target="_top",n.textContent="Review accounts";let o=document.createElement("button");o.type="button",o.textContent="Dismiss",o.addEventListener("click",()=>i.remove()),u.append(n,o),e.append(l,m,u),x.append(d,e),document.documentElement.append(i)};document.readyState==="loading"?document.addEventListener("DOMContentLoaded",a,{once:!0}):a()};g();})();';
101
26
 
102
- Diagnostic ID: ${traceId}`;
103
- disclosure.append(summary, report);
104
- status = document.createElement("p");
105
- status.setAttribute("role", "status");
106
- status.textContent = deliveryMessage;
107
- const reload = document.createElement("button");
108
- reload.type = "button";
109
- reload.textContent = "Reload page";
110
- reload.addEventListener("click", () => location.reload());
111
- const close = document.createElement("button");
112
- close.type = "button";
113
- close.textContent = "Close";
114
- close.addEventListener("click", () => dialog.close());
115
- dialog.addEventListener("close", () => {
116
- if (activeDialog === dialog) activeDialog = void 0;
117
- host.remove();
118
- });
119
- dialog.append(title, message, warning, disclosure, status, reload, close);
120
- root.append(dialog);
121
- document.documentElement.append(host);
122
- dialog.showModal();
123
- };
124
- if (document.readyState === "loading") {
125
- document.addEventListener("DOMContentLoaded", show, { once: true });
126
- } else {
127
- show();
128
- }
129
- void fetch("/_executor/api/telemetry/traces", {
130
- method: "POST",
131
- headers: { "content-type": "application/json" },
132
- credentials: "same-origin",
133
- keepalive: true,
134
- body: JSON.stringify({
135
- resourceSpans: [
136
- {
137
- scopeSpans: [
138
- {
139
- spans: [
140
- {
141
- traceId,
142
- spanId: traceId.slice(0, 16),
143
- name: "ui.app.failure",
144
- kind: 1,
145
- startTimeUnixNano: time,
146
- endTimeUnixNano: time,
147
- status: { code: 2 },
148
- attributes: [
149
- { key: "executor.ui.failure.kind", value: { stringValue: kind } },
150
- ...attributes
151
- ]
152
- }
153
- ]
154
- }
155
- ]
156
- }
157
- ]
158
- })
159
- }).then(
160
- (response) => {
161
- deliveryMessage = response.ok ? "Error report sent." : "Could not send the error report. You can copy the error details.";
162
- if (status) status.textContent = deliveryMessage;
163
- },
164
- () => {
165
- deliveryMessage = "Could not send the error report. You can copy the error details.";
166
- if (status) status.textContent = deliveryMessage;
167
- }
168
- );
169
- };
170
- window.addEventListener(
171
- "error",
172
- (event) => {
173
- if (event instanceof ErrorEvent) {
174
- if (extension.test(event.filename)) return;
175
- const attributes = sourceAttributes(event.filename, event.lineno, event.colno);
176
- if (event.error instanceof Error && errorTypes.has(event.error.name))
177
- attributes.push({ key: "exception.type", value: { stringValue: event.error.name } });
178
- fail(
179
- "runtime",
180
- event.error instanceof Error ? event.error.stack ?? event.message : event.message,
181
- attributes
182
- );
183
- } else if (event.target instanceof HTMLScriptElement) {
184
- fail(
185
- "script",
186
- `Could not load script: ${event.target.src}`,
187
- sourceAttributes(event.target.src)
188
- );
189
- }
190
- },
191
- true
192
- );
193
- window.addEventListener("unhandledrejection", (event) => {
194
- const reason = event.reason;
195
- fail(
196
- "rejection",
197
- reason instanceof Error ? reason.stack ?? reason.message : "An app operation failed without handling its error."
198
- );
199
- });
27
+ // src/implementation/ui-account-notice.ts
28
+ var escape = (text) => text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
29
+ var accountProblemBlocks = (problem) => problem.reason === "missing" || problem.reason === "removed" || problem.reason === "incompatible" || problem.reason === "credentials_rejected";
30
+ var accountProblemText = (problem) => {
31
+ switch (problem.reason) {
32
+ case "missing":
33
+ return `No ${problem.provider} account chosen`;
34
+ case "removed":
35
+ return `${problem.provider} account was removed or is no longer shared`;
36
+ case "incompatible":
37
+ return `Chosen account can't be used as the ${problem.provider} account`;
38
+ case "credentials_rejected":
39
+ return `${problem.account} (${problem.provider}): sign-in rejected at last check`;
40
+ case "forbidden":
41
+ return `${problem.account} (${problem.provider}): missing permission at last check`;
42
+ case "upstream_unavailable":
43
+ return `${problem.account} (${problem.provider}): service unavailable at last check`;
44
+ case "check_failed":
45
+ return `${problem.account} (${problem.provider}): last check failed`;
46
+ }
47
+ };
48
+ var accountBlockedPage = (notice) => {
49
+ const title = `${escape(notice.app)} can't open`;
50
+ const items = notice.problems.map(
51
+ (problem) => `<li class="${accountProblemBlocks(problem) ? "bad" : "warn"}">${escape(accountProblemText(problem))}</li>`
52
+ ).join("");
53
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${title}</title><style>
54
+ :root{color-scheme:light dark;font-family:ui-sans-serif,system-ui,sans-serif;color:#18181b;background:#fafafa}
55
+ @media (prefers-color-scheme:dark){:root{color:#f4f4f5;background:#18181b}main{background:#27272a!important;border-color:#3f3f46!important}a{color:inherit!important;border-color:#52525b!important}a.primary{background:#f4f4f5!important;color:#18181b!important}p{color:#a1a1aa!important}}
56
+ body{margin:0;min-height:100vh;display:grid;place-items:center;padding:24px;box-sizing:border-box}
57
+ main{max-width:480px;width:100%;background:#fff;border:1px solid #e4e4e7;border-radius:12px;padding:28px}
58
+ h1{margin:0 0 8px;font-size:20px}p{margin:0 0 16px;color:#52525b;line-height:1.5}
59
+ ul{margin:0 0 24px;padding:0;list-style:none;display:grid;gap:8px}
60
+ li{display:flex;gap:10px;align-items:baseline;line-height:1.4}li::before{content:"";flex:none;width:8px;height:8px;border-radius:50%;transform:translateY(-1px)}
61
+ li.bad::before{background:#dc2626}li.warn::before{background:#f59e0b}
62
+ .actions{display:flex;flex-wrap:wrap;gap:8px}
63
+ a{padding:8px 14px;border-radius:8px;border:1px solid #d4d4d8;color:#18181b;text-decoration:none;font-weight:500}
64
+ a.primary{background:#18181b;border-color:#18181b;color:#fff}
65
+ </style></head><body><main><h1>${title}</h1><p>This profile's accounts need attention before the app can work.</p><ul>${items}</ul><div class="actions"><a class="primary" href="${escape(notice.fix)}">Fix accounts</a><a href="${escape(notice.choose)}">Open with another profile</a></div></main></body></html>`;
200
66
  };
201
- var appFailureBootstrap = `<script>(${installAppFailureUI.toString()})()</script>`;
67
+ var accountNoticeBootstrap = (notice) => `<script type="application/json" id="executor-account-notice">${JSON.stringify({
68
+ items: notice.problems.map(accountProblemText),
69
+ fix: notice.fix
70
+ }).replaceAll("<", "\\u003c")}</script><script>${accountNoticeSource}</script>`;
71
+
72
+ // src/implementation/ui-errors.ts
73
+ var appFailureBootstrap = `<script>${appFailureSource}</script>`;
202
74
 
203
75
  // src/implementation/ui-serving.ts
204
76
  var appDocument = (options) => Effect_exports.gen(function* () {
@@ -214,8 +86,15 @@ var appDocument = (options) => Effect_exports.gen(function* () {
214
86
  });
215
87
  if (pathname.includes(".") && pathname !== "/index.html")
216
88
  return HttpServerResponse_exports.empty({ status: 404 });
217
- const document2 = yield* options.asset("index.html");
218
- if (document2 === void 0)
89
+ const notice = options.accounts;
90
+ if (notice?.problems.some(accountProblemBlocks))
91
+ return HttpServerResponse_exports.text(accountBlockedPage(notice), {
92
+ status: 409,
93
+ contentType: "text/html",
94
+ headers: appPrivateHeaders
95
+ });
96
+ const document = yield* options.asset("index.html");
97
+ if (document === void 0)
219
98
  return HttpServerResponse_exports.text("This app has no UI.", {
220
99
  status: 404,
221
100
  headers: appPrivateHeaders
@@ -228,9 +107,9 @@ var appDocument = (options) => Effect_exports.gen(function* () {
228
107
  const telemetry = yield* CurrentTelemetryConfig;
229
108
  const attribute = (text) => text.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;");
230
109
  const metadata = telemetry === void 0 ? "" : `<meta name="executor-build" content="${attribute(telemetry.version)}"><meta name="executor-environment" content="${attribute(telemetry.environment)}">`;
231
- const boot = `${metadata}<base href="/_executor/assets/${attribute(options.deployment)}/"><script type="application/json" id="executor-context">${context}</script>${appFailureBootstrap}<script src="/_executor/watch.js" defer></script>`;
110
+ const boot = `${metadata}<base href="/_executor/assets/${attribute(options.deployment)}/"><script type="application/json" id="executor-context">${context}</script>${appFailureBootstrap}${notice === void 0 || notice.problems.length === 0 ? "" : accountNoticeBootstrap(notice)}<script src="/_executor/watch.js" defer></script>`;
232
111
  return HttpServerResponse_exports.text(
233
- new TextDecoder().decode(document2.body).replace("<!--executor-ui-->", boot),
112
+ new TextDecoder().decode(document.body).replace("<!--executor-ui-->", boot),
234
113
  { contentType: "text/html", headers: appPrivateHeaders }
235
114
  );
236
115
  });
@@ -252,34 +131,7 @@ var appAsset = (asset, build, path) => Effect_exports.gen(function* () {
252
131
  });
253
132
 
254
133
  // src/implementation/ui-watch.ts
255
- var installAppWatch = () => {
256
- const context = document.getElementById("executor-context")?.textContent;
257
- if (!context) return;
258
- const pinned = JSON.parse(context);
259
- if (typeof pinned !== "object" || pinned === null || !("deployment" in pinned)) return;
260
- const deployment = pinned.deployment;
261
- if (typeof deployment !== "string" || deployment.length === 0) return;
262
- const stream = new EventSource("/_executor/version");
263
- let reloading = false;
264
- const reload = () => {
265
- if (reloading) return;
266
- reloading = true;
267
- stream.close();
268
- location.reload();
269
- };
270
- stream.addEventListener("version", (event) => {
271
- const current = JSON.parse(event.data);
272
- if (typeof current === "object" && current !== null && "deployment" in current && (current.deployment === null || typeof current.deployment === "string") && current.deployment !== deployment)
273
- reload();
274
- });
275
- stream.addEventListener("revoked", reload);
276
- window.addEventListener("executor:deployment-changed", reload);
277
- window.addEventListener("pagehide", () => stream.close(), { once: true });
278
- window.addEventListener("pageshow", (event) => {
279
- if (event.persisted) reload();
280
- });
281
- };
282
- var appWatchScript = `(${installAppWatch.toString()})()`;
134
+ var appWatchScript = appWatchSource;
283
135
  export {
284
136
  appAsset,
285
137
  appDocument,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apps",
3
- "version": "0.0.1-beta.17",
3
+ "version": "0.0.1-beta.19",
4
4
  "description": "TypeScript apps with typed tools, accounts, storage, and UI.",
5
5
  "type": "module",
6
6
  "sideEffects": false,