Skip to content

Commit 749d961

Browse files
wip - permutive
1 parent e4f7fed commit 749d961

File tree

3 files changed

+190
-4
lines changed

3 files changed

+190
-4
lines changed

crates/common/src/permutive_proxy.rs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::error::TrustedServerError;
1212
use crate::settings::Settings;
1313

1414
const PERMUTIVE_API_BASE: &str = "https://api.permutive.com";
15+
const PERMUTIVE_SECURE_SIGNALS_BASE: &str = "https://secure-signals.permutive.app";
1516

1617
/// Handles transparent proxying of Permutive API requests.
1718
///
@@ -93,6 +94,86 @@ pub async fn handle_permutive_api_proxy(
9394
Ok(permutive_response)
9495
}
9596

97+
/// Handles transparent proxying of Permutive Secure Signals requests.
98+
///
99+
/// This function:
100+
/// 1. Extracts the path after `/permutive/secure-signal/`
101+
/// 2. Preserves query parameters
102+
/// 3. Copies request headers and body
103+
/// 4. Forwards to `secure-signals.permutive.app`
104+
/// 5. Returns response transparently
105+
///
106+
/// # Example Request Flow
107+
///
108+
/// ```text
109+
/// Browser: GET /permutive/secure-signal/data/v1/cohorts
110+
/// ↓
111+
/// Trusted Server processes and forwards:
112+
/// ↓
113+
/// Permutive: GET https://secure-signals.permutive.app/data/v1/cohorts
114+
/// ```
115+
///
116+
/// # Errors
117+
///
118+
/// Returns a [`TrustedServerError`] if:
119+
/// - Path extraction fails
120+
/// - Backend communication fails
121+
/// - Request forwarding fails
122+
pub async fn handle_permutive_secure_signals_proxy(
123+
_settings: &Settings,
124+
mut req: Request,
125+
) -> Result<Response, Report<TrustedServerError>> {
126+
let original_path = req.get_path();
127+
let method = req.get_method();
128+
129+
log::info!(
130+
"Proxying Permutive Secure Signals request: {} {}",
131+
method,
132+
original_path
133+
);
134+
135+
// Extract the path after /permutive/secure-signal
136+
let signals_path = original_path
137+
.strip_prefix("/permutive/secure-signal")
138+
.ok_or_else(|| TrustedServerError::PermutiveApi {
139+
message: format!("Invalid Permutive Secure Signals path: {}", original_path),
140+
})?;
141+
142+
// Build the full Permutive Secure Signals URL with query parameters
143+
let permutive_url = build_secure_signals_url(signals_path, &req)?;
144+
145+
log::info!("Forwarding to Permutive Secure Signals: {}", permutive_url);
146+
147+
// Create new request to Permutive
148+
let mut permutive_req = Request::new(method.clone(), &permutive_url);
149+
150+
// Copy relevant headers
151+
copy_request_headers(&req, &mut permutive_req);
152+
153+
// Copy body for POST requests
154+
if has_body(method) {
155+
let body = req.take_body();
156+
permutive_req.set_body(body);
157+
}
158+
159+
// Get backend and forward request
160+
let backend_name = ensure_backend_from_url(PERMUTIVE_SECURE_SIGNALS_BASE)?;
161+
162+
let permutive_response = permutive_req
163+
.send(backend_name)
164+
.change_context(TrustedServerError::PermutiveApi {
165+
message: format!("Failed to forward request to {}", permutive_url),
166+
})?;
167+
168+
log::info!(
169+
"Permutive Secure Signals responded with status: {}",
170+
permutive_response.get_status()
171+
);
172+
173+
// Return response transparently
174+
Ok(permutive_response)
175+
}
176+
96177
/// Builds the full Permutive API URL including query parameters.
97178
fn build_permutive_url(
98179
api_path: &str,
@@ -111,6 +192,24 @@ fn build_permutive_url(
111192
Ok(url)
112193
}
113194

195+
/// Builds the full Permutive Secure Signals URL including query parameters.
196+
fn build_secure_signals_url(
197+
signals_path: &str,
198+
req: &Request,
199+
) -> Result<String, Report<TrustedServerError>> {
200+
// Get query string if present
201+
let query = req
202+
.get_url()
203+
.query()
204+
.map(|q| format!("?{}", q))
205+
.unwrap_or_default();
206+
207+
// Build full URL
208+
let url = format!("{}{}{}", PERMUTIVE_SECURE_SIGNALS_BASE, signals_path, query);
209+
210+
Ok(url)
211+
}
212+
114213
/// Copies relevant headers from the original request to the Permutive request.
115214
fn copy_request_headers(from: &Request, to: &mut Request) {
116215
// Headers that should be forwarded to Permutive
@@ -193,4 +292,38 @@ mod tests {
193292
assert!(!has_body(&Method::HEAD));
194293
assert!(!has_body(&Method::OPTIONS));
195294
}
295+
296+
#[test]
297+
fn test_secure_signals_path_extraction() {
298+
let test_cases = vec![
299+
("/permutive/secure-signal/data/v1/cohorts", "/data/v1/cohorts"),
300+
("/permutive/secure-signal/v1/track", "/v1/track"),
301+
("/permutive/secure-signal/", "/"),
302+
("/permutive/secure-signal", ""),
303+
];
304+
305+
for (input, expected) in test_cases {
306+
let result = input.strip_prefix("/permutive/secure-signal").unwrap_or("");
307+
assert_eq!(result, expected, "Failed for input: {}", input);
308+
}
309+
}
310+
311+
#[test]
312+
fn test_secure_signals_url_building() {
313+
let signals_path = "/data/v1/cohorts";
314+
let expected = "https://secure-signals.permutive.app/data/v1/cohorts";
315+
316+
let url = format!("{}{}", PERMUTIVE_SECURE_SIGNALS_BASE, signals_path);
317+
assert_eq!(url, expected);
318+
}
319+
320+
#[test]
321+
fn test_secure_signals_url_building_with_query() {
322+
let signals_path = "/data/v1/cohorts";
323+
let query = "?key=123";
324+
let expected = "https://secure-signals.permutive.app/data/v1/cohorts?key=123";
325+
326+
let url = format!("{}{}{}", PERMUTIVE_SECURE_SIGNALS_BASE, signals_path, query);
327+
assert_eq!(url, expected);
328+
}
196329
}

crates/fastly/src/main.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ use log_fastly::Logger;
44

55
use trusted_server_common::ad::{handle_server_ad, handle_server_ad_get};
66
use trusted_server_common::auth::enforce_basic_auth;
7-
use trusted_server_common::permutive_proxy::handle_permutive_api_proxy;
7+
use trusted_server_common::permutive_proxy::{
8+
handle_permutive_api_proxy, handle_permutive_secure_signals_proxy,
9+
};
810
use trusted_server_common::permutive_sdk::handle_permutive_sdk;
911
use trusted_server_common::proxy::{
1012
handle_first_party_click, handle_first_party_proxy, handle_first_party_proxy_rebuild,
@@ -54,6 +56,11 @@ async fn route_request(settings: Settings, req: Request) -> Result<Response, Err
5456
handle_permutive_api_proxy(&settings, req).await
5557
}
5658

59+
// Permutive Secure Signals proxy - /permutive/secure-signal/* → secure-signals.permutive.app/*
60+
(&Method::GET | &Method::POST, path) if path.starts_with("/permutive/secure-signal/") => {
61+
handle_permutive_secure_signals_proxy(&settings, req).await
62+
}
63+
5764
// Serve the Permutive SDK (proxied from Permutive CDN)
5865
(&Method::GET, path) if path.starts_with("/static/tsjs=tsjs-permutive") => {
5966
handle_permutive_sdk(&settings, req).await

crates/js/lib/src/ext/permutive.ts

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,54 @@
11
import { log } from '../core/log';
22

3+
declare const permutive: {
4+
config: {
5+
advertiserApiVersion: string;
6+
apiHost: string;
7+
apiKey: string;
8+
apiProtocol: string;
9+
apiVersion: string;
10+
cdnBaseUrl: string;
11+
cdnProtocol: string;
12+
classificationModelsApiVersion: string;
13+
consentRequired: boolean;
14+
cookieDomain: string;
15+
cookieExpiry: string;
16+
cookieName: string;
17+
environment: string;
18+
eventsCacheLimitBytes: number;
19+
eventsTTLInDays: number | null;
20+
localStorageDebouncedKeys: string[];
21+
localStorageWriteDelay: number;
22+
localStorageWriteMaxDelay: number;
23+
loggingEnabled: boolean;
24+
metricsSamplingPercentage: number;
25+
permutiveDataMiscKey: string;
26+
permutiveDataQueriesKey: string;
27+
prebidAuctionsRandomDownsamplingThreshold: number;
28+
pxidHost: string;
29+
requestTimeout: number;
30+
sdkErrorsApiVersion: string;
31+
sdkType: string;
32+
secureSignalsApiHost: string;
33+
segmentSyncApiHost: string;
34+
sendClientErrors: boolean;
35+
stateNamespace: string;
36+
tracingEnabled: boolean;
37+
viewId: string;
38+
watson: {
39+
enabled: boolean;
40+
};
41+
windowKey: string;
42+
workspaceId: string;
43+
};
44+
};
345

446
export function installPermutiveShim() {
5-
//@ts-ignore
6-
permutive.config.apiHost = window.location.host + "/permutive/api";
7-
}
847

48+
49+
50+
permutive.config.apiHost = window.location.host + '/permutive/api';
51+
permutive.config.apiProtocol = window.location.protocol === "https:" ? "https" : "http";
52+
53+
permutive.config.secureSignalsApiHost = window.location.host + '/permutive/secure-signal';
54+
}

0 commit comments

Comments
 (0)