sneakoscope 8.2.0 → 8.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -110
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/command-manifest-lite.js +0 -2
- package/dist/cli/command-registry.js +0 -2
- package/dist/commands/codex.js +3 -3
- package/dist/commands/doctor.js +5 -157
- package/dist/config/skills-manifest.json +1 -1
- package/dist/core/codex/codex-cli-syntax-builder.js +0 -8
- package/dist/core/codex-app/codex-models-cache.js +2 -1
- package/dist/core/codex-app/menubar/constants.js +0 -7
- package/dist/core/codex-compat/codex-capability-matrix.js +7 -7
- package/dist/core/codex-compat/codex-compat-report.js +8 -12
- package/dist/core/codex-compat/codex-runtime-contract.js +62 -0
- package/dist/core/codex-compat/codex-version-policy.js +7 -7
- package/dist/core/codex-control/codex-current-app-capability.js +3 -3
- package/dist/core/codex-control/codex-current-capability.js +67 -94
- package/dist/core/codex-control/codex-current-core-capability.js +3 -3
- package/dist/core/codex-control/codex-current-core-probe-runner.js +4 -4
- package/dist/core/codex-control/codex-current-core-real-probes.js +2 -2
- package/dist/core/codex-control/codex-current-feature-capability.js +4 -4
- package/dist/core/codex-control/codex-current-image-path-real-probe.js +1 -1
- package/dist/core/codex-control/codex-current-web-search-probe.js +1 -1
- package/dist/core/codex-control/codex-sdk-capability.js +2 -2
- package/dist/core/commands/remote-command.js +2 -2
- package/dist/core/context7-client.js +5 -2
- package/dist/core/doctor/codex-current-core-probe-doctor.js +2 -2
- package/dist/core/doctor/doctor-dirty-planner.js +1 -1
- package/dist/core/doctor/doctor-readiness-matrix.js +0 -7
- package/dist/core/feature-fixtures.js +0 -4
- package/dist/core/feature-registry.js +0 -1
- package/dist/core/image-ux-review.js +3 -3
- package/dist/core/install/installed-package-smoke.js +2 -3
- package/dist/core/mad-sks/sql-plane/mcp-executor.js +4 -3
- package/dist/core/mcp/modern-protocol.js +36 -0
- package/dist/core/mcp-config/health-check.js +45 -30
- package/dist/core/perf/release-latency-slo.js +1 -2
- package/dist/core/proof/runtime-truth-matrix.js +1 -1
- package/dist/core/release/canonical-test-proof.js +1 -2
- package/dist/core/release/gate-manifest.js +5 -2
- package/dist/core/release/physical-release-gates.js +0 -26
- package/dist/core/release/release-gate-affected-selector.js +2 -1
- package/dist/core/release/release-gate-contract.js +3 -1
- package/dist/core/release/release-pack-receipt.js +0 -1
- package/dist/core/release/stage-publish.js +1 -1
- package/dist/core/version-manager.js +4 -0
- package/dist/core/version.js +1 -1
- package/dist/native/sks-menubar/Sources/AppDelegate.swift +1 -12
- package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +2 -6
- package/dist/native/sks-menubar/Sources/RemoteCodingViewController.swift +28 -471
- package/dist/native/sks-menubar/Sources/SecureProcessEnvelope.swift +1 -43
- package/dist/scripts/codex-current-app-server-v2-check.js +9 -8
- package/dist/scripts/codex-current-binary-identity-check.js +2 -2
- package/dist/scripts/codex-current-capability-check.js +9 -7
- package/dist/scripts/codex-current-dependency-check.js +21 -0
- package/dist/scripts/docs-truthfulness-check.js +4 -19
- package/dist/scripts/fast-codex-service-tier-proof-check.js +0 -1
- package/dist/scripts/install-surface-ssot-check.js +2 -2
- package/dist/scripts/latest-version-guidance-check.js +1 -1
- package/dist/scripts/packlist-performance-check.js +0 -1
- package/dist/scripts/release-metadata-check.js +4 -1
- package/dist/scripts/release-version-truth-check.js +149 -0
- package/dist/vendor/openai-codex/latest/hooks/permission-request.command.input.schema.json +6 -0
- package/dist/vendor/openai-codex/latest/hooks/permission-request.command.output.schema.json +2 -16
- package/dist/vendor/openai-codex/latest/hooks/post-compact.command.input.schema.json +6 -0
- package/dist/vendor/openai-codex/latest/hooks/post-tool-use.command.input.schema.json +6 -0
- package/dist/vendor/openai-codex/latest/hooks/post-tool-use.command.output.schema.json +2 -16
- package/dist/vendor/openai-codex/latest/hooks/pre-compact.command.input.schema.json +6 -0
- package/dist/vendor/openai-codex/latest/hooks/pre-tool-use.command.input.schema.json +6 -0
- package/dist/vendor/openai-codex/latest/hooks/pre-tool-use.command.output.schema.json +2 -16
- package/dist/vendor/openai-codex/latest/hooks/session-start.command.output.schema.json +2 -16
- package/dist/vendor/openai-codex/latest/hooks/snapshot-metadata.json +7 -5
- package/dist/vendor/openai-codex/latest/hooks/subagent-start.command.output.schema.json +4 -18
- package/dist/vendor/openai-codex/latest/hooks/user-prompt-submit.command.input.schema.json +6 -0
- package/dist/vendor/openai-codex/latest/hooks/user-prompt-submit.command.output.schema.json +2 -16
- package/package.json +4 -7
- package/release-gates.v2.json +49 -19
- package/config/codex-releases/rust-v0.146.0.json +0 -45
- package/dist/core/agents/agent-runner-codex-exec.js +0 -177
- package/dist/core/codex-compat/codex-release-manifest.js +0 -72
- package/dist/core/commands/telegram-command.js +0 -447
- package/dist/core/telegram/access.js +0 -58
- package/dist/core/telegram/client.js +0 -157
- package/dist/core/telegram/confirmation.js +0 -37
- package/dist/core/telegram/doctor.js +0 -154
- package/dist/core/telegram/index.js +0 -9
- package/dist/core/telegram/keychain.js +0 -317
- package/dist/core/telegram/liveness.js +0 -124
- package/dist/core/telegram/poller.js +0 -90
- package/dist/core/telegram/redaction.js +0 -15
- package/dist/core/telegram/state-lock.js +0 -416
- package/dist/core/telegram/types.js +0 -1
- package/dist/native/sks-menubar/Sources/TelegramPrivateFileStore.swift +0 -498
- package/dist/native/sks-menubar/Sources/TelegramPrivateFileSupport.swift +0 -236
- package/dist/native/sks-menubar/Sources/TelegramProcessGateway.swift +0 -454
- package/dist/native/sks-menubar/Sources/TelegramRuntimeSupport.swift +0 -56
- package/dist/native/sks-menubar/Sources/TelegramStateLock.swift +0 -448
- package/dist/native/sks-menubar/Sources/TelegramSupport.swift +0 -243
- package/dist/native/sks-menubar/Sources/TelegramTransport.swift +0 -499
- package/dist/scripts/codex-current-manifest-check.js +0 -29
- package/schemas/codex/app-server-0.146/codex_app_server_protocol.v2.schemas.json +0 -19858
|
@@ -1,498 +0,0 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
|
|
3
|
-
struct TelegramStoredConfirmation: Sendable, Equatable {
|
|
4
|
-
let request: TelegramTypedCommandRequest
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
enum TelegramTokenSource: String, Codable, Sendable {
|
|
8
|
-
case environment = "env"
|
|
9
|
-
case userSecretFile = "user_secret_file"
|
|
10
|
-
case none
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
struct TelegramResolvedAccessToken: Sendable, Equatable {
|
|
14
|
-
let token: String?
|
|
15
|
-
let source: TelegramTokenSource
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
struct TelegramBotBinding: Sendable, Equatable {
|
|
19
|
-
let botID: Int64
|
|
20
|
-
let previousBotID: Int64?
|
|
21
|
-
let pollOffset: Int64
|
|
22
|
-
let stateReset: Bool
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
enum TelegramTokenResolutionError: Error {
|
|
26
|
-
case invalid(source: TelegramTokenSource)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
protocol TelegramAccessStoring: Sendable {
|
|
30
|
-
func resolveToken() throws -> TelegramResolvedAccessToken
|
|
31
|
-
func bindBotIdentity(_ botID: Int64) throws -> TelegramBotBinding
|
|
32
|
-
func persistPollOffset(_ offset: Int64, botID: Int64) throws
|
|
33
|
-
func consumePairing(code: String, chatID: Int64, senderID: Int64, chatType: String) throws -> Bool
|
|
34
|
-
func stagePairing(code: String, chatID: Int64, senderID: Int64, chatType: String) throws -> Bool
|
|
35
|
-
func activatePairing(chatID: Int64, senderID: Int64) throws -> Bool
|
|
36
|
-
func abandonPendingPairing(chatID: Int64, senderID: Int64) throws
|
|
37
|
-
func isAuthorized(chatID: Int64, senderID: Int64) throws -> Bool
|
|
38
|
-
func authorizedCount() throws -> Int
|
|
39
|
-
func issueConfirmation(
|
|
40
|
-
chatID: Int64,
|
|
41
|
-
senderID: Int64,
|
|
42
|
-
request: TelegramTypedCommandRequest,
|
|
43
|
-
expiresAt: Date
|
|
44
|
-
) throws -> String
|
|
45
|
-
func consumeConfirmation(nonce: String, chatID: Int64, senderID: Int64) throws -> TelegramStoredConfirmation?
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
extension TelegramAccessStoring {
|
|
49
|
-
// Compatibility for deterministic/injected stores. The production private
|
|
50
|
-
// store overrides these with a durable inactive-to-active transition.
|
|
51
|
-
func stagePairing(code: String, chatID: Int64, senderID: Int64, chatType: String) throws -> Bool {
|
|
52
|
-
try consumePairing(code: code, chatID: chatID, senderID: senderID, chatType: chatType)
|
|
53
|
-
}
|
|
54
|
-
func activatePairing(chatID: Int64, senderID: Int64) throws -> Bool { true }
|
|
55
|
-
func abandonPendingPairing(chatID: Int64, senderID: Int64) throws { }
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
final class TelegramPrivateFileStore: TelegramAccessStoring, @unchecked Sendable {
|
|
59
|
-
static let stateSchema = "sks.telegram-state.v1"
|
|
60
|
-
static let pairingSchema = "sks.telegram-pairing.v1"
|
|
61
|
-
static let tokenEnvironmentNames = ["TELEGRAM_BOT_TOKEN", "SKS_TELEGRAM_BOT_TOKEN"]
|
|
62
|
-
|
|
63
|
-
private struct PairingState {
|
|
64
|
-
let schema: String
|
|
65
|
-
let code: String
|
|
66
|
-
let expiresAt: String
|
|
67
|
-
var used: Bool
|
|
68
|
-
var raw: [String: Any]
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
private struct AuthorizedChat {
|
|
72
|
-
let chatID: Int64
|
|
73
|
-
let senderID: Int64
|
|
74
|
-
let pairedAt: String
|
|
75
|
-
var active: Bool
|
|
76
|
-
var raw: [String: Any]
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
private struct ConfirmationState {
|
|
80
|
-
let nonce: String
|
|
81
|
-
let chatID: Int64
|
|
82
|
-
let senderID: Int64
|
|
83
|
-
let command: String
|
|
84
|
-
let inputJSON: String
|
|
85
|
-
let expiresAt: String
|
|
86
|
-
var raw: [String: Any]
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
private struct State {
|
|
90
|
-
let schema: String
|
|
91
|
-
var botID: Int64?
|
|
92
|
-
var pollOffset: Int64
|
|
93
|
-
var pairing: PairingState?
|
|
94
|
-
var chats: [AuthorizedChat]
|
|
95
|
-
var confirmations: [ConfirmationState]
|
|
96
|
-
var raw: [String: Any]
|
|
97
|
-
|
|
98
|
-
init(
|
|
99
|
-
schema: String = TelegramPrivateFileStore.stateSchema,
|
|
100
|
-
botID: Int64? = nil,
|
|
101
|
-
pollOffset: Int64 = 0,
|
|
102
|
-
pairing: PairingState? = nil,
|
|
103
|
-
chats: [AuthorizedChat] = [],
|
|
104
|
-
confirmations: [ConfirmationState] = [],
|
|
105
|
-
raw: [String: Any] = [:]
|
|
106
|
-
) {
|
|
107
|
-
self.schema = schema
|
|
108
|
-
self.botID = botID
|
|
109
|
-
self.pollOffset = pollOffset
|
|
110
|
-
self.pairing = pairing
|
|
111
|
-
self.chats = chats
|
|
112
|
-
self.confirmations = confirmations
|
|
113
|
-
self.raw = raw
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
private let environment: [String: String]
|
|
118
|
-
private let files: TelegramPrivateFileSupport
|
|
119
|
-
private let now: @Sendable () -> Date
|
|
120
|
-
|
|
121
|
-
init(
|
|
122
|
-
homeDirectory: URL? = nil,
|
|
123
|
-
environment: [String: String] = ProcessInfo.processInfo.environment,
|
|
124
|
-
now: @escaping @Sendable () -> Date = { Date() }
|
|
125
|
-
) {
|
|
126
|
-
self.environment = environment
|
|
127
|
-
self.files = TelegramPrivateFileSupport(homeDirectory: homeDirectory, environment: environment)
|
|
128
|
-
self.now = now
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
static func operatorEnvironmentOverrideActive(
|
|
132
|
-
_ environment: [String: String] = ProcessInfo.processInfo.environment
|
|
133
|
-
) -> Bool {
|
|
134
|
-
tokenEnvironmentNames.contains {
|
|
135
|
-
!(environment[$0] ?? "").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
func resolveToken() throws -> TelegramResolvedAccessToken {
|
|
140
|
-
for name in Self.tokenEnvironmentNames {
|
|
141
|
-
let value = (environment[name] ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
|
|
142
|
-
if !value.isEmpty {
|
|
143
|
-
guard let token = try? validatedToken(value) else {
|
|
144
|
-
throw TelegramTokenResolutionError.invalid(source: .environment)
|
|
145
|
-
}
|
|
146
|
-
return TelegramResolvedAccessToken(token: token, source: .environment)
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
guard let data = try files.readTokenData() else {
|
|
150
|
-
return TelegramResolvedAccessToken(token: nil, source: .none)
|
|
151
|
-
}
|
|
152
|
-
guard let value = String(data: data, encoding: .utf8) else {
|
|
153
|
-
throw TelegramTokenResolutionError.invalid(source: .userSecretFile)
|
|
154
|
-
}
|
|
155
|
-
let normalized = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
156
|
-
guard let token = try? validatedToken(normalized) else {
|
|
157
|
-
throw TelegramTokenResolutionError.invalid(source: .userSecretFile)
|
|
158
|
-
}
|
|
159
|
-
return TelegramResolvedAccessToken(token: token, source: .userSecretFile)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
func loadToken() throws -> String? {
|
|
163
|
-
do { return try resolveToken().token }
|
|
164
|
-
catch is TelegramTokenResolutionError { throw TelegramPrivateFileError.invalidStoredValue }
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
func bindBotIdentity(_ botID: Int64) throws -> TelegramBotBinding {
|
|
168
|
-
guard botID > 0 else { throw TelegramPrivateFileError.invalidStoredValue }
|
|
169
|
-
return try files.withStateTransaction {
|
|
170
|
-
var state = try loadStateUnlocked()
|
|
171
|
-
let previousBotID = state.botID
|
|
172
|
-
let reset = previousBotID != botID || state.chats.count > 1 || state.chats.contains { !$0.active }
|
|
173
|
-
state.botID = botID
|
|
174
|
-
if reset {
|
|
175
|
-
state.pollOffset = 0
|
|
176
|
-
state.pairing = nil
|
|
177
|
-
state.chats = []
|
|
178
|
-
state.confirmations = []
|
|
179
|
-
}
|
|
180
|
-
try writeStateUnlocked(state)
|
|
181
|
-
return TelegramBotBinding(
|
|
182
|
-
botID: botID,
|
|
183
|
-
previousBotID: previousBotID,
|
|
184
|
-
pollOffset: state.pollOffset,
|
|
185
|
-
stateReset: reset
|
|
186
|
-
)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
func persistPollOffset(_ offset: Int64, botID: Int64) throws {
|
|
191
|
-
guard offset >= 0, botID > 0 else { throw TelegramPrivateFileError.invalidStoredValue }
|
|
192
|
-
try files.withStateTransaction {
|
|
193
|
-
var state = try loadStateUnlocked()
|
|
194
|
-
guard state.botID == botID else { throw TelegramPrivateFileError.invalidStoredValue }
|
|
195
|
-
state.pollOffset = offset
|
|
196
|
-
try writeStateUnlocked(state)
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
func consumePairing(code: String, chatID: Int64, senderID: Int64, chatType: String) throws -> Bool {
|
|
201
|
-
try replacePairing(
|
|
202
|
-
code: code, chatID: chatID, senderID: senderID,
|
|
203
|
-
chatType: chatType, active: true
|
|
204
|
-
)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
func stagePairing(code: String, chatID: Int64, senderID: Int64, chatType: String) throws -> Bool {
|
|
208
|
-
try replacePairing(
|
|
209
|
-
code: code, chatID: chatID, senderID: senderID,
|
|
210
|
-
chatType: chatType, active: false
|
|
211
|
-
)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
func activatePairing(chatID: Int64, senderID: Int64) throws -> Bool {
|
|
215
|
-
guard chatID > 0, senderID > 0 else { return false }
|
|
216
|
-
return try files.withStateTransaction {
|
|
217
|
-
var state = try loadStateUnlocked()
|
|
218
|
-
guard state.chats.count == 1,
|
|
219
|
-
state.chats[0].chatID == chatID,
|
|
220
|
-
state.chats[0].senderID == senderID,
|
|
221
|
-
!state.chats[0].active else { return false }
|
|
222
|
-
state.chats[0].active = true
|
|
223
|
-
try writeStateUnlocked(state)
|
|
224
|
-
return true
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
func abandonPendingPairing(chatID: Int64, senderID: Int64) throws {
|
|
229
|
-
try files.withStateTransaction {
|
|
230
|
-
var state = try loadStateUnlocked()
|
|
231
|
-
let originalCount = state.chats.count
|
|
232
|
-
state.chats.removeAll {
|
|
233
|
-
$0.chatID == chatID && $0.senderID == senderID && !$0.active
|
|
234
|
-
}
|
|
235
|
-
if state.chats.count != originalCount { try writeStateUnlocked(state) }
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
private func replacePairing(
|
|
240
|
-
code: String,
|
|
241
|
-
chatID: Int64,
|
|
242
|
-
senderID: Int64,
|
|
243
|
-
chatType: String,
|
|
244
|
-
active: Bool
|
|
245
|
-
) throws -> Bool {
|
|
246
|
-
guard chatType == "private", chatID > 0, senderID > 0 else { return false }
|
|
247
|
-
return try files.withStateTransaction {
|
|
248
|
-
var state = try loadStateUnlocked()
|
|
249
|
-
guard var pairing = state.pairing,
|
|
250
|
-
pairing.code == code,
|
|
251
|
-
!pairing.used,
|
|
252
|
-
let expiration = parseTelegramDate(pairing.expiresAt),
|
|
253
|
-
expiration > now() else { return false }
|
|
254
|
-
pairing.used = true
|
|
255
|
-
state.pairing = pairing
|
|
256
|
-
state.chats = [AuthorizedChat(
|
|
257
|
-
chatID: chatID,
|
|
258
|
-
senderID: senderID,
|
|
259
|
-
pairedAt: telegramPrivateStoreDate(now()),
|
|
260
|
-
active: active,
|
|
261
|
-
raw: [:]
|
|
262
|
-
)]
|
|
263
|
-
state.confirmations = []
|
|
264
|
-
try writeStateUnlocked(state)
|
|
265
|
-
return true
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
func isAuthorized(chatID: Int64, senderID: Int64) throws -> Bool {
|
|
270
|
-
try files.withStateTransaction {
|
|
271
|
-
try loadStateUnlocked().chats.contains {
|
|
272
|
-
$0.chatID == chatID && $0.senderID == senderID && $0.active
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
func authorizedCount() throws -> Int {
|
|
278
|
-
try files.withStateTransaction { try loadStateUnlocked().chats.filter(\.active).count }
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
func issueConfirmation(
|
|
282
|
-
chatID: Int64,
|
|
283
|
-
senderID: Int64,
|
|
284
|
-
request: TelegramTypedCommandRequest,
|
|
285
|
-
expiresAt: Date
|
|
286
|
-
) throws -> String {
|
|
287
|
-
try files.withStateTransaction {
|
|
288
|
-
var state = try loadStateUnlocked()
|
|
289
|
-
removeExpiredConfirmations(from: &state)
|
|
290
|
-
guard state.confirmations.count < 64 else {
|
|
291
|
-
throw TelegramPrivateFileError.tooManyConfirmations
|
|
292
|
-
}
|
|
293
|
-
let nonce = UUID().uuidString.replacingOccurrences(of: "-", with: "")
|
|
294
|
-
state.confirmations.append(ConfirmationState(
|
|
295
|
-
nonce: nonce,
|
|
296
|
-
chatID: chatID,
|
|
297
|
-
senderID: senderID,
|
|
298
|
-
command: request.name,
|
|
299
|
-
inputJSON: request.inputJSON,
|
|
300
|
-
expiresAt: telegramPrivateStoreDate(expiresAt),
|
|
301
|
-
raw: [:]
|
|
302
|
-
))
|
|
303
|
-
try writeStateUnlocked(state)
|
|
304
|
-
return nonce
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
func consumeConfirmation(nonce: String, chatID: Int64, senderID: Int64) throws -> TelegramStoredConfirmation? {
|
|
309
|
-
try files.withStateTransaction {
|
|
310
|
-
var state = try loadStateUnlocked()
|
|
311
|
-
let countBeforeExpiration = state.confirmations.count
|
|
312
|
-
removeExpiredConfirmations(from: &state)
|
|
313
|
-
guard let index = state.confirmations.firstIndex(where: {
|
|
314
|
-
$0.nonce == nonce && $0.chatID == chatID && $0.senderID == senderID
|
|
315
|
-
}) else {
|
|
316
|
-
if state.confirmations.count != countBeforeExpiration { try writeStateUnlocked(state) }
|
|
317
|
-
return nil
|
|
318
|
-
}
|
|
319
|
-
let confirmation = state.confirmations.remove(at: index)
|
|
320
|
-
try writeStateUnlocked(state)
|
|
321
|
-
return TelegramStoredConfirmation(request: TelegramTypedCommandRequest(
|
|
322
|
-
name: confirmation.command,
|
|
323
|
-
inputJSON: confirmation.inputJSON
|
|
324
|
-
))
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
private func validatedToken(_ value: String) throws -> String {
|
|
329
|
-
guard value.range(
|
|
330
|
-
of: #"^\d{5,20}:[A-Za-z0-9_-]{20,128}$"#,
|
|
331
|
-
options: .regularExpression
|
|
332
|
-
) != nil else { throw TelegramPrivateFileError.invalidStoredValue }
|
|
333
|
-
return value
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
private func removeExpiredConfirmations(from state: inout State) {
|
|
337
|
-
let current = now()
|
|
338
|
-
state.confirmations.removeAll {
|
|
339
|
-
guard let expiration = parseTelegramDate($0.expiresAt) else { return true }
|
|
340
|
-
return expiration <= current
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
private func loadStateUnlocked() throws -> State {
|
|
345
|
-
guard let data = try files.readStateData() else { return State() }
|
|
346
|
-
guard let root = try JSONSerialization.jsonObject(with: data) as? [String: Any],
|
|
347
|
-
root["schema"] as? String == Self.stateSchema,
|
|
348
|
-
root.keys.contains("pairing"),
|
|
349
|
-
let chatRows = root["chats"] as? [[String: Any]],
|
|
350
|
-
let confirmationRows = root["confirmations"] as? [[String: Any]] else {
|
|
351
|
-
throw TelegramPrivateFileError.invalidStoredValue
|
|
352
|
-
}
|
|
353
|
-
let pairing: PairingState?
|
|
354
|
-
if root["pairing"] is NSNull {
|
|
355
|
-
pairing = nil
|
|
356
|
-
} else if let row = root["pairing"] as? [String: Any] {
|
|
357
|
-
pairing = try decodePairing(row)
|
|
358
|
-
} else {
|
|
359
|
-
throw TelegramPrivateFileError.invalidStoredValue
|
|
360
|
-
}
|
|
361
|
-
let chats = try chatRows.map(decodeChat)
|
|
362
|
-
let confirmations = try confirmationRows.map(decodeConfirmation)
|
|
363
|
-
let botID: Int64?
|
|
364
|
-
if root["bot_id"] == nil || root["bot_id"] is NSNull {
|
|
365
|
-
botID = nil
|
|
366
|
-
} else if let value = telegramInt64(root["bot_id"]), value > 0 {
|
|
367
|
-
botID = value
|
|
368
|
-
} else {
|
|
369
|
-
throw TelegramPrivateFileError.invalidStoredValue
|
|
370
|
-
}
|
|
371
|
-
let pollOffset: Int64
|
|
372
|
-
if root["poll_offset"] == nil {
|
|
373
|
-
pollOffset = 0
|
|
374
|
-
} else if let value = telegramInt64(root["poll_offset"]), value >= 0 {
|
|
375
|
-
pollOffset = value
|
|
376
|
-
} else {
|
|
377
|
-
throw TelegramPrivateFileError.invalidStoredValue
|
|
378
|
-
}
|
|
379
|
-
return State(
|
|
380
|
-
botID: botID,
|
|
381
|
-
pollOffset: pollOffset,
|
|
382
|
-
pairing: pairing,
|
|
383
|
-
chats: chats,
|
|
384
|
-
confirmations: confirmations,
|
|
385
|
-
raw: root
|
|
386
|
-
)
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
private func writeStateUnlocked(_ state: State) throws {
|
|
390
|
-
var root = state.raw
|
|
391
|
-
root["schema"] = Self.stateSchema
|
|
392
|
-
root["bot_id"] = state.botID ?? NSNull()
|
|
393
|
-
root["poll_offset"] = state.pollOffset
|
|
394
|
-
root["pairing"] = state.pairing.map(encodePairing) ?? NSNull()
|
|
395
|
-
root["chats"] = state.chats.map(encodeChat)
|
|
396
|
-
root["confirmations"] = state.confirmations.map(encodeConfirmation)
|
|
397
|
-
guard JSONSerialization.isValidJSONObject(root) else {
|
|
398
|
-
throw TelegramPrivateFileError.invalidStoredValue
|
|
399
|
-
}
|
|
400
|
-
var data = try JSONSerialization.data(withJSONObject: root, options: [.sortedKeys])
|
|
401
|
-
data.append(0x0A)
|
|
402
|
-
try files.writeStateData(data)
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
private func decodePairing(_ row: [String: Any]) throws -> PairingState {
|
|
406
|
-
guard let schema = row["schema"] as? String, schema == Self.pairingSchema,
|
|
407
|
-
let code = row["code"] as? String,
|
|
408
|
-
let expiresAt = row["expires_at"] as? String,
|
|
409
|
-
let used = row["used"] as? Bool else {
|
|
410
|
-
throw TelegramPrivateFileError.invalidStoredValue
|
|
411
|
-
}
|
|
412
|
-
return PairingState(schema: schema, code: code, expiresAt: expiresAt, used: used, raw: row)
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
private func decodeChat(_ row: [String: Any]) throws -> AuthorizedChat {
|
|
416
|
-
guard let chatID = telegramInt64(row["chat_id"]),
|
|
417
|
-
let senderID = telegramInt64(row["sender_id"]),
|
|
418
|
-
chatID > 0,
|
|
419
|
-
senderID > 0,
|
|
420
|
-
let pairedAt = row["paired_at"] as? String,
|
|
421
|
-
row["active"] == nil || row["active"] is Bool else {
|
|
422
|
-
throw TelegramPrivateFileError.invalidStoredValue
|
|
423
|
-
}
|
|
424
|
-
return AuthorizedChat(
|
|
425
|
-
chatID: chatID, senderID: senderID, pairedAt: pairedAt,
|
|
426
|
-
active: row["active"] as? Bool ?? true, raw: row
|
|
427
|
-
)
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
private func decodeConfirmation(_ row: [String: Any]) throws -> ConfirmationState {
|
|
431
|
-
guard let nonce = row["nonce"] as? String,
|
|
432
|
-
let chatID = telegramInt64(row["chat_id"]),
|
|
433
|
-
let senderID = telegramInt64(row["sender_id"]),
|
|
434
|
-
chatID > 0,
|
|
435
|
-
senderID > 0,
|
|
436
|
-
let command = row["command"] as? String,
|
|
437
|
-
let inputJSON = row["input_json"] as? String,
|
|
438
|
-
let expiresAt = row["expires_at"] as? String else {
|
|
439
|
-
throw TelegramPrivateFileError.invalidStoredValue
|
|
440
|
-
}
|
|
441
|
-
return ConfirmationState(
|
|
442
|
-
nonce: nonce,
|
|
443
|
-
chatID: chatID,
|
|
444
|
-
senderID: senderID,
|
|
445
|
-
command: command,
|
|
446
|
-
inputJSON: inputJSON,
|
|
447
|
-
expiresAt: expiresAt,
|
|
448
|
-
raw: row
|
|
449
|
-
)
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
private func encodePairing(_ pairing: PairingState) -> [String: Any] {
|
|
453
|
-
var row = pairing.raw
|
|
454
|
-
row["schema"] = Self.pairingSchema
|
|
455
|
-
row["code"] = pairing.code
|
|
456
|
-
row["expires_at"] = pairing.expiresAt
|
|
457
|
-
row["used"] = pairing.used
|
|
458
|
-
return row
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
private func encodeChat(_ chat: AuthorizedChat) -> [String: Any] {
|
|
462
|
-
var row = chat.raw
|
|
463
|
-
row["chat_id"] = chat.chatID
|
|
464
|
-
row["sender_id"] = chat.senderID
|
|
465
|
-
row["paired_at"] = chat.pairedAt
|
|
466
|
-
row["active"] = chat.active
|
|
467
|
-
return row
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
private func encodeConfirmation(_ confirmation: ConfirmationState) -> [String: Any] {
|
|
471
|
-
var row = confirmation.raw
|
|
472
|
-
row["nonce"] = confirmation.nonce
|
|
473
|
-
row["chat_id"] = confirmation.chatID
|
|
474
|
-
row["sender_id"] = confirmation.senderID
|
|
475
|
-
row["command"] = confirmation.command
|
|
476
|
-
row["input_json"] = confirmation.inputJSON
|
|
477
|
-
row["expires_at"] = confirmation.expiresAt
|
|
478
|
-
return row
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
private func telegramInt64(_ value: Any?) -> Int64? {
|
|
483
|
-
guard let number = value as? NSNumber,
|
|
484
|
-
CFGetTypeID(number) != CFBooleanGetTypeID() else { return nil }
|
|
485
|
-
return Int64(number.stringValue)
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
private func parseTelegramDate(_ value: String) -> Date? {
|
|
489
|
-
let fractional = ISO8601DateFormatter()
|
|
490
|
-
fractional.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
491
|
-
return fractional.date(from: value) ?? ISO8601DateFormatter().date(from: value)
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
private func telegramPrivateStoreDate(_ date: Date) -> String {
|
|
495
|
-
let formatter = ISO8601DateFormatter()
|
|
496
|
-
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
497
|
-
return formatter.string(from: date)
|
|
498
|
-
}
|