@omg-dev/pwa 0.4.26 → 0.4.27

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.
@@ -4,6 +4,7 @@ import { createRoot } from "react-dom/client";
4
4
  //#region src/remix-cta.tsx
5
5
  const DISMISS_KEY = "vibes-remix:dismissed";
6
6
  const REMIX_ORIGIN = typeof import.meta !== "undefined" && import.meta.env?.VITE_REMIX_ORIGIN || "https://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.27",
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"
@@ -23,6 +25,11 @@ const REMIX_ORIGIN =
23
25
  (typeof import.meta !== "undefined" &&
24
26
  (import.meta as { env?: Record<string, string | undefined> }).env?.VITE_REMIX_ORIGIN) ||
25
27
  "https://omg.dev"
28
+ const CONTROLPLANE_URL = (
29
+ (typeof import.meta !== "undefined" &&
30
+ (import.meta as { env?: Record<string, string | undefined> }).env?.VITE_CONTROLPLANE_URL) ||
31
+ "https://backend.omg.dev"
32
+ ).replace(/\/$/, "")
26
33
 
27
34
  function appSlug(): string | null {
28
35
  if (typeof import.meta === "undefined") return null
@@ -48,6 +55,23 @@ function setDismissed() {
48
55
  }
49
56
  }
50
57
 
58
+ /** Live check against the public catalog. Fail closed on network/error. */
59
+ async function isRemixable(slug: string): Promise<boolean> {
60
+ try {
61
+ const res = await fetch(`${CONTROLPLANE_URL}/api/projects/isRemixable`, {
62
+ method: "POST",
63
+ headers: { "content-type": "application/json" },
64
+ body: JSON.stringify({ slug }),
65
+ cache: "no-store",
66
+ })
67
+ if (!res.ok) return false
68
+ const data = (await res.json()) as { remixable?: unknown }
69
+ return data?.remixable === true
70
+ } catch {
71
+ return false
72
+ }
73
+ }
74
+
51
75
  const STYLE_ID = "vibes-remix-cta-styles"
52
76
  function ensureStyles() {
53
77
  if (typeof document === "undefined") return
@@ -139,10 +163,15 @@ function shouldShow(): boolean {
139
163
  return appSlug() !== null
140
164
  }
141
165
 
142
- function mount() {
166
+ async function mount() {
143
167
  if (!shouldShow()) return
144
168
  const slug = appSlug()
145
169
  if (!slug) return
170
+ // Only mount for Explore-listed public apps. Private published apps get no
171
+ // CTA (branding lives on the omg badge when that path is enabled).
172
+ if (!(await isRemixable(slug))) return
173
+ if (isDismissed()) return
174
+ if (document.getElementById("vibes-remix-cta")) return
146
175
  ensureStyles()
147
176
  const host = document.createElement("div")
148
177
  host.id = "vibes-remix-cta"
@@ -153,8 +182,8 @@ function mount() {
153
182
  if (typeof window !== "undefined") {
154
183
  // Let the app paint first — the CTA is an afterthought, not a gate.
155
184
  if (document.readyState === "complete") {
156
- setTimeout(mount, 1800)
185
+ setTimeout(() => void mount(), 1800)
157
186
  } else {
158
- window.addEventListener("load", () => setTimeout(mount, 1800), { once: true })
187
+ window.addEventListener("load", () => setTimeout(() => void mount(), 1800), { once: true })
159
188
  }
160
189
  }