[aidan] feat/affiliate-filenames: bind installs from stamped filenames

This commit is contained in:
abccodes
2026-07-10 03:01:32 -07:00
parent ab982afcea
commit ac2f8e31b6
10 changed files with 242 additions and 10 deletions
+2
View File
@@ -90,6 +90,7 @@ class SigninActivateRequest(BaseModel):
token: str
signin_method: Literal["google", "email"]
email: Optional[str] = None
app_install_id: Optional[str] = None
@auth.router.post("/signin-activate")
@@ -114,6 +115,7 @@ async def signin_activate(body: SigninActivateRequest):
"token": body.token,
"signin_method": body.signin_method,
"email": body.email,
"app_install_id": body.app_install_id,
},
)
except httpx.HTTPError as e:
+5
View File
@@ -58,9 +58,14 @@ def test_signin_activate_persists_user_id(client, reset_settings):
"token": "fake-bearer-1234567890abcdef",
"signin_method": "google",
"email": "smoke@example.com",
"app_install_id": "app-install-affiliate-123",
},
)
assert r.status_code == 200
assert any(
call.kwargs.get("json", {}).get("app_install_id") == "app-install-affiliate-123"
for call in instance.post.call_args_list
)
body = r.json()
assert body["user_id"] == "u-1234"
assert body["email"] == "smoke@example.com"
+119 -1
View File
@@ -13,6 +13,7 @@
// first_launch_at: 1700000000000, // unix ms; presence = "this isn't first launch"
// ref: "haik" | null, // populated once lookup succeeds
// ref_bound_at: 1700000000000 | null,
// ref_bind_method: "affiliate_filename_hash" | null,
// attempts: 0 // last polling attempt count, for debugging
// }
//
@@ -22,6 +23,7 @@
const fs = require("fs");
const path = require("path");
const crypto = require("crypto");
const os = require("os");
const DEFAULT_LANDING_URL = "https://openswarm.com";
const DEFAULT_CLOUD_URL = "https://api.openswarm.com";
@@ -35,6 +37,10 @@ const DEFAULT_CLOUD_URL = "https://api.openswarm.com";
// poll window instead of 60s.
const POLL_INTERVAL_MS = Number(process.env.OPENSWARM_AFFILIATE_POLL_INTERVAL_MS) || 5000;
const POLL_MAX_ATTEMPTS = Number(process.env.OPENSWARM_AFFILIATE_POLL_MAX_ATTEMPTS) || 12;
const FILENAME_ATTRIBUTION_WINDOW_MS =
Number(process.env.OPENSWARM_AFFILIATE_FILENAME_WINDOW_MS) || 30 * 24 * 60 * 60 * 1000;
const INSTALLER_HASH_RE =
/^OpenSwarm(?:-Setup)?-(?:arm64|x64)-([A-Za-z0-9_-]{16,32})(?: \([0-9]+\))?\.(dmg|exe|AppImage)$/i;
function getStateFilePath(userDataDir) {
return path.join(userDataDir, "install.json");
@@ -91,6 +97,90 @@ async function pollLookupOnce(cloudUrl, appInstallId) {
}
}
async function bindAffiliateHashOnce(cloudUrl, appInstallId, affiliateHash) {
const url = `${cloudUrl}/api/install/bind`;
const controller = new AbortController();
const t = setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
signal: controller.signal,
body: JSON.stringify({ affiliate_hash: affiliateHash, app_install_id: appInstallId }),
});
if (!res.ok) return null;
const body = await res.json();
if (body && typeof body.ref === "string" && body.ref) return body.ref;
return null;
} catch (_) {
return null;
} finally {
clearTimeout(t);
}
}
function hashFromInstallerBasename(filePath) {
const base = path.basename(String(filePath || ""));
const m = INSTALLER_HASH_RE.exec(base);
return m ? m[1] : null;
}
function likelyDownloadDirs(homeDir) {
if (!homeDir) return [];
return [path.join(homeDir, "Downloads"), path.join(homeDir, "Desktop")];
}
function recentInstallerHashesInDir(dir, nowMs) {
const out = [];
let entries = [];
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
} catch (_) {
return out;
}
for (const entry of entries) {
if (!entry.isFile()) continue;
const hash = hashFromInstallerBasename(entry.name);
if (!hash) continue;
const fullPath = path.join(dir, entry.name);
try {
const st = fs.statSync(fullPath);
const ageMs = nowMs - st.mtimeMs;
if (ageMs < 0 || ageMs > FILENAME_ATTRIBUTION_WINDOW_MS) continue;
out.push({ hash, path: fullPath, mtimeMs: st.mtimeMs });
} catch (_) {}
}
return out;
}
function findAffiliateHashFromInstaller({
platform = process.platform,
env = process.env,
homeDir = os.homedir(),
nowMs = Date.now(),
} = {}) {
if (platform === "linux") {
return hashFromInstallerBasename(env.APPIMAGE);
}
if (platform !== "darwin" && platform !== "win32") {
return null;
}
const matches = [];
for (const dir of likelyDownloadDirs(homeDir)) {
matches.push(...recentInstallerHashesInDir(dir, nowMs));
}
if (matches.length !== 1) {
if (matches.length > 1) {
console.log("[affiliate] multiple stamped installers found; falling back to welcome flow");
}
return null;
}
return matches[0].hash;
}
function delay(ms) {
return new Promise((r) => setTimeout(r, ms));
}
@@ -118,7 +208,15 @@ async function pollUntilBound({ cloudUrl, appInstallId, userDataDir }) {
// call on every launch — internal first-launch check makes subsequent calls
// a no-op. `shell` is electron's shell module, passed in to avoid this
// module needing to require electron at the top (keeps it test-friendly).
async function maybeRunFirstLaunchHandshake({ shell, userDataDir, isDev, isPackaged }) {
async function maybeRunFirstLaunchHandshake({
shell,
userDataDir,
isDev,
isPackaged,
platform = process.platform,
env = process.env,
homeDir = os.homedir(),
}) {
// Skip in dev to avoid spawning a browser tab on every `bash run.sh`.
// OPENSWARM_AFFILIATE_FORCE=1 lets us actually exercise the flow against
// a local landing page + local cloud during integration testing.
@@ -161,6 +259,23 @@ async function maybeRunFirstLaunchHandshake({ shell, userDataDir, isDev, isPacka
writeState(userDataDir, fresh);
const { landingUrl, cloudUrl } = urlsFromEnv();
const affiliateHash = findAffiliateHashFromInstaller({ platform, env, homeDir });
if (affiliateHash) {
const ref = await bindAffiliateHashOnce(cloudUrl, appInstallId, affiliateHash);
if (ref) {
const bound = {
...fresh,
ref,
ref_bound_at: Date.now(),
ref_bind_method: "affiliate_filename_hash",
};
writeState(userDataDir, bound);
console.log(`[affiliate] bound filename hash ref=${ref}; skipping welcome URL`);
return;
}
console.log("[affiliate] filename hash bind failed; falling back to welcome flow");
}
const welcomeUrl = `${landingUrl}/welcome?app_install_id=${encodeURIComponent(appInstallId)}`;
console.log(`[affiliate] first launch: opening ${welcomeUrl}`);
@@ -186,4 +301,7 @@ module.exports = {
_writeState: writeState,
_getStateFilePath: getStateFilePath,
_pollLookupOnce: pollLookupOnce,
_bindAffiliateHashOnce: bindAffiliateHashOnce,
_hashFromInstallerBasename: hashFromInstallerBasename,
_findAffiliateHashFromInstaller: findAffiliateHashFromInstaller,
};
+78 -2
View File
@@ -38,6 +38,7 @@ const affiliateTracking = require("./affiliateTracking");
function makeMockCloud() {
// Mirrors the install_tokens table.
const tokens = new Map();
const filenameHashes = new Map();
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
@@ -77,8 +78,23 @@ function makeMockCloud() {
if (req.method === "POST" && url.pathname === "/api/install/bind") {
const body = await readBody();
if (!body || typeof body.install_token !== "string" || typeof body.app_install_id !== "string") {
return send(400, { error: "install_token + app_install_id required" });
if (!body || typeof body.app_install_id !== "string") {
return send(400, { error: "app_install_id required" });
}
if (typeof body.affiliate_hash === "string" && !body.install_token) {
const ref = filenameHashes.get(body.affiliate_hash);
if (!ref) return send(404, { error: "affiliate_hash not found" });
const token = `filename_${crypto.randomBytes(24).toString("base64url")}`;
tokens.set(token, {
ref,
app_install_id: body.app_install_id,
bound_at: Date.now(),
expires_at: Date.now() + 24 * 60 * 60 * 1000,
});
return send(200, { ok: true, ref });
}
if (typeof body.install_token !== "string") {
return send(400, { error: "install_token required" });
}
const row = tokens.get(body.install_token);
if (!row) return send(404, { error: "not found" });
@@ -114,6 +130,7 @@ function makeMockCloud() {
resolve({
url: `http://127.0.0.1:${addr.port}`,
tokens,
filenameHashes,
close: () => new Promise((r) => server.close(r)),
});
});
@@ -236,6 +253,65 @@ test("first launch: opens welcome URL and binds ref via poll loop", async () =>
}
});
test("first launch: stamped AppImage hash binds before opening welcome URL", async () => {
const cloud = await makeMockCloud();
try {
const userDataDir = makeTempUserDataDir();
const shell = makeFakeShell();
const hash = "abcDEF1234567890_hash";
cloud.filenameHashes.set(hash, "filename-affiliate");
process.env.OPENSWARM_AFFILIATE_LANDING_URL = "https://landing.test";
process.env.OPENSWARM_AFFILIATE_CLOUD_URL = cloud.url;
await affiliateTracking.maybeRunFirstLaunchHandshake({
shell,
userDataDir,
isDev: false,
isPackaged: true,
platform: "linux",
env: { APPIMAGE: `/tmp/OpenSwarm-x64-${hash}.AppImage` },
});
assert.equal(shell.opened.length, 0, "filename hash bind skips welcome URL");
const state = readJson(path.join(userDataDir, "install.json"));
assert.equal(state.ref, "filename-affiliate");
assert.equal(state.ref_bind_method, "affiliate_filename_hash");
assert.ok(state.ref_bound_at > 0);
} finally {
await cloud.close();
}
});
test("filename parser accepts browser duplicate suffix", () => {
assert.equal(
affiliateTracking._hashFromInstallerBasename("OpenSwarm-arm64-abcDEF1234567890_hash (1).dmg"),
"abcDEF1234567890_hash",
);
});
test("filename parser keeps hyphens inside base64url affiliate hash", () => {
assert.equal(
affiliateTracking._hashFromInstallerBasename("OpenSwarm-arm64-abcDEF1234567890-hash.dmg"),
"abcDEF1234567890-hash",
);
});
test("download scan refuses ambiguous stamped installers", () => {
const userDataDir = makeTempUserDataDir();
const downloads = path.join(userDataDir, "Downloads");
fs.mkdirSync(downloads, { recursive: true });
fs.writeFileSync(path.join(downloads, "OpenSwarm-arm64-abcDEF1234567890_a.dmg"), "");
fs.writeFileSync(path.join(downloads, "OpenSwarm-arm64-abcDEF1234567890_b.dmg"), "");
const hash = affiliateTracking._findAffiliateHashFromInstaller({
platform: "darwin",
homeDir: userDataDir,
nowMs: Date.now(),
});
assert.equal(hash, null);
});
test("returning launch: no-op when ref already bound", async () => {
const cloud = await makeMockCloud();
try {
@@ -19,6 +19,7 @@ import { activateSignin, fetchSettings } from '@/shared/state/settingsSlice';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import { OPENSWARM_DEFAULT_PROXY_URL } from '@/shared/config';
import { report } from '@/shared/serviceClient';
import { getAffiliateAppInstallId } from '@/shared/affiliateInstall';
type Stage = 'choose' | 'email_form' | 'code_form';
@@ -46,13 +47,15 @@ export default function SignInDialog({ onClose }: { onClose: () => void }): JSX.
const cloudBase = proxyUrl.replace(/\/$/, '');
const onGoogle = () => {
const onGoogle = async () => {
report('signin', 'google_clicked');
const localPort = (window as any).__OPENSWARM_PORT__ || 8324;
const params = new URLSearchParams({
install_id: installId,
local_port: String(localPort),
});
const appInstallId = await getAffiliateAppInstallId();
if (appInstallId) params.set('app_install_id', appInstallId);
const startUrl = `${cloudBase}/api/auth/google/start?${params.toString()}`;
const api = (window as any).openswarm;
if (api?.openExternal) {
@@ -115,6 +118,7 @@ export default function SignInDialog({ onClose }: { onClose: () => void }): JSX.
try {
report('signin', 'email_verify_submitted');
const localPort = (window as any).__OPENSWARM_PORT__ || 8324;
const appInstallId = await getAffiliateAppInstallId();
const res = await fetch(`${cloudBase}/api/auth/email/verify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -122,6 +126,7 @@ export default function SignInDialog({ onClose }: { onClose: () => void }): JSX.
email: email.trim(),
code,
install_id: installId,
...(appInstallId ? { app_install_id: appInstallId } : {}),
local_port: localPort,
}),
});
@@ -137,6 +142,7 @@ export default function SignInDialog({ onClose }: { onClose: () => void }): JSX.
token: data.bearer,
email: data.user_email,
signin_method: 'email',
...(appInstallId ? { app_install_id: appInstallId } : {}),
}),
).unwrap();
} catch (err) {
@@ -8,6 +8,7 @@ import { signOut } from '@/shared/state/settingsSlice';
import { OPENSWARM_DEFAULT_PROXY_URL } from '@/shared/config';
import { useClaudeTokens } from '@/shared/styles/ThemeContext';
import SignInDialog from '@/app/components/overlays/SignInDialog';
import { getAffiliateAppInstallId } from '@/shared/affiliateInstall';
/** Account card at top of General tab; three states: signed in, paid-but-unlinked, or not signed in. */
const AccountCard: React.FC = () => {
@@ -43,13 +44,15 @@ const AccountCard: React.FC = () => {
}
};
const onSignIn = () => {
const onSignIn = async () => {
// Pass local_port so the bearer-handoff page POSTs to the right backend (Electron binds in 8324..8424).
const localPort = (window as any).__OPENSWARM_PORT__ || 8324;
const params = new URLSearchParams({
install_id: installId,
local_port: String(localPort),
});
const appInstallId = await getAffiliateAppInstallId();
if (appInstallId) params.set('app_install_id', appInstallId);
const startUrl = proxyUrl.replace(/\/$/, '') + '/api/auth/google/start?' + params.toString();
const api = (window as any).openswarm;
if (api?.openExternal) api.openExternal(startUrl);
+14
View File
@@ -0,0 +1,14 @@
const APP_INSTALL_ID_RE = /^[A-Za-z0-9_-]{8,128}$/;
export async function getAffiliateAppInstallId(): Promise<string | null> {
try {
const api = (window as any).openswarm;
const state = await api?.getInstallState?.();
const appInstallId = state && typeof state.app_install_id === 'string'
? state.app_install_id
: '';
return APP_INSTALL_ID_RE.test(appInstallId) ? appInstallId : null;
} catch {
return null;
}
}
+11 -5
View File
@@ -5,6 +5,7 @@ import { fetchModels } from '@/shared/state/modelsSlice';
import { fetchTools } from '@/shared/state/toolsSlice';
import { API_BASE } from '@/shared/config';
import { report } from '@/shared/serviceClient';
import { getAffiliateAppInstallId } from '@/shared/affiliateInstall';
/** Subscribe to openswarm:// auth/oauth deep-links from Electron main; no-op in browser. */
export function useDeepLink(): void {
@@ -14,7 +15,7 @@ export function useDeepLink(): void {
const api = (window as any).openswarm as OpenSwarmAPI | undefined;
if (!api) return;
const unsubscribe = api.onAuthUrl?.((rawUrl: string) => {
const unsubscribe = api.onAuthUrl?.(async (rawUrl: string) => {
try {
// openswarm://auth?token=...; signin=true => free sign-in, else Stripe activation.
const url = new URL(rawUrl);
@@ -34,11 +35,16 @@ export function useDeepLink(): void {
const expires = url.searchParams.get('expires');
if (isSignin) {
// 1.0.29 only ships Google sign-in; read for forward compat.
void signinMethodRaw;
report('signin', 'deep_link_received', { method: 'google' });
const signinMethod = signinMethodRaw === 'email' ? 'email' : 'google';
const appInstallId = url.searchParams.get('app_install_id') || (await getAffiliateAppInstallId());
report('signin', 'deep_link_received', { method: signinMethod });
dispatch(activateSignin({ token, signin_method: 'google', email }))
dispatch(activateSignin({
token,
signin_method: signinMethod,
email,
...(appInstallId ? { app_install_id: appInstallId } : {}),
}))
.unwrap()
.then((res) => {
report('signin', 'activated', { method: res.signin_method, plan: res.plan });
@@ -87,6 +87,7 @@ export interface ActivateSigninPayload {
token: string;
signin_method: 'google' | 'email';
email?: string | null;
app_install_id?: string | null;
}
export interface BrowseResult {
+1
View File
@@ -48,6 +48,7 @@ declare global {
onUpdateError: (cb: (message: string) => void) => () => void;
onWebviewNewWindow: (cb: (url: string, webContentsId: number) => void) => () => void;
openExternal: (url: string) => Promise<void>;
getInstallState?: () => Promise<{ app_install_id?: string; ref?: string | null; ref_bind_method?: string | null }>;
hardReset?: () => Promise<void>;
onAuthUrl?: (cb: (url: string) => void) => () => void;
onOauthClaim?: (cb: (url: string) => void) => () => void;