Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PoC] Sentry client #4178

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
4 changes: 2 additions & 2 deletions StripeCore/StripeCore/Source/Helpers/InstallMethod.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@

import Foundation

enum InstallMethod: String {
@_spi(STP) public enum InstallMethod: String {
case cocoapods = "C"
case spm = "S"
case binary = "B" // Built via export_builds.sh
case xcode = "X" // Directly built via Xcode or xcodebuild

static let current: InstallMethod = {
@_spi(STP) public static let current: InstallMethod = {
#if COCOAPODS
return .cocoapods
#elseif SWIFT_PACKAGE
Expand Down
4 changes: 2 additions & 2 deletions StripeCore/StripeCore/Source/Helpers/STPDeviceUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

import Foundation

struct STPDeviceUtils {
static var deviceType: String? {
@_spi(STP) public struct STPDeviceUtils {
@_spi(STP) public static var deviceType: String? {
var systemInfo: utsname = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,15 @@
492039952CA4972B00CE2072 /* FinancialConnectionsWebFlowTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 492039942CA4972B00CE2072 /* FinancialConnectionsWebFlowTests.swift */; };
492651662C24C9E7001DDBCA /* TestModeAutofillBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 492651652C24C9E7001DDBCA /* TestModeAutofillBannerView.swift */; };
492651682C25C0C2001DDBCA /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = 492651672C25C0C2001DDBCA /* [email protected] */; };
494D32162CC7FCE500C5EFAF /* FinancialConnectionsSentryClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494D32152CC7FCE500C5EFAF /* FinancialConnectionsSentryClient.swift */; };
494D32182CC819F000C5EFAF /* SentryContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494D32172CC819F000C5EFAF /* SentryContext.swift */; };
494D321A2CC8237F00C5EFAF /* SentryPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494D32192CC8237F00C5EFAF /* SentryPayload.swift */; };
494D321C2CC8264D00C5EFAF /* SentryStacktrace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494D321B2CC8264D00C5EFAF /* SentryStacktrace.swift */; };
494D322A2CC85E9000C5EFAF /* TraceSymbolsParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494D32292CC85E9000C5EFAF /* TraceSymbolsParser.swift */; };
494D322C2CC85FE100C5EFAF /* TraceSymbolsParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 494D322B2CC85FE100C5EFAF /* TraceSymbolsParserTests.swift */; };
494D62072C45B9B700106519 /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = 494D62062C45B9B700106519 /* [email protected] */; };
495539EE2C484DC200543D18 /* FinancialConnectionsTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 495539ED2C484DC200543D18 /* FinancialConnectionsTheme.swift */; };
4967A0EA2CC971AB002513AC /* SentryException.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4967A0E92CC971AB002513AC /* SentryException.swift */; };
496A6AE72C29E0BB00D34F8E /* [email protected] in Resources */ = {isa = PBXBuildFile; fileRef = 496A6AE62C29E0BB00D34F8E /* [email protected] */; };
497142BC2C514B08000DFA64 /* FlowRouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 497142BB2C514B08000DFA64 /* FlowRouterTests.swift */; };
49A0B5862C5D2F3C00D697D9 /* FinancialConnectionsAPIClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49A0B5852C5D2F3C00D697D9 /* FinancialConnectionsAPIClientTests.swift */; };
Expand Down Expand Up @@ -322,8 +329,15 @@
492039942CA4972B00CE2072 /* FinancialConnectionsWebFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsWebFlowTests.swift; sourceTree = "<group>"; };
492651652C24C9E7001DDBCA /* TestModeAutofillBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestModeAutofillBannerView.swift; sourceTree = "<group>"; };
492651672C25C0C2001DDBCA /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
494D32152CC7FCE500C5EFAF /* FinancialConnectionsSentryClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsSentryClient.swift; sourceTree = "<group>"; };
494D32172CC819F000C5EFAF /* SentryContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryContext.swift; sourceTree = "<group>"; };
494D32192CC8237F00C5EFAF /* SentryPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryPayload.swift; sourceTree = "<group>"; };
494D321B2CC8264D00C5EFAF /* SentryStacktrace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryStacktrace.swift; sourceTree = "<group>"; };
494D32292CC85E9000C5EFAF /* TraceSymbolsParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceSymbolsParser.swift; sourceTree = "<group>"; };
494D322B2CC85FE100C5EFAF /* TraceSymbolsParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraceSymbolsParserTests.swift; sourceTree = "<group>"; };
494D62062C45B9B700106519 /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
495539ED2C484DC200543D18 /* FinancialConnectionsTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsTheme.swift; sourceTree = "<group>"; };
4967A0E92CC971AB002513AC /* SentryException.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryException.swift; sourceTree = "<group>"; };
496A6AE62C29E0BB00D34F8E /* [email protected] */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "[email protected]"; sourceTree = "<group>"; };
497142BB2C514B08000DFA64 /* FlowRouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlowRouterTests.swift; sourceTree = "<group>"; };
49A0B5852C5D2F3C00D697D9 /* FinancialConnectionsAPIClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FinancialConnectionsAPIClientTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -636,6 +650,7 @@
328390D72E3911449BB9FD0B /* Analytics */ = {
isa = PBXGroup;
children = (
494D32132CC7FCCF00C5EFAF /* Sentry */,
DBBF5CEE2C9030B2D374BC76 /* FinancialConnectionsAnalyticsClient.swift */,
A6038978C79785C18257CD74 /* FinancialConnectionsSheetAnalytics.swift */,
);
Expand Down Expand Up @@ -671,6 +686,19 @@
path = FinancialConnectionsSDK;
sourceTree = "<group>";
};
494D32132CC7FCCF00C5EFAF /* Sentry */ = {
isa = PBXGroup;
children = (
494D32152CC7FCE500C5EFAF /* FinancialConnectionsSentryClient.swift */,
494D32172CC819F000C5EFAF /* SentryContext.swift */,
494D32192CC8237F00C5EFAF /* SentryPayload.swift */,
494D321B2CC8264D00C5EFAF /* SentryStacktrace.swift */,
494D32292CC85E9000C5EFAF /* TraceSymbolsParser.swift */,
4967A0E92CC971AB002513AC /* SentryException.swift */,
);
path = Sentry;
sourceTree = "<group>";
};
49C911362C597EAF00589E0D /* LinkLogin */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1083,6 +1111,7 @@
CF731140836AE438C7F4F6AB /* StringExtensionsTests.swift */,
497142BB2C514B08000DFA64 /* FlowRouterTests.swift */,
492039942CA4972B00CE2072 /* FinancialConnectionsWebFlowTests.swift */,
494D322B2CC85FE100C5EFAF /* TraceSymbolsParserTests.swift */,
);
path = StripeFinancialConnectionsTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -1230,6 +1259,8 @@
"zh-Hant",
);
mainGroup = 7879CBA341D7E807714A831B;
packageReferences = (
);
productRefGroup = 820BF9CF057CF92872BC3C15 /* Products */;
projectDirPath = "";
projectRoot = "";
Expand Down Expand Up @@ -1299,6 +1330,7 @@
6A3DA1F52C34A37F005C3F6E /* GenericInfoViewController.swift in Sources */,
FBF513C7F73002FA30CC7C21 /* ConsumerSessionModels.swift in Sources */,
6A3739142C40558900D1F765 /* GenericInfoBodyView.swift in Sources */,
494D321A2CC8237F00C5EFAF /* SentryPayload.swift in Sources */,
EC74B719F0FA1A977EF4708C /* FinancialConnectionsAccount.swift in Sources */,
460C7685096AA6C693309647 /* FinancialConnectionsAuthSession.swift in Sources */,
AB5AFAC3C70D6195075DE5AE /* FinancialConnectionsBulletPoint.swift in Sources */,
Expand All @@ -1308,8 +1340,10 @@
F67624595BD2CD7B6793BFDA /* FinancialConnectionsImage.swift in Sources */,
07712610C7D2F484AAB96982 /* FinancialConnectionsInstitution.swift in Sources */,
7386E1F9256B23CE29BF996D /* FinancialConnectionsInstitutionSearchResultResource.swift in Sources */,
494D321C2CC8264D00C5EFAF /* SentryStacktrace.swift in Sources */,
C7D2763ACCE2CC71E788E18F /* FinancialConnectionsLegalDetailsNotice.swift in Sources */,
B271AAF41C9FE6AE392B88D3 /* FinancialConnectionsMixedOAuthParams.swift in Sources */,
4967A0EA2CC971AB002513AC /* SentryException.swift in Sources */,
DAA51ABB496551074DBA1A20 /* FinancialConnectionsNetworkedAccountsResponse.swift in Sources */,
6A732CA62B69A46D00828CB1 /* PhoneTextField.swift in Sources */,
6FE9F171CF9A5D0EDB2035AA /* FinancialConnectionsNetworkingLinkSignup.swift in Sources */,
Expand Down Expand Up @@ -1374,6 +1408,7 @@
3446145FCA3278D51A9D4B80 /* AttachLinkedPaymentAccountDataSource.swift in Sources */,
E3F62D2F9C344A1178030E8E /* AttachLinkedPaymentAccountViewController.swift in Sources */,
707C265C4179A8FEC98913FE /* ConsentBodyView.swift in Sources */,
494D322A2CC85E9000C5EFAF /* TraceSymbolsParser.swift in Sources */,
465AE8A58AD2183E1E2042FE /* ConsentDataSource.swift in Sources */,
97C528CE821C6A55D58F68A4 /* ConsentFooterView.swift in Sources */,
8927328EE28A0C94B5AB69DB /* ConsentLogoView.swift in Sources */,
Expand Down Expand Up @@ -1431,6 +1466,7 @@
9AF6EC34D666BEB3C1397092 /* BulletPointLabelView.swift in Sources */,
313F5F7F2B0BE5D100BD98A9 /* Docs.docc in Sources */,
F65E8D16DE691EB6C99C4521 /* Button+Extensions.swift in Sources */,
494D32162CC7FCE500C5EFAF /* FinancialConnectionsSentryClient.swift in Sources */,
33FA1684CE79F21271D14F23 /* HitTestStackView.swift in Sources */,
691619AE9A989548ABA36535 /* HitTestView.swift in Sources */,
91A3583A0BDE0F8F0C4AD3E2 /* InstitutionIconView.swift in Sources */,
Expand All @@ -1440,6 +1476,7 @@
49C911372C597EAF00589E0D /* LinkLoginDataSource.swift in Sources */,
6A7814182B361C5000168992 /* PaneLayoutView+Extensions.swift in Sources */,
99F41681B77ECB0090F34E31 /* SFSafariViewController+Extensions.swift in Sources */,
494D32182CC819F000C5EFAF /* SentryContext.swift in Sources */,
AA80602323C28AFAC391358D /* TimeInterval+Extensions.swift in Sources */,
E637387728FA1597B1B51E5D /* UIImage+Extensions.swift in Sources */,
495539EE2C484DC200543D18 /* FinancialConnectionsTheme.swift in Sources */,
Expand Down Expand Up @@ -1471,6 +1508,7 @@
492039952CA4972B00CE2072 /* FinancialConnectionsWebFlowTests.swift in Sources */,
700B745FEF43088D9E34C0E4 /* AccountPickerHelpersTests.swift in Sources */,
6744CB1B182C5F7220B0B804 /* AuthFlowHelpersTests.swift in Sources */,
494D322C2CC85FE100C5EFAF /* TraceSymbolsParserTests.swift in Sources */,
39E5D4531961150E9CB3262F /* EmptyFinancialConnectionsAPIClient.swift in Sources */,
CB734C25A19D38A87876FB2B /* FinancialConnectionsAnalyticsTest.swift in Sources */,
497142BC2C514B08000DFA64 /* FlowRouterTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// FinancialConnectionsSentryClient.swift
// StripeFinancialConnections
//
// Created by Mat Schmid on 2024-10-22.
//

import Foundation
@_spi(STP) import StripeCore

protocol FinancialConnectionsErrorReporter {
func report(error: Error, parameters: [String: Any])
}

class FinancialConnectionsSentryClient: FinancialConnectionsErrorReporter {
private static let endpoint: URL = {
let projectId = "871"
var components = URLComponents()
components.scheme = "https"
components.host = "errors.stripe.com"
components.path = "/api/\(projectId)/envelope/"
return components.url!
}()

func report(error: Error, parameters: [String: Any]) {
// TODO
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// SentryContext.swift
// StripeFinancialConnections
//
// Created by Mat Schmid on 2024-10-22.
//

import Foundation
@_spi(STP) import StripeCore
import UIKit

struct SentryContext: Encodable {
let app: SentryAppContext
let os: SentryOsContext
let device: SentryDeviceContext

static let shared: SentryContext = {
let app = SentryAppContext(
appIdentifier: Bundle.stp_applicationBundleId() ?? "",
appName: Bundle.stp_applicationName() ?? "",
appVersion: Bundle.stp_applicationVersion() ?? ""
)
let os = SentryOsContext(
name: "iOS",
version: UIDevice.current.systemVersion,
type: InstallMethod.current.rawValue
)
let device = SentryDeviceContext(
modelId: UIDevice.current.identifierForVendor?.uuidString ?? "",
model: UIDevice.current.model,
manufacturer: "Apple",
type: STPDeviceUtils.deviceType ?? ""
)

return SentryContext(app: app, os: os, device: device)
}()
}

struct SentryAppContext: Encodable {
let appIdentifier: String
let appName: String
let appVersion: String
}

struct SentryOsContext: Encodable {
let name: String
let version: String
let type: String
}

struct SentryDeviceContext: Encodable {
let modelId: String
let model: String
let manufacturer: String
let type: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// SentryException.swift
// StripeFinancialConnections
//
// Created by Mat Schmid on 2024-10-23.
//

import Foundation

struct SentryException: Encodable {
var values: [SentryExceptionValue] = []
}

/// https://develop.sentry.dev/sdk/data-model/event-payloads/exception/
struct SentryExceptionValue: Encodable {
let type: String
let value: String
let stacktrace: SentryStacktrace
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// SentryPayload.swift
// StripeFinancialConnections
//
// Created by Mat Schmid on 2024-10-22.
//

import Foundation
@_spi(STP) import StripeCore

struct SentryPayload: Encodable {
let eventId: String = UUID().uuidString.replacingOccurrences(of: "-", with: "")
let timestamp: TimeInterval = Date().timeIntervalSince1970
let release: String = StripeAPIConfiguration.STPSDKVersion
let context: SentryContext = .shared
let exception: SentryException
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// SentryStacktrace.swift
// StripeFinancialConnections
//
// Created by Mat Schmid on 2024-10-22.
//

import Foundation

/// https://develop.sentry.dev/sdk/data-model/event-payloads/stacktrace/
struct SentryTrace: Encodable, Equatable {
let module: String?
let file: String?
let function: String
let lineno: Int?

init(module: String? = nil, file: String? = nil, function: String, lineno: Int? = nil) {
self.module = module
self.file = file
self.function = function
self.lineno = lineno
}

enum CodingKeys: CodingKey {
case module
case file
case function
case lineno
}

// Custom encoder to only encode non-nil properties.
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(self.module, forKey: .module)
try container.encodeIfPresent(self.file, forKey: .file)
try container.encode(self.function, forKey: .function)
try container.encodeIfPresent(self.lineno, forKey: .lineno)
}
}

struct SentryStacktrace: Encodable {
let frames: [SentryTrace]

static func capture(
filePath: String = #file,
function: String = #function,
line: Int = #line,
callsiteDepth: Int = 1
) -> [SentryTrace] {
var traces: [SentryTrace] = []
if let file = filePath.components(separatedBy: "/").last {
// Start with a Root trace, which includes the file, function, and lineno.
traces.append(SentryTrace(
file: file,
function: function,
lineno: line
))
}

// Add all other traces by adding 1 to the callsite depth. This removes the meta call
// to `SentryStacktrace.capture` from the stacktrace.
let callStackTrace = TraceSymbolsParser.current(callsiteDepth: callsiteDepth + 1)
traces.append(contentsOf: callStackTrace)
return traces
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// TraceSymbolsParser.swift
// StripeFinancialConnections
//
// Created by Mat Schmid on 2024-10-22.
//

import Foundation

enum TraceSymbolsParser {
/// Get the current call stack as an `CallStacktrace` array representaion.
/// Removes the first `callsiteDepth` traces from the stack.
/// These traces are usually meta traces from classes calling the parser.
/// i.e. removes `TraceSymbolsParser.current` from the stack trace
static func current(callsiteDepth: Int = 0) -> [SentryTrace] {
let callStackSymbols: [String] = Array(Thread.callStackSymbols.dropFirst(callsiteDepth))
return callStackSymbols.compactMap { symbols in
Self.parse(symbols: symbols)
}
}

/// Parses a line of `Thread.callStackSymbols` to `CallStackTrace`.
/// - `symbols`: Input which follows the `DLADDR` format.
/// ```
/// // {depth} {fname} {fbase} {sname} + {saddr}
/// (number with radix 10) (string) (number with radix 16) (string) + (number with radix 10)
/// ```
/// This extracts `fname` into the `module`, and `sname` into the `functions.`
static func parse(symbols: String) -> SentryTrace? {
// Split the input string by whitespaces and filter out empty components
let components = symbols.split(whereSeparator: \.isWhitespace).filter { !$0.isEmpty }
guard components.indices.contains(3) else {
return nil // Invalid symbol, not enough components.
}
// The `module` is the second component
let module = String(components[1])

// The `function` is everything from the fourth component up to but not including the "+"
let functionComponents = components[3...]
let functionString = functionComponents.joined(separator: " ")

// Find the index of the "+" symbol in the original string
guard let plusIndex = functionString.range(of: "+")?.lowerBound else {
return nil // "+" symbol not found.
}

// Extract the final function string from the joined components, trimmed and until the "+"
let functionEndIndex = symbols.distance(from: symbols.startIndex, to: plusIndex)
let function = String(functionString.prefix(functionEndIndex)).trimmingCharacters(in: .whitespaces)
return SentryTrace(module: module, function: function)
}
}
Loading
Loading