Skip to content

Commit 2c80798

Browse files
authored
Add source/dest middleware equivalents for ObjC (#204)
* Add source/dest middleware equivalents for ObjC * Added ifdef for linux * Fixed xcodeproj
1 parent e75afb2 commit 2c80798

File tree

4 files changed

+137
-0
lines changed

4 files changed

+137
-0
lines changed

Examples/apps/ObjCExample/ObjCExample/AppDelegate.m

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,22 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
2828

2929
[self.analytics flush];
3030

31+
[self.analytics addSourceMiddleware:^NSDictionary<NSString *,id> * _Nullable(NSDictionary<NSString *,id> * _Nullable event) {
32+
// drop all events named booya
33+
NSString *eventType = event[@"type"];
34+
if ([eventType isEqualToString:@"track"]) {
35+
NSString *eventName = event[@"event"];
36+
if ([eventName isEqualToString:@"booya"]) {
37+
return nil;
38+
}
39+
}
40+
return event;
41+
}];
42+
43+
dispatch_async(dispatch_get_main_queue(), ^{
44+
[self.analytics track:@"booya"];
45+
});
46+
3147
return YES;
3248
}
3349

Segment.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
4663C729267A799100ADDD1A /* QueueTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4663C728267A799100ADDD1A /* QueueTimer.swift */; };
3838
466EC2CE28FB7D5D001B384E /* OutputFileStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 466EC2CD28FB7D5D001B384E /* OutputFileStream.swift */; };
3939
4697A238290341EF00FAE14F /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4697A237290341EF00FAE14F /* Errors.swift */; };
40+
469891A329C11A890021B552 /* ObjCPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 469891A229C11A890021B552 /* ObjCPlugin.swift */; };
4041
46A018C225E5857D00F9CCD8 /* Context.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A018C125E5857D00F9CCD8 /* Context.swift */; };
4142
46A018D425E6C9C200F9CCD8 /* LinuxUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A018D325E6C9C200F9CCD8 /* LinuxUtils.swift */; };
4243
46A018DA25E97FDF00F9CCD8 /* AppleUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46A018D925E97FDF00F9CCD8 /* AppleUtils.swift */; };
@@ -124,6 +125,7 @@
124125
4663C728267A799100ADDD1A /* QueueTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueTimer.swift; sourceTree = "<group>"; };
125126
466EC2CD28FB7D5D001B384E /* OutputFileStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutputFileStream.swift; sourceTree = "<group>"; };
126127
4697A237290341EF00FAE14F /* Errors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = "<group>"; };
128+
469891A229C11A890021B552 /* ObjCPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjCPlugin.swift; sourceTree = "<group>"; };
127129
46A018C125E5857D00F9CCD8 /* Context.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Context.swift; sourceTree = "<group>"; };
128130
46A018D325E6C9C200F9CCD8 /* LinuxUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinuxUtils.swift; sourceTree = "<group>"; };
129131
46A018D925E97FDF00F9CCD8 /* AppleUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleUtils.swift; sourceTree = "<group>"; };
@@ -220,6 +222,7 @@
220222
children = (
221223
46F7485B26C718710042798E /* ObjCAnalytics.swift */,
222224
46F7485C26C718710042798E /* ObjCConfiguration.swift */,
225+
469891A229C11A890021B552 /* ObjCPlugin.swift */,
223226
);
224227
path = ObjC;
225228
sourceTree = "<group>";
@@ -540,6 +543,7 @@
540543
46210836260BBEE400EBC4A8 /* DeviceToken.swift in Sources */,
541544
9692724E25A4E5B7009B5298 /* Startup.swift in Sources */,
542545
4663C729267A799100ADDD1A /* QueueTimer.swift in Sources */,
546+
469891A329C11A890021B552 /* ObjCPlugin.swift in Sources */,
543547
46FE4C9C25A3F41C003A7362 /* LinuxLifecycleMonitor.swift in Sources */,
544548
460227422612987300A9E913 /* watchOSLifecycleEvents.swift in Sources */,
545549
46F7485E26C718710042798E /* ObjCConfiguration.swift in Sources */,

Sources/Segment/ObjC/ObjCAnalytics.swift

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,63 @@ extension ObjCAnalytics {
129129
}
130130
}
131131

132+
// MARK: - ObjC Plugin Functionality
133+
134+
@objc
135+
extension ObjCAnalytics {
136+
/// This method allows you to add middleware to an Analytics instance, similar to Analytics-iOS.
137+
/// However, it is **strongly encouraged** that Enrichments/Plugins/Middlewares be written in swift
138+
/// to avoid the overhead of type conversion back and forth. This exists solely for compatibility
139+
/// purposes.
140+
///
141+
/// Example:
142+
/// [self.analytics addSourceMiddleware:^NSDictionary<NSString *,id> * _Nullable(NSDictionary<NSString *,id> * _Nullable event) {
143+
/// // drop all events named booya
144+
/// NSString *eventType = event[@"type"];
145+
/// if ([eventType isEqualToString:@"track"]) {
146+
/// NSString *eventName = event[@"event"];
147+
/// if ([eventName isEqualToString:@"booya"]) {
148+
/// return nil;
149+
/// }
150+
/// }
151+
/// return event;
152+
/// }];
153+
///
154+
/// - Parameter middleware: The middleware to execute at the source level.
155+
@objc(addSourceMiddleware:)
156+
public func addSourceMiddleware(middleware: @escaping ((_ event: [String: Any]?) -> [String: Any]?)) {
157+
analytics.add(plugin: ObjCShimPlugin(middleware: middleware))
158+
}
159+
160+
/// This method allows you to add middleware to an Analytics instance, similar to Analytics-iOS.
161+
/// However, it is **strongly encouraged** that Enrichments/Plugins/Middlewares be written in swift
162+
/// to avoid the overhead of type conversion back and forth. This exists solely for compatibility
163+
/// purposes.
164+
///
165+
/// Example:
166+
/// [self.analytics addDestinationMiddleware:^NSDictionary<NSString *,id> * _Nullable(NSDictionary<NSString *,id> * _Nullable event) {
167+
/// // drop all events named booya on the amplitude destination
168+
/// NSString *eventType = event[@"type"];
169+
/// if ([eventType isEqualToString:@"track"]) {
170+
/// NSString *eventName = event[@"event"];
171+
/// if ([eventName isEqualToString:@"booya"]) {
172+
/// return nil;
173+
/// }
174+
/// }
175+
/// return event;
176+
/// }, forKey: @"Amplitude"];
177+
///
178+
/// - Parameters:
179+
/// - middleware: The middleware to execute at the source level.
180+
/// - destinationKey: A string value representing the destination. ie: @"Amplitude"
181+
@objc(addDestinationMiddleware:forKey:)
182+
public func addDestinationMiddleware(middleware: @escaping ((_ event: [String: Any]?) -> [String: Any]?), destinationKey: String) {
183+
// couldn't find the destination they wanted
184+
guard let dest = analytics.find(key: destinationKey) else { return }
185+
_ = dest.add(plugin: ObjCShimPlugin(middleware: middleware))
186+
}
187+
}
188+
132189
// MARK: - ObjC Peripheral Functionality
133190

134191
@objc

Sources/Segment/ObjC/ObjCPlugin.swift

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//
2+
// ObjCPlugin.swift
3+
//
4+
//
5+
// Created by Brandon Sneed on 3/14/23.
6+
//
7+
8+
#if !os(Linux)
9+
10+
import Foundation
11+
import Sovran
12+
13+
internal class ObjCShimPlugin: Plugin, Subscriber {
14+
var type: PluginType = .enrichment
15+
var analytics: Analytics? = nil
16+
var executionBlock: (([String: Any]?) -> [String: Any]?)? = nil
17+
18+
required init(middleware: @escaping ([String: Any]?) -> [String: Any]?) {
19+
executionBlock = middleware
20+
}
21+
22+
func execute<T>(event: T?) -> T? where T : RawEvent {
23+
// is our event actually valid?
24+
guard let event = event else { return event }
25+
// do we actually have an execution block?
26+
guard let executionBlock = executionBlock else { return event }
27+
// can we conver this to a JSON dictionary?
28+
guard let dictEvent = try? JSON(with: event).dictionaryValue else { return event }
29+
// is it valid json?
30+
guard JSONSerialization.isValidJSONObject(dictEvent as Any) == true else { return event }
31+
// run the execution block, a nil result tells us to drop the event.
32+
guard let result = executionBlock(dictEvent) else { return nil }
33+
34+
if let jsonData = try? JSONSerialization.data(withJSONObject: result, options: .prettyPrinted) {
35+
let decoder = JSONDecoder()
36+
var newEvent: RawEvent? = nil
37+
switch event {
38+
case is IdentifyEvent:
39+
newEvent = try? decoder.decode(IdentifyEvent.self, from: jsonData)
40+
case is TrackEvent:
41+
newEvent = try? decoder.decode(TrackEvent.self, from: jsonData)
42+
case is ScreenEvent:
43+
newEvent = try? decoder.decode(ScreenEvent.self, from: jsonData)
44+
case is AliasEvent:
45+
newEvent = try? decoder.decode(AliasEvent.self, from: jsonData)
46+
case is GroupEvent:
47+
newEvent = try? decoder.decode(GroupEvent.self, from: jsonData)
48+
default:
49+
break
50+
}
51+
// return the decoded event ...
52+
return newEvent as? T
53+
} else {
54+
// we weren't able to serialize, so return the original event.
55+
return event
56+
}
57+
}
58+
}
59+
60+
#endif

0 commit comments

Comments
 (0)