Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,4 @@ Examples/BasicExample/.DS_Store
Sources/.DS_Store

Sources/AnalyticsLive/.DS_Store
.DS_Store
4 changes: 2 additions & 2 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 5.7
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand All @@ -18,7 +18,7 @@ let package = Package(
targets: ["AnalyticsLive"])
],
dependencies: [
.package(url: "https://github.com/segmentio/analytics-swift.git", from: "1.7.2"),
.package(url: "https://github.com/segmentio/analytics-swift.git", from: "1.8.0"),
.package(url: "https://github.com/segmentio/substrata-swift.git", from: "2.0.11"),
//.package(path: "../substrata-swift")
],
Expand All @@ -37,9 +37,13 @@ let package = Package(
],
resources: [
.copy("TestHelpers/filterSettings.json"),
.copy("TestHelpers/badSettings.json"),
.copy("TestHelpers/testbundle.js"),
.copy("TestHelpers/addliveplugin.js"),
.copy("TestHelpers/MyEdgeFunctions.js"),
.copy("TestHelpers/badtest.js"),
.copy("TestHelpers/runtimeBundle.js"),
.copy("TestHelpers/runtimeBundleNoProcess.js")
]),
]
)
183 changes: 106 additions & 77 deletions Sources/AnalyticsLive/LivePlugins/AnalyticsJS.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// File.swift
//
//
//
// Created by Brandon Sneed on 5/5/22.
//
Expand All @@ -14,8 +14,6 @@ public class AnalyticsJS: JSExport {

internal weak var analytics: Analytics?

internal var addedPlugins = [(String?, LivePlugin)]()

internal lazy var currentLivePluginVersion: String? = {
return LivePlugins.currentLivePluginVersion()
}()
Expand All @@ -36,60 +34,95 @@ public class AnalyticsJS: JSExport {
Self.existingInstances.removeAll(where: { $0 === self.analytics })
}


static func insertOrigin(event: RawEvent?, data: [String: Any]) -> RawEvent? {
guard var working = event else { return event }
if let newContext = try? working.context?.add(value: data, forKey: "__eventOrigin") {
if let newContext = try? working.context?.add(
value: data,
forKey: "__eventOrigin"
) {
working.context = newContext
}
return working
}

internal func originMarkerEnrichment(event: RawEvent?) -> RawEvent? {
return Self.insertOrigin(event: event, data: [
"type": "signals",
"type": "js",
"version": currentLivePluginVersion ?? ""
])
}

private func contextEnrichment(with context: [String: Any]?) -> EnrichmentClosure {
return { event in
guard var working = event, let context = context else {
return event
}

// Get existing context as dictionary and merge with new context
var mergedContext = working.context?.dictionaryValue ?? [:]
mergedContext.merge(context) { _, new in new }

do {
working.context = try JSON(mergedContext)
} catch {
// If JSON creation fails, return original event
return event
}
return working
}
}

private func enrichments(with context: [String: Any]?) -> [EnrichmentClosure] {
var enrichments = [originMarkerEnrichment]
if let context = context {
enrichments.append(contextEnrichment(with: context))
}
return enrichments
}

internal func setupExports() {
exportProperty(named: "traits") { [weak self] in
guard let self else { return nil }
let traits: [String: Any]? = analytics?.traits()
guard let self, let analytics = self.analytics else { return nil }
let traits: [String: Any]? = analytics.traits()
return traits as? JSConvertible
}

exportProperty(named: "userId") { [weak self] in
guard let self else { return nil }
return analytics?.userId
return self?.analytics?.userId
}

exportProperty(named: "anonymousId") { [weak self] in
guard let self else { return nil }
return analytics?.anonymousId
return self?.analytics?.anonymousId
}

exportMethod(named: "track") { [weak self] in self?.track(args: $0) }
exportMethod(named: "identify") { [weak self] in self?.identify(args: $0) }
exportMethod(named: "identify") {
[weak self] in self?.identify(args: $0)
}
exportMethod(named: "screen") { [weak self] in self?.screen(args: $0) }
exportMethod(named: "group") { [weak self] in self?.group(args: $0) }
exportMethod(named: "alias") { [weak self] in self?.alias(args: $0) }
exportMethod(named: "add") { [weak self] in self?.add(args: $0) }
exportMethod(named: "flush") { [weak self] in self?.flush(args: $0) }
exportMethod(named: "reset") { [weak self] in self?.reset(args: $0) }
exportMethod(named: "removeLivePlugins") { [weak self] in self?.removeLivePlugins(args: $0) }
}

public override func construct(args: [JSConvertible?]) {
if let writeKey = args.typed(as: String.self, index: 0) {
let existing = Self.existingInstances.first(where: { $0.writeKey == writeKey })
let existing = Self.existingInstances.first(
where: { $0.writeKey == writeKey
})
if let existing {
self.analytics = existing
return
} else {
// we do some work on user-created instances to avoid multiple instances of the same write key
// by just attaching whatever existing instance that might exist with that write key.
let createdAnalytics = Analytics(configuration: Configuration(writeKey: writeKey)
.trackApplicationLifecycleEvents(false)
let createdAnalytics = Analytics(
configuration: Configuration(
writeKey: writeKey
)
.setTrackedApplicationLifecycleEvents(.none)
)
Self.existingInstances.append(createdAnalytics)
self.analytics = createdAnalytics
Expand All @@ -99,113 +132,109 @@ public class AnalyticsJS: JSExport {

public func track(args: [JSConvertible?]) -> JSConvertible? {
guard let analytics else { return nil }
guard let name = args.typed(as: String.self, index: 0) else { return nil }
let properties = args.typed(as: Dictionary.self, index: 1)

let addEventOrigin: EnrichmentClosure = { [weak self] event in
return Self.insertOrigin(event: event, data: [
"type": "signals",
"version": self?.currentLivePluginVersion ?? ""
])
guard let name = args.typed(as: String.self, index: 0) else {
return nil
}
let properties = args.typed(as: Dictionary.self, index: 1)
let context = args.typed(as: Dictionary.self, index: 2)

analytics.track(name: name, properties: properties, enrichments: [addEventOrigin])
analytics
.track(
name: name,
properties: properties,
enrichments: enrichments(with: context)
)
return nil
}

public func identify(args: [JSConvertible?]) -> JSConvertible? {
guard let analytics else { return nil }
guard let userId = args.typed(as: String.self, index: 0) else { return nil }
guard let userId = args.typed(as: String.self, index: 0) else {
return nil
}
let traits = args.typed(as: Dictionary.self, index: 1)
analytics.identify(userId: userId, traits: traits, enrichments: [originMarkerEnrichment])
let context = args.typed(as: Dictionary.self, index: 2)
analytics
.identify(
userId: userId,
traits: traits,
enrichments: enrichments(with: context)
)
return nil
}

public func screen(args: [JSConvertible?]) -> JSConvertible? {
guard let analytics else { return nil }
guard let title = args.typed(as: String.self, index: 0) else { return nil }
guard let title = args.typed(as: String.self, index: 0) else {
return nil
}
let category = args.typed(as: String.self, index: 1)
let properties = args.typed(as: Dictionary.self, index: 2)
analytics.screen(title: title, category: category, properties: properties, enrichments: [originMarkerEnrichment])
let context = args.typed(as: Dictionary.self, index: 3)
analytics
.screen(
title: title,
category: category,
properties: properties,
enrichments: enrichments(with: context)
)
return nil

}

public func group(args: [JSConvertible?]) -> JSConvertible? {
guard let analytics else { return nil }
guard let groupId = args.typed(as: String.self, index: 0) else { return nil }
guard let groupId = args.typed(as: String.self, index: 0) else {
return nil
}
let traits = args.typed(as: Dictionary.self, index: 1)
analytics.group(groupId: groupId, traits: traits, enrichments: [originMarkerEnrichment])
return nil
}

public func alias(args: [JSConvertible?]) -> JSConvertible? {
guard let analytics else { return nil }
guard let newId = args.typed(as: String.self, index: 0) else { return nil }
analytics.alias(newId: newId, enrichments: [originMarkerEnrichment])
let context = args.typed(as: Dictionary.self, index: 2)
analytics
.group(
groupId: groupId,
traits: traits,
enrichments: enrichments(with: context)
)
return nil

}

public func flush(args: [JSConvertible?]) -> JSConvertible? {
guard let analytics else { return nil }
analytics.flush()
return nil

}

public func reset(args: [JSConvertible?]) -> JSConvertible? {
guard let analytics else { return nil }
analytics.reset()
return nil

}

public func add(args: [JSConvertible?]) -> JSConvertible? {
var result = false

guard let analytics else { return result }
guard let plugin = args.typed(as: JSClass.self, index: 0) else { return result }
guard let analytics else { return nil }
guard let plugin = args.typed(as: JSClass.self, index: 0) else {
return false
}

let type = plugin["type"]?.typed(as: Int.self)
let destination = plugin["destination"]?.typed(as: String.self)

guard let type = type else { return result }
guard let type = type else { return false }
guard let pluginType = PluginType(rawValue: type) else { return false }

guard let pluginType = PluginType(rawValue: type) else { return result }
let edgeFn = LivePlugin(jsPlugin: plugin, type: pluginType)

if let dest = destination {
// we have a destination specified, so add it there
if let d = analytics.find(key: dest) {
//DispatchQueue.main.async {
_ = d.add(plugin: edgeFn)
//}
result = true
self.addedPlugins.append((dest, edgeFn))
}
guard let d = analytics.find(key: dest) else { return false }
_ = d.add(plugin: edgeFn)
return true
} else {
//DispatchQueue.main.async {
_ = analytics.add(plugin: edgeFn)
//}
result = true
self.addedPlugins.append((nil, edgeFn))
_ = analytics.add(plugin: edgeFn)
return true
}
return result
}

public func removeLivePlugins(args: [JSConvertible?]) -> JSConvertible? {
guard let analytics else { return nil }

for tuple in self.addedPlugins {
let (dest, p) = tuple
if let dst = dest {
// Remove from destination
if let d = analytics.find(key: dst) {
d.remove(plugin: p)
}
} else {
// Remove from main timeline
analytics.remove(plugin: p)
}
}
self.addedPlugins.removeAll()
return nil
}
}
2 changes: 1 addition & 1 deletion Sources/AnalyticsLive/LivePlugins/Bundler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ internal class Bundler {
static var sessionConfig = URLSessionConfiguration.default

class func getLocalBundleFolderURL() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let paths = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
var result = paths[0]
result.appendPathComponent("segmentJSBundles")
if let identifier = Bundle.main.bundleIdentifier {
Expand Down
8 changes: 4 additions & 4 deletions Sources/AnalyticsLive/LivePlugins/EmbeddedJS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public struct EmbeddedJS {
public static let edgeFnBaseSetupScript = """
class LivePlugin {
constructor(type, destination) {
console.log("js: LivePlugin.constructor() called");
//console.log("js: LivePlugin.constructor() called");
this._type = type;
this._destination = destination;
}
Expand All @@ -39,7 +39,7 @@ public struct EmbeddedJS {
update(settings, type) { }

execute(event) {
console.log("js: LivePlugin.execute() called");
//console.log("js: LivePlugin.execute() called");
var result = event;
switch(event.type) {
case "identify":
Expand All @@ -66,7 +66,7 @@ public struct EmbeddedJS {
}

track(event) {
console.log("js: Super.track() called");
//console.log("js: Super.track() called");
return event;
}

Expand All @@ -79,7 +79,7 @@ public struct EmbeddedJS {
}

screen(event) {
console.log("js: Super.screen() called");
//console.log("js: Super.screen() called");
return event;
}

Expand Down
Loading
Loading