Skip to content

Commit 78cd34f

Browse files
authored
Merge branch 'main' into cj/repackage
2 parents 2e41c94 + 5b1545f commit 78cd34f

41 files changed

Lines changed: 947 additions & 986 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

auth/credentials.mdx

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -311,17 +311,19 @@ const auth = await kernel.auth.connections.create({
311311

312312
const login = await kernel.auth.connections.login(auth.id);
313313

314-
// Poll until password is needed
315-
let state = await kernel.auth.connections.retrieve(auth.id);
316-
while (state.flow_status === 'IN_PROGRESS') {
317-
if (state.flow_step === 'AWAITING_INPUT' && state.discovered_fields?.length) {
314+
// Stream state changes and submit the missing password
315+
const authEvents = await kernel.auth.connections.follow(auth.id);
316+
for await (const event of authEvents) {
317+
if (
318+
event.event === 'managed_auth_state' &&
319+
event.flow_step === 'AWAITING_INPUT' &&
320+
event.discovered_fields?.length
321+
) {
318322
// Only password field will be pending (email auto-filled from credential)
319323
await kernel.auth.connections.submit(auth.id, {
320324
fields: { password: 'user-provided-password' }
321325
});
322326
}
323-
await new Promise(r => setTimeout(r, 2000));
324-
state = await kernel.auth.connections.retrieve(auth.id);
325327
}
326328
// TOTP auto-submitted from credential → SUCCESS
327329
```
@@ -342,17 +344,19 @@ auth = await kernel.auth.connections.create(
342344

343345
login = await kernel.auth.connections.login(auth.id)
344346

345-
# Poll until password is needed
346-
state = await kernel.auth.connections.retrieve(auth.id)
347-
while state.flow_status == "IN_PROGRESS":
348-
if state.flow_step == "AWAITING_INPUT" and state.discovered_fields:
347+
# Stream state changes and submit the missing password
348+
auth_events = await kernel.auth.connections.follow(auth.id)
349+
async for event in auth_events:
350+
if (
351+
event.event == "managed_auth_state"
352+
and event.flow_step == "AWAITING_INPUT"
353+
and event.discovered_fields
354+
):
349355
# Only password field will be pending (email auto-filled from credential)
350356
await kernel.auth.connections.submit(
351357
auth.id,
352358
fields={"password": "user-provided-password"},
353359
)
354-
await asyncio.sleep(2)
355-
state = await kernel.auth.connections.retrieve(auth.id)
356360
# TOTP auto-submitted from credential → SUCCESS
357361
```
358362

@@ -390,13 +394,11 @@ if err != nil {
390394
}
391395
_ = login
392396

393-
// Poll until password is needed
394-
state, err := client.Auth.Connections.Get(ctx, auth.ID)
395-
if err != nil {
396-
panic(err)
397-
}
398-
for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress {
399-
if state.FlowStep == kernel.ManagedAuthFlowStepAwaitingInput && len(state.DiscoveredFields) > 0 {
397+
// Stream state changes and submit the missing password
398+
authEvents := client.Auth.Connections.FollowStreaming(ctx, auth.ID)
399+
for authEvents.Next() {
400+
event := authEvents.Current()
401+
if event.Event == "managed_auth_state" && event.FlowStep == "AWAITING_INPUT" && len(event.DiscoveredFields) > 0 {
400402
// Only password field will be pending (email auto-filled from credential)
401403
_, err := client.Auth.Connections.Submit(ctx, auth.ID, kernel.AuthConnectionSubmitParams{
402404
SubmitFieldsRequest: kernel.SubmitFieldsRequestParam{
@@ -407,12 +409,9 @@ for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress {
407409
panic(err)
408410
}
409411
}
410-
411-
time.Sleep(2 * time.Second)
412-
state, err = client.Auth.Connections.Get(ctx, auth.ID)
413-
if err != nil {
414-
panic(err)
415-
}
412+
}
413+
if err := authEvents.Err(); err != nil {
414+
panic(err)
416415
}
417416
// TOTP auto-submitted from credential → SUCCESS
418417
```

auth/hosted-ui.mdx

Lines changed: 54 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -92,57 +92,60 @@ The user will:
9292
2. Enter their credentials
9393
3. Complete 2FA if needed
9494

95-
### 4. Poll for Completion
95+
### 4. Stream until completion
9696

97-
On your backend, poll until authentication completes:
97+
On your backend, follow the connection's SSE stream until authentication completes:
9898

9999
<CodeGroup>
100100
```typescript TypeScript
101-
let state = await kernel.auth.connections.retrieve(auth.id);
101+
const events = await kernel.auth.connections.follow(auth.id);
102+
let finalState;
102103

103-
while (state.flow_status === 'IN_PROGRESS') {
104-
await new Promise(r => setTimeout(r, 2000));
105-
state = await kernel.auth.connections.retrieve(auth.id);
104+
for await (const event of events) {
105+
if (event.event === 'managed_auth_state') {
106+
finalState = event;
107+
}
106108
}
107109

108-
if (state.status === 'AUTHENTICATED') {
110+
if (finalState?.flow_status === 'SUCCESS') {
109111
console.log('Authentication successful!');
110112
}
111113
```
112114

113115
```python Python
114-
state = await kernel.auth.connections.retrieve(auth.id)
116+
events = await kernel.auth.connections.follow(auth.id)
117+
final_state = None
115118

116-
while state.flow_status == "IN_PROGRESS":
117-
await asyncio.sleep(2)
118-
state = await kernel.auth.connections.retrieve(auth.id)
119+
async for event in events:
120+
if event.event == "managed_auth_state":
121+
final_state = event
119122

120-
if state.status == "AUTHENTICATED":
123+
if final_state and final_state.flow_status == "SUCCESS":
121124
print("Authentication successful!")
122125
```
123126

124127
```go Go
125-
state, err := client.Auth.Connections.Get(ctx, auth.ID)
126-
if err != nil {
127-
panic(err)
128-
}
128+
events := client.Auth.Connections.FollowStreaming(ctx, auth.ID)
129+
authenticated := false
129130

130-
for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress {
131-
time.Sleep(2 * time.Second)
132-
state, err = client.Auth.Connections.Get(ctx, auth.ID)
133-
if err != nil {
134-
panic(err)
131+
for events.Next() {
132+
event := events.Current()
133+
if event.Event == "managed_auth_state" && event.FlowStatus == "SUCCESS" {
134+
authenticated = true
135135
}
136136
}
137+
if err := events.Err(); err != nil {
138+
panic(err)
139+
}
137140

138-
if state.Status == kernel.ManagedAuthStatusAuthenticated {
141+
if authenticated {
139142
fmt.Println("Authentication successful!")
140143
}
141144
```
142145
</CodeGroup>
143146

144147
<Info>
145-
Poll every 2 seconds. The session expires after 20 minutes if not completed, and the flow times out after 10 minutes of waiting for user input.
148+
The SSE stream closes automatically when the flow succeeds, fails, expires, or is canceled. The session expires after 20 minutes if not completed, and the flow times out after 10 minutes of waiting for user input.
146149
</Info>
147150

148151
### 5. Use the Profile
@@ -216,14 +219,16 @@ const login = await kernel.auth.connections.login(auth.id);
216219
// Send user to hosted page
217220
console.log('Login URL:', login.hosted_url);
218221

219-
// Poll for completion
220-
let state = await kernel.auth.connections.retrieve(auth.id);
221-
while (state.flow_status === 'IN_PROGRESS') {
222-
await new Promise(r => setTimeout(r, 2000));
223-
state = await kernel.auth.connections.retrieve(auth.id);
222+
// Stream state changes until the flow completes
223+
const events = await kernel.auth.connections.follow(auth.id);
224+
let finalState;
225+
for await (const event of events) {
226+
if (event.event === 'managed_auth_state') {
227+
finalState = event;
228+
}
224229
}
225230

226-
if (state.status === 'AUTHENTICATED') {
231+
if (finalState?.flow_status === 'SUCCESS') {
227232
const browser = await kernel.browsers.create({
228233
profile: { name: 'doordash-user-123' },
229234
stealth: true,
@@ -235,10 +240,9 @@ if (state.status === 'AUTHENTICATED') {
235240
```
236241

237242
```python Python
238-
from kernel import Kernel
239-
import asyncio
243+
from kernel import AsyncKernel
240244

241-
kernel = Kernel()
245+
kernel = AsyncKernel()
242246

243247
# Create connection
244248
auth = await kernel.auth.connections.create(
@@ -252,13 +256,14 @@ login = await kernel.auth.connections.login(auth.id)
252256
# Send user to hosted page
253257
print(f"Login URL: {login.hosted_url}")
254258

255-
# Poll for completion
256-
state = await kernel.auth.connections.retrieve(auth.id)
257-
while state.flow_status == "IN_PROGRESS":
258-
await asyncio.sleep(2)
259-
state = await kernel.auth.connections.retrieve(auth.id)
259+
# Stream state changes until the flow completes
260+
events = await kernel.auth.connections.follow(auth.id)
261+
final_state = None
262+
async for event in events:
263+
if event.event == "managed_auth_state":
264+
final_state = event
260265

261-
if state.status == "AUTHENTICATED":
266+
if final_state and final_state.flow_status == "SUCCESS":
262267
browser = await kernel.browsers.create(
263268
profile={"name": "doordash-user-123"},
264269
stealth=True,
@@ -274,7 +279,6 @@ package main
274279
import (
275280
"context"
276281
"fmt"
277-
"time"
278282

279283
"github.com/kernel/kernel-go-sdk"
280284
"github.com/kernel/kernel-go-sdk/shared"
@@ -304,20 +308,20 @@ func main() {
304308
// Send user to hosted page
305309
fmt.Println("Login URL:", login.HostedURL)
306310

307-
// Poll for completion
308-
state, err := client.Auth.Connections.Get(ctx, auth.ID)
309-
if err != nil {
310-
panic(err)
311-
}
312-
for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress {
313-
time.Sleep(2 * time.Second)
314-
state, err = client.Auth.Connections.Get(ctx, auth.ID)
315-
if err != nil {
316-
panic(err)
311+
// Stream state changes until the flow completes
312+
events := client.Auth.Connections.FollowStreaming(ctx, auth.ID)
313+
authenticated := false
314+
for events.Next() {
315+
event := events.Current()
316+
if event.Event == "managed_auth_state" && event.FlowStatus == "SUCCESS" {
317+
authenticated = true
317318
}
318319
}
320+
if err := events.Err(); err != nil {
321+
panic(err)
322+
}
319323

320-
if state.Status == kernel.ManagedAuthStatusAuthenticated {
324+
if authenticated {
321325
browser, err := client.Browsers.New(ctx, kernel.BrowserNewParams{
322326
Profile: shared.BrowserProfileParam{
323327
Name: kernel.String("doordash-user-123"),

auth/overview.mdx

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,17 @@ const login = await kernel.auth.connections.login(auth.id);
5252
// Send user to login page
5353
console.log('Login URL:', login.hosted_url);
5454

55-
// Poll until complete
56-
let state = await kernel.auth.connections.retrieve(auth.id);
57-
while (state.flow_status === 'IN_PROGRESS') {
58-
await new Promise(r => setTimeout(r, 2000));
59-
state = await kernel.auth.connections.retrieve(auth.id);
55+
// Stream state changes until the flow completes
56+
const events = await kernel.auth.connections.follow(auth.id);
57+
let finalState;
58+
59+
for await (const event of events) {
60+
if (event.event === 'managed_auth_state') {
61+
finalState = event;
62+
}
6063
}
6164

62-
if (state.status === 'AUTHENTICATED') {
65+
if (finalState?.flow_status === 'SUCCESS') {
6366
console.log('Authenticated!');
6467
}
6568
```
@@ -70,13 +73,15 @@ login = await kernel.auth.connections.login(auth.id)
7073
# Send user to login page
7174
print(f"Login URL: {login.hosted_url}")
7275

73-
# Poll until complete
74-
state = await kernel.auth.connections.retrieve(auth.id)
75-
while state.flow_status == "IN_PROGRESS":
76-
await asyncio.sleep(2)
77-
state = await kernel.auth.connections.retrieve(auth.id)
76+
# Stream state changes until the flow completes
77+
events = await kernel.auth.connections.follow(auth.id)
78+
final_state = None
7879

79-
if state.status == "AUTHENTICATED":
80+
async for event in events:
81+
if event.event == "managed_auth_state":
82+
final_state = event
83+
84+
if final_state and final_state.flow_status == "SUCCESS":
8085
print("Authenticated!")
8186
```
8287

@@ -89,20 +94,21 @@ if err != nil {
8994
// Send user to login page
9095
fmt.Println("Login URL:", login.HostedURL)
9196

92-
// Poll until complete
93-
state, err := client.Auth.Connections.Get(ctx, auth.ID)
94-
if err != nil {
95-
panic(err)
96-
}
97-
for state.FlowStatus == kernel.ManagedAuthFlowStatusInProgress {
98-
time.Sleep(2 * time.Second)
99-
state, err = client.Auth.Connections.Get(ctx, auth.ID)
100-
if err != nil {
101-
panic(err)
97+
// Stream state changes until the flow completes
98+
events := client.Auth.Connections.FollowStreaming(ctx, auth.ID)
99+
authenticated := false
100+
101+
for events.Next() {
102+
event := events.Current()
103+
if event.Event == "managed_auth_state" && event.FlowStatus == "SUCCESS" {
104+
authenticated = true
102105
}
103106
}
107+
if err := events.Err(); err != nil {
108+
panic(err)
109+
}
104110

105-
if state.Status == kernel.ManagedAuthStatusAuthenticated {
111+
if authenticated {
106112
fmt.Println("Authenticated!")
107113
}
108114
```

auth/profiles.mdx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,26 @@ func main() {
6969
```
7070
</CodeGroup>
7171

72+
## Rename a profile
73+
74+
Profiles can be renamed without recreating their stored browser state. The new name must be unique within the project.
75+
76+
<CodeGroup>
77+
```typescript TypeScript
78+
await kernel.profiles.update('profiles-demo', { name: 'checkout-session' });
79+
```
80+
81+
```python Python
82+
kernel.profiles.update("profiles-demo", name="checkout-session")
83+
```
84+
85+
```go Go
86+
_, err := client.Profiles.Update(ctx, "profiles-demo", kernel.ProfileUpdateParams{
87+
Name: "checkout-session",
88+
})
89+
```
90+
</CodeGroup>
91+
7292
## 2. Start a browser session using the profile and save changes
7393

7494
After creating the profile, reference it by its `name` or `id` when creating a browser.
@@ -264,7 +284,7 @@ You cannot load a profile into a browser that was already created with a profile
264284
</Warning>
265285

266286
<Note>
267-
To use profiles with browser pools, read: [Can pooled browsers save changes back to a profile?](/browsers/pools/faq#can-pooled-browsers-save-changes-back-to-a-profile)
287+
To use profiles with browser pools, see [Profiles with browser pools](/browsers/pools#profiles-with-browser-pools)
268288
</Note>
269289

270290
## Other ways to use profiles
@@ -494,5 +514,5 @@ _ = browser
494514
- Profiles store cookies and local storage. Start the session with `save_changes: true` to write changes back when the browser is closed.
495515
- To keep a profile immutable for a run, omit `save_changes` (default) when creating the browser.
496516
- Multiple browsers in parallel can use the same profile, but only one browser should write (`save_changes: true`) to it at a time. Parallel browsers with `save_changes: true` may cause profile corruption and unpredictable behavior.
497-
- `save_changes` applies to a profile attached to a single browser — either at creation (`kernel.browsers.create()`) or loaded afterward with `kernel.browsers.update()`. A profile set on a [browser pool's](/browsers/pools/overview) config is loaded read-only and never persisted; `save_changes` sent on a pool's profile is silently ignored. To persist per-user state through a pool, attach the profile after acquiring the browser and release with `reuse: false` — see [Per-user profiles with pools](/browsers/pools/overview#per-user-profiles-with-pools).
517+
- `save_changes` applies to a profile attached to a single browser — either at creation (`kernel.browsers.create()`) or loaded afterward with `kernel.browsers.update()`. A profile set on a [browser pool's](/browsers/pools) config is loaded read-only and never persisted; `save_changes` sent on a pool's profile is silently ignored. To persist per-user state through a pool, attach the profile with `save_changes: true` after acquiring the browser and release with `reuse: false` — see [Per-user profiles with pools](/browsers/pools#per-user-profiles-with-browser-pools).
498518
- Profile data is encrypted end to end using a per-organization key.

0 commit comments

Comments
 (0)