@omg-dev/pwa 0.4.26 → 0.4.28

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.
@@ -3,7 +3,8 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { createRoot } from "react-dom/client";
4
4
  //#region src/remix-cta.tsx
5
5
  const DISMISS_KEY = "vibes-remix:dismissed";
6
- const REMIX_ORIGIN = typeof import.meta !== "undefined" && import.meta.env?.VITE_REMIX_ORIGIN || "https://omg.dev";
6
+ const REMIX_ORIGIN = typeof import.meta !== "undefined" && import.meta.env?.VITE_REMIX_ORIGIN || "https://app.omg.dev";
7
+ const CONTROLPLANE_URL = (typeof import.meta !== "undefined" && import.meta.env?.VITE_CONTROLPLANE_URL || "https://backend.omg.dev").replace(/\/$/, "");
7
8
  function appSlug() {
8
9
  if (typeof import.meta === "undefined") return null;
9
10
  const slug = import.meta.env?.VITE_APP_SLUG?.trim();
@@ -22,6 +23,21 @@ function setDismissed() {
22
23
  localStorage.setItem(DISMISS_KEY, "1");
23
24
  } catch {}
24
25
  }
26
+ /** Live check against the public catalog. Fail closed on network/error. */
27
+ async function isRemixable(slug) {
28
+ try {
29
+ const res = await fetch(`${CONTROLPLANE_URL}/api/projects/isRemixable`, {
30
+ method: "POST",
31
+ headers: { "content-type": "application/json" },
32
+ body: JSON.stringify({ slug }),
33
+ cache: "no-store"
34
+ });
35
+ if (!res.ok) return false;
36
+ return (await res.json())?.remixable === true;
37
+ } catch {
38
+ return false;
39
+ }
40
+ }
25
41
  const STYLE_ID = "vibes-remix-cta-styles";
26
42
  function ensureStyles() {
27
43
  if (typeof document === "undefined") return;
@@ -106,17 +122,20 @@ function shouldShow() {
106
122
  if (isDismissed()) return false;
107
123
  return appSlug() !== null;
108
124
  }
109
- function mount() {
125
+ async function mount() {
110
126
  if (!shouldShow()) return;
111
127
  const slug = appSlug();
112
128
  if (!slug) return;
129
+ if (!await isRemixable(slug)) return;
130
+ if (isDismissed()) return;
131
+ if (document.getElementById("vibes-remix-cta")) return;
113
132
  ensureStyles();
114
133
  const host = document.createElement("div");
115
134
  host.id = "vibes-remix-cta";
116
135
  document.body.appendChild(host);
117
136
  createRoot(host).render(/* @__PURE__ */ jsx(RemixCta, { slug }));
118
137
  }
119
- if (typeof window !== "undefined") if (document.readyState === "complete") setTimeout(mount, 1800);
120
- else window.addEventListener("load", () => setTimeout(mount, 1800), { once: true });
138
+ if (typeof window !== "undefined") if (document.readyState === "complete") setTimeout(() => void mount(), 1800);
139
+ else window.addEventListener("load", () => setTimeout(() => void mount(), 1800), { once: true });
121
140
  //#endregion
122
141
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omg-dev/pwa",
3
- "version": "0.4.26",
3
+ "version": "0.4.28",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
package/src/remix-cta.tsx CHANGED
@@ -11,6 +11,8 @@
11
11
  // Policy (deliberately quiet):
12
12
  // - never in an iframe (the dashboard preview pane is not the app URL)
13
13
  // - only when import.meta.env.VITE_APP_SLUG is baked in (prod builds only)
14
+ // - only when the app is currently listed public (isPublic) — private apps
15
+ // keep no CTA; remixBySlug rejects them
14
16
  // - dismissable forever (per-origin localStorage)
15
17
 
16
18
  import { createRoot } from "react-dom/client"
@@ -18,11 +20,21 @@ import { isInIframe } from "./core"
18
20
 
19
21
  const DISMISS_KEY = "vibes-remix:dismissed"
20
22
  // The dashboard origin the remix deep-link points at. Overridable for
21
- // self-host via VITE_REMIX_ORIGIN; defaults to the omg.dev dashboard.
23
+ // self-host via VITE_REMIX_ORIGIN.
24
+ //
25
+ // app.omg.dev, NOT omg.dev: /remix/<slug> is a dashboard SPA route, and the
26
+ // dashboard moved off omg.dev when that host became the marketing landing.
27
+ // The landing has no /remix route, so the old default sent every recipient of
28
+ // a shared app to a "Not Found" page — the whole recipient->creator loop.
22
29
  const REMIX_ORIGIN =
23
30
  (typeof import.meta !== "undefined" &&
24
31
  (import.meta as { env?: Record<string, string | undefined> }).env?.VITE_REMIX_ORIGIN) ||
25
- "https://omg.dev"
32
+ "https://app.omg.dev"
33
+ const CONTROLPLANE_URL = (
34
+ (typeof import.meta !== "undefined" &&
35
+ (import.meta as { env?: Record<string, string | undefined> }).env?.VITE_CONTROLPLANE_URL) ||
36
+ "https://backend.omg.dev"
37
+ ).replace(/\/$/, "")
26
38
 
27
39
  function appSlug(): string | null {
28
40
  if (typeof import.meta === "undefined") return null
@@ -48,6 +60,23 @@ function setDismissed() {
48
60
  }
49
61
  }
50
62
 
63
+ /** Live check against the public catalog. Fail closed on network/error. */
64
+ async function isRemixable(slug: string): Promise<boolean> {
65
+ try {
66
+ const res = await fetch(`${CONTROLPLANE_URL}/api/projects/isRemixable`, {
67
+ method: "POST",
68
+ headers: { "content-type": "application/json" },
69
+ body: JSON.stringify({ slug }),
70
+ cache: "no-store",
71
+ })
72
+ if (!res.ok) return false
73
+ const data = (await res.json()) as { remixable?: unknown }
74
+ return data?.remixable === true
75
+ } catch {
76
+ return false
77
+ }
78
+ }
79
+
51
80
  const STYLE_ID = "vibes-remix-cta-styles"
52
81
  function ensureStyles() {
53
82
  if (typeof document === "undefined") return
@@ -139,10 +168,15 @@ function shouldShow(): boolean {
139
168
  return appSlug() !== null
140
169
  }
141
170
 
142
- function mount() {
171
+ async function mount() {
143
172
  if (!shouldShow()) return
144
173
  const slug = appSlug()
145
174
  if (!slug) return
175
+ // Only mount for Explore-listed public apps. Private published apps get no
176
+ // CTA (branding lives on the omg badge when that path is enabled).
177
+ if (!(await isRemixable(slug))) return
178
+ if (isDismissed()) return
179
+ if (document.getElementById("vibes-remix-cta")) return
146
180
  ensureStyles()
147
181
  const host = document.createElement("div")
148
182
  host.id = "vibes-remix-cta"
@@ -153,8 +187,8 @@ function mount() {
153
187
  if (typeof window !== "undefined") {
154
188
  // Let the app paint first — the CTA is an afterthought, not a gate.
155
189
  if (document.readyState === "complete") {
156
- setTimeout(mount, 1800)
190
+ setTimeout(() => void mount(), 1800)
157
191
  } else {
158
- window.addEventListener("load", () => setTimeout(mount, 1800), { once: true })
192
+ window.addEventListener("load", () => setTimeout(() => void mount(), 1800), { once: true })
159
193
  }
160
194
  }