@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.
@@ -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 @('waiting_for_account', 'account_attention') } { return 1 }
58
- { $_ -in @('account_connected', 'discovering') } { return 2 }
59
- 'devices_found' { return 3 }
60
- 'validating' { return 3 }
61
- 'cameras_created' { return 4 }
62
- { $_ -in @('local_ai', 'local_ai_error', 'starting_service') } { return 4 }
63
- 'ready' { return 5 }
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
- 'waiting_for_account' { return 18 }
71
- 'account_attention' { return 18 }
72
- 'account_connected' { return 30 }
73
- 'discovering' { return 42 }
74
- 'devices_found' { return 50 }
75
- 'validating' { return 68 }
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 -in @('waiting_for_account', 'account_attention')) {
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 6 -or $Form.Controls.Count -lt 8 -or $Action.Text -ne 'Start setup') {
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.135';
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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"}