@trillboards/edge-sdk 0.2.135 → 0.2.137
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/deploy/docker/README.md +0 -11
- package/deploy/macos/README.md +7 -1
- package/deploy/macos/TrillboardsSetupApp.m +407 -14
- package/deploy/macos/pair.command +6 -1
- package/deploy/windows/trillboards-pair.cmd +7 -1
- package/deploy/windows/trillboards-setup.ps1 +349 -16
- package/dist/EdgeAgent.js +1 -1
- package/dist/InstallerOnboardingClient.d.ts +102 -0
- package/dist/InstallerOnboardingClient.d.ts.map +1 -0
- package/dist/InstallerOnboardingClient.js +219 -0
- package/dist/InstallerOnboardingClient.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +37 -0
- package/dist/cli.js.map +1 -1
- package/dist/diagnose.d.ts +1 -1
- package/dist/diagnose.js +1 -1
- package/dist/installerOnboardingMode.d.ts +19 -0
- package/dist/installerOnboardingMode.d.ts.map +1 -0
- package/dist/installerOnboardingMode.js +360 -0
- package/dist/installerOnboardingMode.js.map +1 -0
- package/dist/setupMode.d.ts.map +1 -1
- package/dist/setupMode.js +17 -5
- package/dist/setupMode.js.map +1 -1
- package/dist/setupProgress.d.ts +25 -1
- package/dist/setupProgress.d.ts.map +1 -1
- package/dist/setupProgress.js +43 -0
- package/dist/setupProgress.js.map +1 -1
- package/package.json +1 -1
|
@@ -22,6 +22,8 @@ $script:LastStage = 'welcome'
|
|
|
22
22
|
$script:SetupStarted = $false
|
|
23
23
|
$script:PortalUrl = 'https://app.senseanything.com/sense/cameras'
|
|
24
24
|
$script:PairProcess = $null
|
|
25
|
+
$script:SetupInput = $null
|
|
26
|
+
$script:AuthMode = 'signup'
|
|
25
27
|
$script:SetupMutex = $null
|
|
26
28
|
$script:UpdateTask = $null
|
|
27
29
|
$script:UpdateClient = $null
|
|
@@ -31,7 +33,7 @@ $UpdateManifestUrl = 'https://screen.trillboards.com/connector/trillboards-conne
|
|
|
31
33
|
$InstalledVersion = try {
|
|
32
34
|
[string](Get-ItemPropertyValue -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\TrillboardsEdge' -Name DisplayVersion -ErrorAction Stop)
|
|
33
35
|
} catch { '0.0.0' }
|
|
34
|
-
$StepNames = @('Welcome', 'Account', 'Recorder', 'Cameras', 'Local AI', 'Ready')
|
|
36
|
+
$StepNames = @('Welcome', 'Account', 'Account setup', 'Plan & Payment', 'Recorder', 'Cameras', 'Local AI', 'Ready')
|
|
35
37
|
$StepLabels = New-Object System.Collections.Generic.List[System.Windows.Forms.Label]
|
|
36
38
|
|
|
37
39
|
function New-Label {
|
|
@@ -54,25 +56,32 @@ function Get-StateValue {
|
|
|
54
56
|
|
|
55
57
|
function Get-StepIndex([string]$Stage) {
|
|
56
58
|
switch ($Stage) {
|
|
57
|
-
{ $_ -in @('
|
|
58
|
-
|
|
59
|
-
'
|
|
60
|
-
'
|
|
61
|
-
'
|
|
62
|
-
{ $_ -in @('
|
|
63
|
-
'
|
|
59
|
+
{ $_ -in @('onboarding_account', 'onboarding_google') } { return 1 }
|
|
60
|
+
'onboarding_choose_account' { return 2 }
|
|
61
|
+
'onboarding_terms' { return 2 }
|
|
62
|
+
{ $_ -in @('onboarding_plan', 'onboarding_checkout') } { return 3 }
|
|
63
|
+
{ $_ -in @('onboarding_install', 'starting', 'waiting_for_account', 'account_attention', 'account_connected', 'discovering', 'devices_found') } { return 4 }
|
|
64
|
+
{ $_ -in @('validating', 'cameras_created') } { return 5 }
|
|
65
|
+
{ $_ -in @('local_ai', 'local_ai_error', 'starting_service') } { return 6 }
|
|
66
|
+
'ready' { return 7 }
|
|
64
67
|
default { return 0 }
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
function Get-ProgressValue([string]$Stage, $State) {
|
|
69
72
|
switch ($Stage) {
|
|
70
|
-
'
|
|
71
|
-
'
|
|
72
|
-
'
|
|
73
|
-
'
|
|
74
|
-
'
|
|
75
|
-
'
|
|
73
|
+
'onboarding_account' { return 10 }
|
|
74
|
+
'onboarding_google' { return 12 }
|
|
75
|
+
'onboarding_terms' { return 25 }
|
|
76
|
+
'onboarding_plan' { return 42 }
|
|
77
|
+
'onboarding_checkout' { return 50 }
|
|
78
|
+
'onboarding_install' { return 65 }
|
|
79
|
+
'waiting_for_account' { return 65 }
|
|
80
|
+
'account_attention' { return 65 }
|
|
81
|
+
'account_connected' { return 65 }
|
|
82
|
+
'discovering' { return 68 }
|
|
83
|
+
'devices_found' { return 70 }
|
|
84
|
+
'validating' { return 75 }
|
|
76
85
|
'cameras_created' { return 80 }
|
|
77
86
|
'local_ai' {
|
|
78
87
|
$localPercent = Get-StateValue $State 'localAiProgressPercent'
|
|
@@ -215,7 +224,248 @@ $CloseAction.Size = New-Object System.Drawing.Size(100, 42)
|
|
|
215
224
|
$CloseAction.Visible = $false
|
|
216
225
|
$Form.Controls.Add($CloseAction)
|
|
217
226
|
|
|
227
|
+
# Native onboarding controls
|
|
228
|
+
# These panels are real WinForms controls rather than a webview. Google and
|
|
229
|
+
# Stripe alone open the system browser for provider-owned secure UI, then the
|
|
230
|
+
# long-lived connector process resumes this window automatically.
|
|
231
|
+
$OnboardingPanels = New-Object System.Collections.Generic.List[System.Windows.Forms.Panel]
|
|
232
|
+
|
|
233
|
+
function New-OnboardingPanel {
|
|
234
|
+
$panel = New-Object System.Windows.Forms.Panel
|
|
235
|
+
$panel.Location = New-Object System.Drawing.Point(236, 218)
|
|
236
|
+
$panel.Size = New-Object System.Drawing.Size(465, 236)
|
|
237
|
+
$panel.BackColor = [System.Drawing.Color]::White
|
|
238
|
+
$panel.Visible = $false
|
|
239
|
+
$Form.Controls.Add($panel)
|
|
240
|
+
$OnboardingPanels.Add($panel)
|
|
241
|
+
return $panel
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function New-TextBox {
|
|
245
|
+
param([System.Windows.Forms.Control]$Parent, [int]$X, [int]$Y, [int]$Width, [bool]$Password = $false)
|
|
246
|
+
$box = New-Object System.Windows.Forms.TextBox
|
|
247
|
+
$box.Location = New-Object System.Drawing.Point($X, $Y)
|
|
248
|
+
$box.Size = New-Object System.Drawing.Size($Width, 28)
|
|
249
|
+
$box.Font = New-Object System.Drawing.Font('Segoe UI', 10)
|
|
250
|
+
$box.UseSystemPasswordChar = $Password
|
|
251
|
+
$Parent.Controls.Add($box)
|
|
252
|
+
return $box
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function New-PanelButton {
|
|
256
|
+
param([System.Windows.Forms.Control]$Parent, [string]$Text, [int]$X, [int]$Y, [int]$Width, [bool]$Primary = $false)
|
|
257
|
+
$button = New-Object System.Windows.Forms.Button
|
|
258
|
+
$button.Text = $Text
|
|
259
|
+
$button.Location = New-Object System.Drawing.Point($X, $Y)
|
|
260
|
+
$button.Size = New-Object System.Drawing.Size($Width, 34)
|
|
261
|
+
$button.Font = New-Object System.Drawing.Font('Segoe UI', 9.5, $(if ($Primary) { [System.Drawing.FontStyle]::Bold } else { [System.Drawing.FontStyle]::Regular }))
|
|
262
|
+
if ($Primary) {
|
|
263
|
+
$button.BackColor = [System.Drawing.Color]::FromArgb(15, 107, 90)
|
|
264
|
+
$button.ForeColor = [System.Drawing.Color]::White
|
|
265
|
+
$button.FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
|
|
266
|
+
$button.FlatAppearance.BorderSize = 0
|
|
267
|
+
}
|
|
268
|
+
$Parent.Controls.Add($button)
|
|
269
|
+
return $button
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
$AccountPanel = New-OnboardingPanel
|
|
273
|
+
$GoogleButton = New-PanelButton -Parent $AccountPanel -Text 'Continue with Google' -X 0 -Y 0 -Width 465
|
|
274
|
+
$AccountDivider = New-Label -Text 'or use email' -Size 8.5
|
|
275
|
+
$AccountDivider.ForeColor = [System.Drawing.Color]::FromArgb(104, 119, 127)
|
|
276
|
+
$AccountDivider.TextAlign = [System.Drawing.ContentAlignment]::MiddleCenter
|
|
277
|
+
$AccountDivider.Location = New-Object System.Drawing.Point(0, 40)
|
|
278
|
+
$AccountDivider.Size = New-Object System.Drawing.Size(465, 20)
|
|
279
|
+
$AccountPanel.Controls.Add($AccountDivider)
|
|
280
|
+
|
|
281
|
+
$NameLabel = New-Label -Text 'Full name' -Size 8.5
|
|
282
|
+
$NameLabel.Location = New-Object System.Drawing.Point(0, 65)
|
|
283
|
+
$NameLabel.Size = New-Object System.Drawing.Size(210, 18)
|
|
284
|
+
$AccountPanel.Controls.Add($NameLabel)
|
|
285
|
+
$NameInput = New-TextBox -Parent $AccountPanel -X 0 -Y 84 -Width 220
|
|
286
|
+
|
|
287
|
+
$EmailLabel = New-Label -Text 'Work email' -Size 8.5
|
|
288
|
+
$EmailLabel.Location = New-Object System.Drawing.Point(235, 65)
|
|
289
|
+
$EmailLabel.Size = New-Object System.Drawing.Size(220, 18)
|
|
290
|
+
$AccountPanel.Controls.Add($EmailLabel)
|
|
291
|
+
$EmailInput = New-TextBox -Parent $AccountPanel -X 235 -Y 84 -Width 230
|
|
292
|
+
|
|
293
|
+
$PasswordLabel = New-Label -Text 'Create password' -Size 8.5
|
|
294
|
+
$PasswordLabel.Location = New-Object System.Drawing.Point(0, 121)
|
|
295
|
+
$PasswordLabel.Size = New-Object System.Drawing.Size(220, 18)
|
|
296
|
+
$AccountPanel.Controls.Add($PasswordLabel)
|
|
297
|
+
$PasswordInput = New-TextBox -Parent $AccountPanel -X 0 -Y 140 -Width 220 -Password $true
|
|
298
|
+
|
|
299
|
+
$AuthModeButton = New-PanelButton -Parent $AccountPanel -Text 'Already have an account? Sign in' -X 235 -Y 140 -Width 230
|
|
300
|
+
$AccountSubmit = New-PanelButton -Parent $AccountPanel -Text 'Create account' -X 285 -Y 193 -Width 180 -Primary $true
|
|
301
|
+
|
|
302
|
+
$TermsPanel = New-OnboardingPanel
|
|
303
|
+
$TermsSummary = New-Label -Text 'Your account must accept the current Sense Anything terms, privacy notice, earner agreement, and data processing addendum.' -Size 10
|
|
304
|
+
$TermsSummary.ForeColor = [System.Drawing.Color]::FromArgb(63, 77, 84)
|
|
305
|
+
$TermsSummary.Location = New-Object System.Drawing.Point(0, 4)
|
|
306
|
+
$TermsSummary.Size = New-Object System.Drawing.Size(465, 54)
|
|
307
|
+
$TermsPanel.Controls.Add($TermsSummary)
|
|
308
|
+
$TermsCheck = New-Object System.Windows.Forms.CheckBox
|
|
309
|
+
$TermsCheck.Text = 'I reviewed and accept the current agreements shown for this account.'
|
|
310
|
+
$TermsCheck.Font = New-Object System.Drawing.Font('Segoe UI', 9.5)
|
|
311
|
+
$TermsCheck.Location = New-Object System.Drawing.Point(0, 75)
|
|
312
|
+
$TermsCheck.Size = New-Object System.Drawing.Size(465, 44)
|
|
313
|
+
$TermsPanel.Controls.Add($TermsCheck)
|
|
314
|
+
$TermsVersion = New-Label -Text '' -Size 8.5
|
|
315
|
+
$TermsVersion.ForeColor = [System.Drawing.Color]::FromArgb(104, 119, 127)
|
|
316
|
+
$TermsVersion.Location = New-Object System.Drawing.Point(24, 119)
|
|
317
|
+
$TermsVersion.Size = New-Object System.Drawing.Size(430, 30)
|
|
318
|
+
$TermsPanel.Controls.Add($TermsVersion)
|
|
319
|
+
$TermsReview = New-PanelButton -Parent $TermsPanel -Text 'Review terms' -X 0 -Y 150 -Width 145
|
|
320
|
+
$PrivacyReview = New-PanelButton -Parent $TermsPanel -Text 'Review privacy' -X 150 -Y 150 -Width 145
|
|
321
|
+
$DpaReview = New-PanelButton -Parent $TermsPanel -Text 'Review DPA' -X 300 -Y 150 -Width 145
|
|
322
|
+
$TermsSubmit = New-PanelButton -Parent $TermsPanel -Text 'Accept & continue' -X 285 -Y 193 -Width 180 -Primary $true
|
|
323
|
+
$TermsSubmit.Enabled = $false
|
|
324
|
+
|
|
325
|
+
$PlanPanel = New-OnboardingPanel
|
|
326
|
+
$MonthlyRadio = New-Object System.Windows.Forms.RadioButton
|
|
327
|
+
$MonthlyRadio.Text = 'Monthly'
|
|
328
|
+
$MonthlyRadio.Checked = $true
|
|
329
|
+
$MonthlyRadio.Location = New-Object System.Drawing.Point(0, 0)
|
|
330
|
+
$MonthlyRadio.Size = New-Object System.Drawing.Size(100, 28)
|
|
331
|
+
$PlanPanel.Controls.Add($MonthlyRadio)
|
|
332
|
+
$AnnualRadio = New-Object System.Windows.Forms.RadioButton
|
|
333
|
+
$AnnualRadio.Text = 'Annual'
|
|
334
|
+
$AnnualRadio.Location = New-Object System.Drawing.Point(105, 0)
|
|
335
|
+
$AnnualRadio.Size = New-Object System.Drawing.Size(100, 28)
|
|
336
|
+
$PlanPanel.Controls.Add($AnnualRadio)
|
|
337
|
+
|
|
338
|
+
$StandardRadio = New-Object System.Windows.Forms.RadioButton
|
|
339
|
+
$StandardRadio.Checked = $true
|
|
340
|
+
$StandardRadio.Location = New-Object System.Drawing.Point(0, 45)
|
|
341
|
+
$StandardRadio.Size = New-Object System.Drawing.Size(22, 24)
|
|
342
|
+
$PlanPanel.Controls.Add($StandardRadio)
|
|
343
|
+
$StandardPlan = New-Label -Text 'Sense Anything' -Size 11 -Style ([System.Drawing.FontStyle]::Bold)
|
|
344
|
+
$StandardPlan.Location = New-Object System.Drawing.Point(28, 42)
|
|
345
|
+
$StandardPlan.Size = New-Object System.Drawing.Size(205, 52)
|
|
346
|
+
$PlanPanel.Controls.Add($StandardPlan)
|
|
347
|
+
|
|
348
|
+
$MultiRadio = New-Object System.Windows.Forms.RadioButton
|
|
349
|
+
$MultiRadio.Location = New-Object System.Drawing.Point(238, 45)
|
|
350
|
+
$MultiRadio.Size = New-Object System.Drawing.Size(22, 24)
|
|
351
|
+
$PlanPanel.Controls.Add($MultiRadio)
|
|
352
|
+
$MultiPlan = New-Label -Text 'Multi-site' -Size 11 -Style ([System.Drawing.FontStyle]::Bold)
|
|
353
|
+
$MultiPlan.Location = New-Object System.Drawing.Point(266, 42)
|
|
354
|
+
$MultiPlan.Size = New-Object System.Drawing.Size(199, 52)
|
|
355
|
+
$PlanPanel.Controls.Add($MultiPlan)
|
|
356
|
+
|
|
357
|
+
$PlanNote = New-Label -Text 'Checkout is securely handled by Stripe. This installer resumes automatically when your plan is active.' -Size 8.5
|
|
358
|
+
$PlanNote.ForeColor = [System.Drawing.Color]::FromArgb(104, 119, 127)
|
|
359
|
+
$PlanNote.Location = New-Object System.Drawing.Point(0, 112)
|
|
360
|
+
$PlanNote.Size = New-Object System.Drawing.Size(465, 45)
|
|
361
|
+
$PlanPanel.Controls.Add($PlanNote)
|
|
362
|
+
$PlanSubmit = New-PanelButton -Parent $PlanPanel -Text 'Continue to checkout' -X 265 -Y 185 -Width 200 -Primary $true
|
|
363
|
+
|
|
364
|
+
$ChooseAccountPanel = New-OnboardingPanel
|
|
365
|
+
$AccountPicker = New-Object System.Windows.Forms.ComboBox
|
|
366
|
+
$AccountPicker.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
|
|
367
|
+
$AccountPicker.Location = New-Object System.Drawing.Point(0, 45)
|
|
368
|
+
$AccountPicker.Size = New-Object System.Drawing.Size(465, 32)
|
|
369
|
+
$ChooseAccountPanel.Controls.Add($AccountPicker)
|
|
370
|
+
$ChooseAccountSubmit = New-PanelButton -Parent $ChooseAccountPanel -Text 'Continue with this account' -X 215 -Y 185 -Width 250 -Primary $true
|
|
371
|
+
$ChooseAccountSubmit.Add_Click({
|
|
372
|
+
if ($AccountPicker.SelectedIndex -lt 0) { return }
|
|
373
|
+
$owner = $script:AccountChoices[$AccountPicker.SelectedIndex].ownerUserId
|
|
374
|
+
$ChooseAccountSubmit.Enabled = $false
|
|
375
|
+
Send-OnboardingCommand @{ action = 'select_account'; ownerUserId = $owner }
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
function Set-OnboardingPanel($Panel) {
|
|
379
|
+
foreach ($item in $OnboardingPanels) { $item.Visible = ($item -eq $Panel) }
|
|
380
|
+
$showStatus = $null -eq $Panel
|
|
381
|
+
$StatusBox.Visible = $showStatus
|
|
382
|
+
$Metric.Visible = $showStatus
|
|
383
|
+
$Progress.Visible = $showStatus
|
|
384
|
+
$UpdateAction.Visible = $showStatus
|
|
385
|
+
$UpdateStatus.Visible = $showStatus
|
|
386
|
+
$Privacy.Visible = $showStatus
|
|
387
|
+
$Action.Visible = $showStatus
|
|
388
|
+
$CloseAction.Visible = $false
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function Send-OnboardingCommand($Payload) {
|
|
392
|
+
if ($null -eq $script:SetupInput) { return }
|
|
393
|
+
try {
|
|
394
|
+
$json = $Payload | ConvertTo-Json -Compress
|
|
395
|
+
$script:SetupInput.WriteLine($json)
|
|
396
|
+
$script:SetupInput.Flush()
|
|
397
|
+
} catch {
|
|
398
|
+
Show-LocalError 'Could not continue setup' 'Close and reopen Sense Anything Connector, then try again.'
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function Update-AuthMode {
|
|
403
|
+
$signingIn = $script:AuthMode -eq 'signin'
|
|
404
|
+
$NameLabel.Visible = -not $signingIn
|
|
405
|
+
$NameInput.Visible = -not $signingIn
|
|
406
|
+
$PasswordLabel.Text = if ($signingIn) { 'Password' } else { 'Create password' }
|
|
407
|
+
$AccountSubmit.Text = if ($signingIn) { 'Sign in & continue' } else { 'Create account' }
|
|
408
|
+
$AuthModeButton.Text = if ($signingIn) { 'New here? Create account' } else { 'Already have an account? Sign in' }
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
$AuthModeButton.Add_Click({
|
|
412
|
+
$script:AuthMode = if ($script:AuthMode -eq 'signin') { 'signup' } else { 'signin' }
|
|
413
|
+
Update-AuthMode
|
|
414
|
+
})
|
|
415
|
+
$GoogleButton.Add_Click({ Send-OnboardingCommand @{ action = 'google_start' } })
|
|
416
|
+
$AccountSubmit.Add_Click({
|
|
417
|
+
if ([string]::IsNullOrWhiteSpace($EmailInput.Text) -or [string]::IsNullOrWhiteSpace($PasswordInput.Text)) {
|
|
418
|
+
[System.Windows.Forms.MessageBox]::Show(
|
|
419
|
+
'Enter your email and password to continue.',
|
|
420
|
+
'Sense Anything',
|
|
421
|
+
[System.Windows.Forms.MessageBoxButtons]::OK,
|
|
422
|
+
[System.Windows.Forms.MessageBoxIcon]::Information
|
|
423
|
+
) | Out-Null
|
|
424
|
+
return
|
|
425
|
+
}
|
|
426
|
+
if ($script:AuthMode -eq 'signup' -and ([string]::IsNullOrWhiteSpace($NameInput.Text) -or $PasswordInput.Text.Length -lt 8)) {
|
|
427
|
+
[System.Windows.Forms.MessageBox]::Show(
|
|
428
|
+
'Enter your name and a password with at least 8 characters.',
|
|
429
|
+
'Sense Anything',
|
|
430
|
+
[System.Windows.Forms.MessageBoxButtons]::OK,
|
|
431
|
+
[System.Windows.Forms.MessageBoxIcon]::Information
|
|
432
|
+
) | Out-Null
|
|
433
|
+
return
|
|
434
|
+
}
|
|
435
|
+
if ($script:AuthMode -eq 'signin') {
|
|
436
|
+
Send-OnboardingCommand @{ action = 'email_sign_in'; email = $EmailInput.Text; password = $PasswordInput.Text }
|
|
437
|
+
} else {
|
|
438
|
+
Send-OnboardingCommand @{ action = 'email_sign_up'; name = $NameInput.Text; email = $EmailInput.Text; password = $PasswordInput.Text }
|
|
439
|
+
}
|
|
440
|
+
$PasswordInput.Clear()
|
|
441
|
+
})
|
|
442
|
+
function Open-LegalDocument([string]$Url) {
|
|
443
|
+
try { Start-Process $Url } catch {
|
|
444
|
+
[System.Windows.Forms.MessageBox]::Show(
|
|
445
|
+
"Open this address in your browser:`r`n$Url",
|
|
446
|
+
'Sense Anything agreements',
|
|
447
|
+
[System.Windows.Forms.MessageBoxButtons]::OK,
|
|
448
|
+
[System.Windows.Forms.MessageBoxIcon]::Information
|
|
449
|
+
) | Out-Null
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
$TermsCheck.Add_CheckedChanged({ $TermsSubmit.Enabled = $TermsCheck.Checked })
|
|
454
|
+
$TermsReview.Add_Click({ Open-LegalDocument 'https://trillboards.com/legal/terms' })
|
|
455
|
+
$PrivacyReview.Add_Click({ Open-LegalDocument 'https://trillboards.com/legal/privacy-policy' })
|
|
456
|
+
$DpaReview.Add_Click({ Open-LegalDocument 'https://trillboards.com/legal/data-processing' })
|
|
457
|
+
$TermsSubmit.Add_Click({
|
|
458
|
+
if ($TermsCheck.Checked) { Send-OnboardingCommand @{ action = 'accept_terms' }; $TermsSubmit.Enabled = $false }
|
|
459
|
+
})
|
|
460
|
+
$PlanSubmit.Add_Click({
|
|
461
|
+
$tier = if ($MultiRadio.Checked) { [string]$MultiRadio.Tag } else { [string]$StandardRadio.Tag }
|
|
462
|
+
if ([string]::IsNullOrWhiteSpace($tier)) { return }
|
|
463
|
+
$interval = if ($AnnualRadio.Checked) { 'annual' } else { 'monthly' }
|
|
464
|
+
Send-OnboardingCommand @{ action = 'select_plan'; tier = $tier; interval = $interval }
|
|
465
|
+
})
|
|
466
|
+
|
|
218
467
|
function Show-Welcome {
|
|
468
|
+
Set-OnboardingPanel $null
|
|
219
469
|
$script:LastStage = 'welcome'
|
|
220
470
|
$Headline.Text = 'Set up this computer for CCTV'
|
|
221
471
|
$Detail.Text = 'Connect your Sense Anything account, find your recorder, and verify every camera before screens are created.'
|
|
@@ -229,6 +479,7 @@ function Show-Welcome {
|
|
|
229
479
|
}
|
|
230
480
|
|
|
231
481
|
function Show-LocalError([string]$Title, [string]$Message) {
|
|
482
|
+
Set-OnboardingPanel $null
|
|
232
483
|
$script:LastStage = 'error'
|
|
233
484
|
$Headline.Text = $Title
|
|
234
485
|
$Detail.Text = $Message
|
|
@@ -379,9 +630,11 @@ function Start-Setup {
|
|
|
379
630
|
$startInfo.Arguments = ('/d /s /c ""{0}" --setup --status-file "{1}" >> "{2}" 2>&1"' -f $Pairer, $StatePath, $SetupLogPath)
|
|
380
631
|
$startInfo.UseShellExecute = $false
|
|
381
632
|
$startInfo.CreateNoWindow = $true
|
|
633
|
+
$startInfo.RedirectStandardInput = $true
|
|
382
634
|
$startInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
|
|
383
635
|
try {
|
|
384
636
|
$script:PairProcess = [System.Diagnostics.Process]::Start($startInfo)
|
|
637
|
+
$script:SetupInput = $script:PairProcess.StandardInput
|
|
385
638
|
} catch {
|
|
386
639
|
Show-LocalError 'Could not start setup' 'Reinstall the Sense Anything Connector, then try again.'
|
|
387
640
|
}
|
|
@@ -417,6 +670,10 @@ function Start-LocalAiSetup {
|
|
|
417
670
|
function Stop-SetupProcess {
|
|
418
671
|
$process = $script:PairProcess
|
|
419
672
|
$script:PairProcess = $null
|
|
673
|
+
if ($null -ne $script:SetupInput) {
|
|
674
|
+
try { $script:SetupInput.Dispose() } catch { }
|
|
675
|
+
$script:SetupInput = $null
|
|
676
|
+
}
|
|
420
677
|
if ($null -eq $process) { return }
|
|
421
678
|
try {
|
|
422
679
|
if (-not $process.HasExited) {
|
|
@@ -462,6 +719,7 @@ function Resume-BackgroundSensing {
|
|
|
462
719
|
function Render-State($State) {
|
|
463
720
|
$stage = [string](Get-StateValue $State 'stage')
|
|
464
721
|
if (-not $stage) { return }
|
|
722
|
+
$previousStage = $script:LastStage
|
|
465
723
|
$script:LastStage = $stage
|
|
466
724
|
$url = Get-StateValue $State 'portalUrl'
|
|
467
725
|
if ($url) { $script:PortalUrl = [string]$url }
|
|
@@ -476,7 +734,73 @@ function Render-State($State) {
|
|
|
476
734
|
$localAiDownloaded = Get-StateValue $State 'localAiDownloadedBytes'
|
|
477
735
|
$localAiTotal = Get-StateValue $State 'localAiTotalBytes'
|
|
478
736
|
|
|
479
|
-
if ($stage -
|
|
737
|
+
if ($stage -eq 'onboarding_account') {
|
|
738
|
+
Set-OnboardingPanel $AccountPanel
|
|
739
|
+
Update-Steps 1
|
|
740
|
+
$AccountSubmit.Enabled = $true
|
|
741
|
+
$GoogleButton.Enabled = $true
|
|
742
|
+
return
|
|
743
|
+
}
|
|
744
|
+
if ($stage -eq 'onboarding_choose_account') {
|
|
745
|
+
Set-OnboardingPanel $ChooseAccountPanel
|
|
746
|
+
Update-Steps 2
|
|
747
|
+
$AccountPicker.Items.Clear()
|
|
748
|
+
$script:AccountChoices = @(Get-StateValue $State 'accounts')
|
|
749
|
+
foreach ($account in $script:AccountChoices) { [void]$AccountPicker.Items.Add("$($account.label) ($($account.role))") }
|
|
750
|
+
if ($script:AccountChoices.Count -gt 0) { $AccountPicker.SelectedIndex = 0 }
|
|
751
|
+
$ChooseAccountSubmit.Enabled = $script:AccountChoices.Count -gt 0
|
|
752
|
+
return
|
|
753
|
+
}
|
|
754
|
+
if ($stage -eq 'onboarding_terms') {
|
|
755
|
+
Set-OnboardingPanel $TermsPanel
|
|
756
|
+
Update-Steps 2
|
|
757
|
+
if ($previousStage -ne 'onboarding_terms') {
|
|
758
|
+
$TermsCheck.Checked = $false
|
|
759
|
+
$TermsSubmit.Enabled = $false
|
|
760
|
+
}
|
|
761
|
+
$TermsSubmit.Enabled = $TermsCheck.Checked
|
|
762
|
+
$agreements = Get-StateValue $State 'requiredAgreements'
|
|
763
|
+
if ($agreements) {
|
|
764
|
+
$TermsVersion.Text = (($agreements | ForEach-Object {
|
|
765
|
+
$label = if ($_.label) { $_.label } else { ([string]$_.type).Replace('_', ' ') }
|
|
766
|
+
"$label v$($_.version)"
|
|
767
|
+
}) -join ' | ')
|
|
768
|
+
}
|
|
769
|
+
return
|
|
770
|
+
}
|
|
771
|
+
if ($stage -eq 'onboarding_plan') {
|
|
772
|
+
Set-OnboardingPanel $PlanPanel
|
|
773
|
+
Update-Steps 3
|
|
774
|
+
$plans = @(Get-StateValue $State 'plans')
|
|
775
|
+
if ($plans.Count -gt 0) {
|
|
776
|
+
$first = $plans[0]
|
|
777
|
+
$StandardRadio.Tag = [string]$first.tier
|
|
778
|
+
$StandardPlan.Text = "$($first.label)`r`n$($first.monthly)/month | $($first.annual)/year"
|
|
779
|
+
$StandardRadio.Visible = $true
|
|
780
|
+
$StandardPlan.Visible = $true
|
|
781
|
+
}
|
|
782
|
+
if ($plans.Count -gt 1) {
|
|
783
|
+
$second = $plans[1]
|
|
784
|
+
$MultiRadio.Tag = [string]$second.tier
|
|
785
|
+
$MultiPlan.Text = "$($second.label)`r`n$($second.monthly)/month | $($second.annual)/year"
|
|
786
|
+
$MultiRadio.Visible = $true
|
|
787
|
+
$MultiPlan.Visible = $true
|
|
788
|
+
} else {
|
|
789
|
+
$MultiRadio.Visible = $false
|
|
790
|
+
$MultiPlan.Visible = $false
|
|
791
|
+
}
|
|
792
|
+
return
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
Set-OnboardingPanel $null
|
|
796
|
+
|
|
797
|
+
if ($stage -eq 'onboarding_google') {
|
|
798
|
+
$StatusValue.Text = 'Waiting for Google'
|
|
799
|
+
} elseif ($stage -eq 'onboarding_checkout') {
|
|
800
|
+
$StatusValue.Text = 'Waiting for Stripe'
|
|
801
|
+
} elseif ($stage -eq 'onboarding_install') {
|
|
802
|
+
$StatusValue.Text = 'Onboarding complete'
|
|
803
|
+
} elseif ($stage -in @('waiting_for_account', 'account_attention')) {
|
|
480
804
|
$StatusValue.Text = [string](Get-StateValue $State 'displayCode')
|
|
481
805
|
} elseif ($stage -eq 'ready') {
|
|
482
806
|
$StatusValue.Text = 'Setup complete'
|
|
@@ -528,6 +852,9 @@ function Render-State($State) {
|
|
|
528
852
|
Update-Steps (Get-StepIndex $stage) ($stage -eq 'ready')
|
|
529
853
|
$CloseAction.Visible = $stage -eq 'ready'
|
|
530
854
|
switch ($stage) {
|
|
855
|
+
'onboarding_google' { $Action.Text = 'Use another sign-in'; $Action.Enabled = $true }
|
|
856
|
+
'onboarding_checkout' { $Action.Text = 'Back to plans'; $Action.Enabled = $true }
|
|
857
|
+
'onboarding_install' { $Action.Text = 'Preparing cameras...'; $Action.Enabled = $false }
|
|
531
858
|
'waiting_for_account' { $Action.Text = 'Open Sense Anything'; $Action.Enabled = $true }
|
|
532
859
|
'account_attention' { $Action.Text = 'Switch account'; $Action.Enabled = $true }
|
|
533
860
|
'local_ai_error' { $Action.Text = 'Retry local AI'; $Action.Enabled = $true }
|
|
@@ -547,6 +874,8 @@ $Action.Add_Click({
|
|
|
547
874
|
return
|
|
548
875
|
}
|
|
549
876
|
switch ($script:LastStage) {
|
|
877
|
+
'onboarding_google' { Send-OnboardingCommand @{ action = 'refresh' } }
|
|
878
|
+
'onboarding_checkout' { Send-OnboardingCommand @{ action = 'refresh' } }
|
|
550
879
|
'waiting_for_account' { Start-Process $script:PortalUrl }
|
|
551
880
|
'account_attention' { Start-Process $script:PortalUrl }
|
|
552
881
|
'local_ai_error' { Start-LocalAiSetup }
|
|
@@ -590,6 +919,10 @@ $Timer.Add_Tick({
|
|
|
590
919
|
}
|
|
591
920
|
if ($null -ne $script:PairProcess -and $script:PairProcess.HasExited) {
|
|
592
921
|
$exitCode = $script:PairProcess.ExitCode
|
|
922
|
+
if ($null -ne $script:SetupInput) {
|
|
923
|
+
try { $script:SetupInput.Dispose() } catch { }
|
|
924
|
+
$script:SetupInput = $null
|
|
925
|
+
}
|
|
593
926
|
$script:PairProcess.Dispose()
|
|
594
927
|
$script:PairProcess = $null
|
|
595
928
|
if ($exitCode -ne 0 -and $script:LastStage -notin @('local_ai', 'local_ai_error', 'ready', 'error')) {
|
|
@@ -599,7 +932,7 @@ $Timer.Add_Tick({
|
|
|
599
932
|
})
|
|
600
933
|
Show-Welcome
|
|
601
934
|
if ($SmokeTest) {
|
|
602
|
-
if ($StepLabels.Count -ne
|
|
935
|
+
if ($StepLabels.Count -ne 8 -or $Form.Controls.Count -lt 8 -or $Action.Text -ne 'Start setup') {
|
|
603
936
|
throw 'Windows setup UI did not construct its expected controls.'
|
|
604
937
|
}
|
|
605
938
|
$smokeStartInfo = New-Object System.Diagnostics.ProcessStartInfo
|
package/dist/EdgeAgent.js
CHANGED
|
@@ -44,7 +44,7 @@ const edge_ads_1 = require("@trillboards/edge-ads");
|
|
|
44
44
|
const CommandRouter_1 = require("./CommandRouter");
|
|
45
45
|
const config_1 = require("./config");
|
|
46
46
|
const dataDir_1 = require("./dataDir");
|
|
47
|
-
const SDK_VERSION = '0.2.
|
|
47
|
+
const SDK_VERSION = '0.2.137';
|
|
48
48
|
class EdgeAgent extends events_1.EventEmitter {
|
|
49
49
|
constructor(config, deps) {
|
|
50
50
|
super();
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { type HttpPoster } from './AudienceAnalyzeClient';
|
|
2
|
+
export interface InstallerAccount {
|
|
3
|
+
ownerUserId: string;
|
|
4
|
+
label: string;
|
|
5
|
+
role: string;
|
|
6
|
+
}
|
|
7
|
+
export type InstallerNextStep = 'terms' | 'plan' | 'install';
|
|
8
|
+
export interface InstallerAuthSession {
|
|
9
|
+
accessToken: string;
|
|
10
|
+
refreshToken: string;
|
|
11
|
+
user: {
|
|
12
|
+
id: string;
|
|
13
|
+
email: string;
|
|
14
|
+
name?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export interface InstallerOnboardingStatus {
|
|
18
|
+
account: {
|
|
19
|
+
id: string;
|
|
20
|
+
email: string;
|
|
21
|
+
name?: string | null;
|
|
22
|
+
};
|
|
23
|
+
agreements: {
|
|
24
|
+
all_current: boolean;
|
|
25
|
+
needs_acceptance: string[];
|
|
26
|
+
status: Record<string, {
|
|
27
|
+
current_version: string;
|
|
28
|
+
is_current: boolean;
|
|
29
|
+
}>;
|
|
30
|
+
};
|
|
31
|
+
entitlement: {
|
|
32
|
+
state: string;
|
|
33
|
+
tier: string | null;
|
|
34
|
+
billingInterval: string | null;
|
|
35
|
+
enrollmentAllowed: boolean;
|
|
36
|
+
};
|
|
37
|
+
legalComplete: boolean;
|
|
38
|
+
planActive: boolean;
|
|
39
|
+
onboardingComplete: boolean;
|
|
40
|
+
nextStep: InstallerNextStep;
|
|
41
|
+
}
|
|
42
|
+
export interface SenseCatalogue {
|
|
43
|
+
tiers: Record<string, {
|
|
44
|
+
key: string;
|
|
45
|
+
label: string;
|
|
46
|
+
standard: Record<'monthly' | 'annual', SensePrice | null>;
|
|
47
|
+
intro: Record<'monthly' | 'annual', SensePrice | null> | null;
|
|
48
|
+
}>;
|
|
49
|
+
introPlaces: {
|
|
50
|
+
total: number;
|
|
51
|
+
taken: number;
|
|
52
|
+
remaining: number;
|
|
53
|
+
soldOut: boolean;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export interface SensePrice {
|
|
57
|
+
amountDisplay: string;
|
|
58
|
+
interval: 'monthly' | 'annual';
|
|
59
|
+
licences: number;
|
|
60
|
+
}
|
|
61
|
+
export declare class InstallerApiError extends Error {
|
|
62
|
+
readonly statusCode: number;
|
|
63
|
+
readonly code: string | null;
|
|
64
|
+
constructor(message: string, statusCode: number, code?: string | null);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Cloud-facing half of installer onboarding. Both native shells use this one
|
|
68
|
+
* implementation so Google account resolution, legal-version checks, Stripe
|
|
69
|
+
* ownership binding, and next-step routing cannot drift by platform.
|
|
70
|
+
*/
|
|
71
|
+
export declare class InstallerOnboardingClient {
|
|
72
|
+
private readonly apiBaseUrl;
|
|
73
|
+
private readonly connectorFingerprint;
|
|
74
|
+
private readonly platform;
|
|
75
|
+
private readonly poster;
|
|
76
|
+
private readonly timeoutMs;
|
|
77
|
+
private auth;
|
|
78
|
+
private selectedOwnerUserId;
|
|
79
|
+
private accounts;
|
|
80
|
+
private googleHandoff;
|
|
81
|
+
constructor(options: {
|
|
82
|
+
apiBaseUrl: string;
|
|
83
|
+
connectorFingerprint: string;
|
|
84
|
+
platform?: NodeJS.Platform;
|
|
85
|
+
timeoutMs?: number;
|
|
86
|
+
}, poster?: HttpPoster);
|
|
87
|
+
get session(): InstallerAuthSession | null;
|
|
88
|
+
private request;
|
|
89
|
+
private setAuth;
|
|
90
|
+
signInWithEmail(email: string, password: string): Promise<InstallerAuthSession>;
|
|
91
|
+
createAccount(name: string, email: string, password: string): Promise<InstallerAuthSession>;
|
|
92
|
+
startGoogleSignIn(): Promise<string>;
|
|
93
|
+
pollGoogleSignIn(): Promise<InstallerAuthSession | null>;
|
|
94
|
+
getAccounts(): Promise<InstallerAccount[]>;
|
|
95
|
+
selectAccount(ownerUserId: string): void;
|
|
96
|
+
getOnboardingStatus(): Promise<InstallerOnboardingStatus>;
|
|
97
|
+
getCatalogue(): Promise<SenseCatalogue>;
|
|
98
|
+
acceptCurrentAgreements(status: InstallerOnboardingStatus): Promise<InstallerOnboardingStatus>;
|
|
99
|
+
startCheckout(tier: string, interval: 'monthly' | 'annual'): Promise<string>;
|
|
100
|
+
createPortalHandoff(targetUrl: string): Promise<string>;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=InstallerOnboardingClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InstallerOnboardingClient.d.ts","sourceRoot":"","sources":["../src/InstallerOnboardingClient.ts"],"names":[],"mappings":"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"}
|