Skip to content

Commit fff3ab0

Browse files
Add Analytics on-device conversion bindings (#120)
1 parent 5b49d23 commit fff3ab0

3 files changed

Lines changed: 132 additions & 0 deletions

File tree

source/Firebase/Analytics/ApiDefinition.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,26 @@ interface Analytics
4545
[Export ("sessionIDWithCompletion:")]
4646
void SessionIdWithCompletion (Action<long, NSError> completion);
4747

48+
// +(void)initiateOnDeviceConversionMeasurementWithEmailAddress:(NSString * _Nonnull)emailAddress;
49+
[Static]
50+
[Export ("initiateOnDeviceConversionMeasurementWithEmailAddress:")]
51+
void InitiateOnDeviceConversionMeasurementWithEmailAddress (string emailAddress);
52+
53+
// +(void)initiateOnDeviceConversionMeasurementWithPhoneNumber:(NSString * _Nonnull)phoneNumber;
54+
[Static]
55+
[Export ("initiateOnDeviceConversionMeasurementWithPhoneNumber:")]
56+
void InitiateOnDeviceConversionMeasurementWithPhoneNumber (string phoneNumber);
57+
58+
// +(void)initiateOnDeviceConversionMeasurementWithHashedEmailAddress:(NSData * _Nonnull)hashedEmailAddress;
59+
[Static]
60+
[Export ("initiateOnDeviceConversionMeasurementWithHashedEmailAddress:")]
61+
void InitiateOnDeviceConversionMeasurementWithHashedEmailAddress (NSData hashedEmailAddress);
62+
63+
// +(void)initiateOnDeviceConversionMeasurementWithHashedPhoneNumber:(NSData * _Nonnull)hashedPhoneNumber;
64+
[Static]
65+
[Export ("initiateOnDeviceConversionMeasurementWithHashedPhoneNumber:")]
66+
void InitiateOnDeviceConversionMeasurementWithHashedPhoneNumber (NSData hashedPhoneNumber);
67+
4868
// + (nullable NSString *)appInstanceID;
4969
[Static]
5070
[NullAllowed]

tests/E2E/Firebase.Foundation/FirebaseFoundationE2E/FirebaseRuntimeDriftCases.cs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
using ObjCRuntime;
77
#endif
88

9+
#if ENABLE_RUNTIME_DRIFT_CASE_ANALYTICS_ONDEVICECONVERSION
10+
using Firebase.Analytics;
11+
using Foundation;
12+
using ObjCRuntime;
13+
#endif
14+
915
#if ENABLE_RUNTIME_DRIFT_CASE_DATABASE_SERVERVALUE_INCREMENT
1016
using Firebase.Database;
1117
using Foundation;
@@ -189,6 +195,106 @@ void OnMarshalObjectiveCException(object? sender, MarshalObjectiveCExceptionEven
189195
}
190196
#endif
191197

198+
#if ENABLE_RUNTIME_DRIFT_CASE_ANALYTICS_ONDEVICECONVERSION
199+
static Task<string> VerifyAnalyticsOnDeviceConversionAsync()
200+
{
201+
var expectedSignatures = new (string MethodName, Type[] ParameterTypes, string Selector)[]
202+
{
203+
(
204+
nameof(Analytics.InitiateOnDeviceConversionMeasurementWithEmailAddress),
205+
new[] { typeof(string) },
206+
"initiateOnDeviceConversionMeasurementWithEmailAddress:"),
207+
(
208+
nameof(Analytics.InitiateOnDeviceConversionMeasurementWithPhoneNumber),
209+
new[] { typeof(string) },
210+
"initiateOnDeviceConversionMeasurementWithPhoneNumber:"),
211+
(
212+
nameof(Analytics.InitiateOnDeviceConversionMeasurementWithHashedEmailAddress),
213+
new[] { typeof(NSData) },
214+
"initiateOnDeviceConversionMeasurementWithHashedEmailAddress:"),
215+
(
216+
nameof(Analytics.InitiateOnDeviceConversionMeasurementWithHashedPhoneNumber),
217+
new[] { typeof(NSData) },
218+
"initiateOnDeviceConversionMeasurementWithHashedPhoneNumber:"),
219+
};
220+
221+
foreach (var (methodName, parameterTypes, selector) in expectedSignatures)
222+
{
223+
var signature = typeof(Analytics).GetMethod(
224+
methodName,
225+
BindingFlags.Static | BindingFlags.Public,
226+
binder: null,
227+
types: parameterTypes,
228+
modifiers: null);
229+
if (signature is null)
230+
{
231+
throw new InvalidOperationException(
232+
$"Expected managed API '{methodName}({string.Join(", ", parameterTypes.Select(type => type.FullName))})' was not found for selector '{selector}'.");
233+
}
234+
}
235+
236+
var marshaledExceptionCaptured = false;
237+
MarshalObjectiveCExceptionMode? marshaledExceptionMode = null;
238+
239+
void OnMarshalObjectiveCException(object? sender, MarshalObjectiveCExceptionEventArgs args)
240+
{
241+
marshaledExceptionCaptured = true;
242+
marshaledExceptionMode ??= args.ExceptionMode;
243+
}
244+
245+
Runtime.MarshalObjectiveCException += OnMarshalObjectiveCException;
246+
try
247+
{
248+
using var hashedEmailAddress = NSData.FromArray(new byte[]
249+
{
250+
0x9a, 0x79, 0x2f, 0x3d, 0xa4, 0x18, 0x7e, 0x1b,
251+
0x02, 0xb7, 0x83, 0xfd, 0x0b, 0x41, 0x77, 0x57,
252+
0x97, 0x7a, 0x2a, 0xaf, 0x62, 0x94, 0x6d, 0x12,
253+
0x9d, 0x4c, 0xd2, 0x5a, 0x73, 0xb1, 0x3f, 0x31,
254+
});
255+
using var hashedPhoneNumber = NSData.FromArray(new byte[]
256+
{
257+
0x2d, 0x71, 0x16, 0x42, 0xb7, 0x26, 0xb0, 0x44,
258+
0x01, 0x62, 0x7c, 0xa9, 0xfb, 0xac, 0x32, 0xf5,
259+
0xc8, 0x53, 0x0f, 0x8d, 0x89, 0xc4, 0x6c, 0x2e,
260+
0x42, 0xb8, 0x6e, 0xfd, 0xb0, 0x33, 0x84, 0xa8,
261+
});
262+
263+
try
264+
{
265+
Analytics.InitiateOnDeviceConversionMeasurementWithEmailAddress("codex@example.com");
266+
Analytics.InitiateOnDeviceConversionMeasurementWithPhoneNumber("+15555550100");
267+
Analytics.InitiateOnDeviceConversionMeasurementWithHashedEmailAddress(hashedEmailAddress);
268+
Analytics.InitiateOnDeviceConversionMeasurementWithHashedPhoneNumber(hashedPhoneNumber);
269+
}
270+
catch (ObjCException ex)
271+
{
272+
throw new InvalidOperationException(
273+
"Analytics on-device conversion selectors should not throw after the missing bindings are added, " +
274+
$"but observed {ex.GetType().FullName}. " +
275+
$"String argument type: {typeof(string).FullName}. Hashed argument type: {typeof(NSData).FullName}. " +
276+
$"Marshal mode: {FormatDetail(marshaledExceptionMode?.ToString())}.",
277+
ex);
278+
}
279+
280+
if (marshaledExceptionCaptured)
281+
{
282+
throw new InvalidOperationException(
283+
"Analytics on-device conversion selectors completed, but Runtime.MarshalObjectiveCException captured an unexpected Objective-C exception. " +
284+
$"Marshal mode: {FormatDetail(marshaledExceptionMode?.ToString())}.");
285+
}
286+
287+
return Task.FromResult(
288+
"Analytics on-device conversion selectors completed without ObjC exception. " +
289+
$"String argument type: {typeof(string).FullName}. Hashed argument type: {typeof(NSData).FullName}.");
290+
}
291+
finally
292+
{
293+
Runtime.MarshalObjectiveCException -= OnMarshalObjectiveCException;
294+
}
295+
}
296+
#endif
297+
192298
#if ENABLE_RUNTIME_DRIFT_CASE_DATABASE_SERVERVALUE_INCREMENT
193299
static Task<string> VerifyDatabaseServerValueIncrementAsync()
194300
{

tests/E2E/Firebase.Foundation/runtime-drift-cases.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
"bindingPackage": "AdamE.Firebase.iOS.Analytics",
77
"packages": []
88
},
9+
{
10+
"id": "analytics-ondeviceconversion",
11+
"method": "VerifyAnalyticsOnDeviceConversionAsync",
12+
"bindingPackage": "AdamE.Firebase.iOS.Analytics",
13+
"packages": []
14+
},
915
{
1016
"id": "database-servervalue-increment",
1117
"method": "VerifyDatabaseServerValueIncrementAsync",

0 commit comments

Comments
 (0)